Compare commits
8
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2f49f321c1 | ||
|
|
c0007301c1 | ||
|
|
0d8996395c | ||
|
|
e24b043ab2 | ||
|
|
6f15b0f916 | ||
|
|
33eb1dd45e | ||
|
|
0ca8107055 | ||
|
|
d844070855 |
@@ -22,10 +22,6 @@ build/
|
|||||||
yarn.lock
|
yarn.lock
|
||||||
test-results/
|
test-results/
|
||||||
|
|
||||||
# Tracing/debug files
|
|
||||||
.cursor/nal-trace/
|
|
||||||
.cursor/*.log
|
|
||||||
|
|
||||||
# Keep bundled code out of Git
|
# Keep bundled code out of Git
|
||||||
dist/
|
dist/
|
||||||
demo/dist/
|
demo/dist/
|
||||||
|
|||||||
@@ -2,13 +2,10 @@
|
|||||||
|
|
||||||
## Architecture Overview
|
## Architecture Overview
|
||||||
|
|
||||||
**Core Structure**: xterm.js is a multi-target terminal emulator with these main source areas:
|
**Core Structure**: xterm.js is a multi-target terminal emulator with three main distributions:
|
||||||
- `src/primitives/`: Building blocks (buffer, parser, input, data, utilities); TypeScript project `src/primitives/tsconfig.json`
|
- `src/browser/`: Full-featured browser terminal with DOM rendering
|
||||||
- `src/runtime/`: Services, `CoreTerminal`, `InputHandler`, public API adapters; TypeScript project `src/runtime/tsconfig.json`
|
- `src/headless/`: Server-side terminal for Node.js (no DOM)
|
||||||
- `src/target-browser/`: Browser terminal with DOM rendering
|
- `src/common/`: Shared core logic (parsing, buffer management, terminal state)
|
||||||
- `src/target-headless/`: Node terminal without DOM
|
|
||||||
|
|
||||||
See `src/ARCHITECTURE.md` for the dependency graph and import-path conventions (`common/*`, `target-browser/*`, `target-headless/*`).
|
|
||||||
|
|
||||||
**Key Classes**:
|
**Key Classes**:
|
||||||
- `Terminal` (browser/headless): Public API wrapper
|
- `Terminal` (browser/headless): Public API wrapper
|
||||||
@@ -101,15 +98,3 @@ const cell = line?.getCell(0);
|
|||||||
## Writing unit tests
|
## Writing unit tests
|
||||||
|
|
||||||
- Unit tests live alongside the source code file of the thing it's testing with a .test.ts suffix.
|
- Unit tests live alongside the source code file of the thing it's testing with a .test.ts suffix.
|
||||||
|
|
||||||
## Cursor Cloud specific instructions
|
|
||||||
|
|
||||||
**Demo server**: Start with `npm start` (port 3000). The demo server uses node-pty to spawn real shell sessions over WebSocket. Integration tests auto-start it via Playwright's `webServer` config, so you don't need to start it manually for `npm run test-integration`.
|
|
||||||
|
|
||||||
**Build before testing**: Always run `npm run build && npm run esbuild` before `npm run test-unit`. Integration tests also need `npm run esbuild-demo-client` and `npm run esbuild-demo-server`. The update script handles this automatically on session start.
|
|
||||||
|
|
||||||
**No external services**: This project has zero external dependencies (no databases, Docker, or APIs). Everything runs locally with Node.js.
|
|
||||||
|
|
||||||
**TypeScript compiler**: The project uses `tsgo` (native TypeScript compiler preview) rather than standard `tsc`. It's installed via the `@typescript/native-preview` package.
|
|
||||||
|
|
||||||
**Lint only changed files**: Prefer `npm run lint-changes` over `npm run lint` when iterating on code changes — it's significantly faster.
|
|
||||||
|
|||||||
@@ -253,7 +253,6 @@ Xterm.js is used in many world-class applications to provide great terminal expe
|
|||||||
- [**SSH Connection Manager**](https://github.com/Amtrend/ssh-manager): Modern web-based SSH terminal and SFTP manager with AES-256 encryption, PWA support, and session management.
|
- [**SSH Connection Manager**](https://github.com/Amtrend/ssh-manager): Modern web-based SSH terminal and SFTP manager with AES-256 encryption, PWA support, and session management.
|
||||||
- [**WooTTY**](https://github.com/icoretech/wootty): Flawless browser terminal for real operators.
|
- [**WooTTY**](https://github.com/icoretech/wootty): Flawless browser terminal for real operators.
|
||||||
- [**Orca**](https://github.com/stablyai/orca): Agentic development environment for 100x builders.
|
- [**Orca**](https://github.com/stablyai/orca): Agentic development environment for 100x builders.
|
||||||
- [**4bit**](https://ciembor.github.io/4bit): Terminal Color Scheme Designer.
|
|
||||||
- [And much more...](https://github.com/xtermjs/xterm.js/network/dependents?package_id=UGFja2FnZS0xNjYzMjc4OQ%3D%3D)
|
- [And much more...](https://github.com/xtermjs/xterm.js/network/dependents?package_id=UGFja2FnZS0xNjYzMjc4OQ%3D%3D)
|
||||||
|
|
||||||
Do you use xterm.js in your application as well? Please [open a Pull Request](https://github.com/sourcelair/xterm.js/pulls) to include it here. We would love to have it on our list. Please add any new contributions to the end of the list.
|
Do you use xterm.js in your application as well? Please [open a Pull Request](https://github.com/sourcelair/xterm.js/pulls) to include it here. We would love to have it on our list. Please add any new contributions to the end of the list.
|
||||||
|
|||||||
@@ -11,11 +11,10 @@
|
|||||||
"removeComments": true,
|
"removeComments": true,
|
||||||
"paths": {
|
"paths": {
|
||||||
"common/*": [
|
"common/*": [
|
||||||
"../../../src/primitives/*",
|
"../../../src/common/*"
|
||||||
"../../../src/runtime/*",
|
|
||||||
],
|
],
|
||||||
"target-browser/*": [
|
"browser/*": [
|
||||||
"../../../src/target-browser/*"
|
"../../../src/browser/*"
|
||||||
],
|
],
|
||||||
"*": [
|
"*": [
|
||||||
"./*"
|
"./*"
|
||||||
@@ -32,10 +31,10 @@
|
|||||||
],
|
],
|
||||||
"references": [
|
"references": [
|
||||||
{
|
{
|
||||||
"path": "../../../src"
|
"path": "../../../src/common"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"path": "../../../src/target-browser"
|
"path": "../../../src/browser"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"path": "../../../test/playwright"
|
"path": "../../../test/playwright"
|
||||||
|
|||||||
@@ -15,8 +15,8 @@
|
|||||||
"../../../node_modules/@types/mocha"
|
"../../../node_modules/@types/mocha"
|
||||||
],
|
],
|
||||||
"paths": {
|
"paths": {
|
||||||
"target-browser/*": [
|
"browser/*": [
|
||||||
"../../../src/target-browser/*"
|
"../../../src/browser/*"
|
||||||
],
|
],
|
||||||
"@xterm/addon-clipboard": [
|
"@xterm/addon-clipboard": [
|
||||||
"../typings/addon-clipboard.d.ts"
|
"../typings/addon-clipboard.d.ts"
|
||||||
@@ -29,7 +29,7 @@
|
|||||||
],
|
],
|
||||||
"references": [
|
"references": [
|
||||||
{
|
{
|
||||||
"path": "../../../src/target-browser"
|
"path": "../../../src/browser"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,11 +11,10 @@
|
|||||||
"removeComments": true,
|
"removeComments": true,
|
||||||
"paths": {
|
"paths": {
|
||||||
"common/*": [
|
"common/*": [
|
||||||
"../../../src/primitives/*",
|
"../../../src/common/*"
|
||||||
"../../../src/runtime/*",
|
|
||||||
],
|
],
|
||||||
"target-browser/*": [
|
"browser/*": [
|
||||||
"../../../src/target-browser/*"
|
"../../../src/browser/*"
|
||||||
],
|
],
|
||||||
"*": [
|
"*": [
|
||||||
"./*"
|
"./*"
|
||||||
@@ -32,10 +31,10 @@
|
|||||||
],
|
],
|
||||||
"references": [
|
"references": [
|
||||||
{
|
{
|
||||||
"path": "../../../src"
|
"path": "../../../src/common"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"path": "../../../src/target-browser"
|
"path": "../../../src/browser"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"path": "../../../test/playwright"
|
"path": "../../../test/playwright"
|
||||||
|
|||||||
@@ -5,7 +5,7 @@
|
|||||||
|
|
||||||
import type { Terminal, ITerminalAddon, IRenderDimensions } from '@xterm/xterm';
|
import type { Terminal, ITerminalAddon, IRenderDimensions } from '@xterm/xterm';
|
||||||
import type { FitAddon as IFitApi } from '@xterm/addon-fit';
|
import type { FitAddon as IFitApi } from '@xterm/addon-fit';
|
||||||
import { ViewportConstants } from 'target-browser/shared/Constants';
|
import { ViewportConstants } from 'browser/shared/Constants';
|
||||||
|
|
||||||
interface ITerminalDimensions {
|
interface ITerminalDimensions {
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -15,8 +15,8 @@
|
|||||||
"../../../node_modules/@types/mocha"
|
"../../../node_modules/@types/mocha"
|
||||||
],
|
],
|
||||||
"paths": {
|
"paths": {
|
||||||
"target-browser/*": [
|
"browser/*": [
|
||||||
"../../../src/target-browser/*"
|
"../../../src/browser/*"
|
||||||
],
|
],
|
||||||
"@xterm/addon-fit": [
|
"@xterm/addon-fit": [
|
||||||
"../typings/addon-fit.d.ts"
|
"../typings/addon-fit.d.ts"
|
||||||
@@ -29,7 +29,7 @@
|
|||||||
],
|
],
|
||||||
"references": [
|
"references": [
|
||||||
{
|
{
|
||||||
"path": "../../../src/target-browser"
|
"path": "../../../src/browser"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,11 +11,10 @@
|
|||||||
"removeComments": true,
|
"removeComments": true,
|
||||||
"paths": {
|
"paths": {
|
||||||
"common/*": [
|
"common/*": [
|
||||||
"../../../src/primitives/*",
|
"../../../src/common/*"
|
||||||
"../../../src/runtime/*",
|
|
||||||
],
|
],
|
||||||
"target-browser/*": [
|
"browser/*": [
|
||||||
"../../../src/target-browser/*"
|
"../../../src/browser/*"
|
||||||
],
|
],
|
||||||
"*": [
|
"*": [
|
||||||
"./*"
|
"./*"
|
||||||
@@ -32,10 +31,10 @@
|
|||||||
],
|
],
|
||||||
"references": [
|
"references": [
|
||||||
{
|
{
|
||||||
"path": "../../../src"
|
"path": "../../../src/common"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"path": "../../../src/target-browser"
|
"path": "../../../src/browser"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"path": "../../../test/playwright"
|
"path": "../../../test/playwright"
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
## @xterm/addon-image
|
## @xterm/addon-image
|
||||||
|
|
||||||
Inline image output in xterm.js. Supports SIXEL, iTerm's inline image protocol (IIP)
|
Inline image output in xterm.js. Supports SIXEL and iTerm's inline image protocol (IIP).
|
||||||
and partially Kitty's terminal graphics prtocol (TGP).
|
|
||||||
|
|
||||||
|
|
||||||

|

|
||||||
@@ -44,6 +43,9 @@ terminal.loadAddon(imageAddon);
|
|||||||
|
|
||||||
### General Notes
|
### General Notes
|
||||||
|
|
||||||
|
- *IMPORTANT:* The worker approach as done in previous releases got removed.
|
||||||
|
The addon contructor no longer expects a worker path as first argument.
|
||||||
|
|
||||||
- By default the addon will activate these `windowOptions` reports on the terminal:
|
- By default the addon will activate these `windowOptions` reports on the terminal:
|
||||||
- getWinSizePixels (CSI 14 t)
|
- getWinSizePixels (CSI 14 t)
|
||||||
- getCellSizePixels (CSI 16 t)
|
- getCellSizePixels (CSI 16 t)
|
||||||
@@ -71,7 +73,7 @@ terminal.loadAddon(imageAddon);
|
|||||||
|
|
||||||
- **Cursor Positioning**
|
- **Cursor Positioning**
|
||||||
If scrolling is set, the cursor will be placed at the first image column of the last image row (VT340 mode).
|
If scrolling is set, the cursor will be placed at the first image column of the last image row (VT340 mode).
|
||||||
Other cursor positioning modes as used by xterm or mintty are not supported for sixel.
|
Other cursor positioning modes as used by xterm or mintty are not supported.
|
||||||
|
|
||||||
- **SIXEL Palette Handling**
|
- **SIXEL Palette Handling**
|
||||||
By default the addon limits the palette size to 256 registers (as demanded by the DEC specification).
|
By default the addon limits the palette size to 256 registers (as demanded by the DEC specification).
|
||||||
@@ -98,24 +100,17 @@ terminal.loadAddon(imageAddon);
|
|||||||
Set by default, change it with `{iipSupport: true}`.
|
Set by default, change it with `{iipSupport: true}`.
|
||||||
|
|
||||||
The IIP implementation has the following features / restrictions (sequence will silently fail for unmet conditions):
|
The IIP implementation has the following features / restrictions (sequence will silently fail for unmet conditions):
|
||||||
- Supported formats: PNG, JPEG, GIF and QOI
|
- Supported formats: PNG, JPEG and GIF
|
||||||
- No animation support.
|
- No animation support.
|
||||||
- Image type hinting is not supported (always deducted from data header).
|
- Image type hinting is not supported (always deducted from data header).
|
||||||
- File download is not supported.
|
- File download is not supported.
|
||||||
- Filename gets parsed but not used.
|
- Filename gets parsed but not used.
|
||||||
- Strict base64 handling as of RFC4648 §4 (standard alphabet, optional padding, no separator bytes allowed).
|
- Strict base64 handling as of RFC4648 §4 (standard alphabet, optional padding, no separator bytes allowed).
|
||||||
|
- Payload size may not exceed CEIL(sizeParameter * 4 / 3).
|
||||||
- Image scaling beyond terminal viewport size is allowed (e.g. `width=200%`).
|
- Image scaling beyond terminal viewport size is allowed (e.g. `width=200%`).
|
||||||
- Image pixel size is restricted by `pixelLimit` (pre- and post resizing).
|
- Image pixel size is restricted by `pixelLimit` (pre- and post resizing).
|
||||||
- Playload size is restricted by `iipSizeLimit`.
|
- Size parameter is restricted by `iipSizeLimit`.
|
||||||
- size parameter is not mandatory anymore.
|
- Cursor positioning behaves the same as for sixel (see above).
|
||||||
- Cursor gets positioned at the next cell of the last image line.
|
|
||||||
- ReportCellSize sequence is supported.
|
|
||||||
- Hi-res display support is currently limited (image gets blurred).
|
|
||||||
|
|
||||||
- **Kitty Graphics Support (TGP)**
|
|
||||||
Set by default, change it with `{kittySupport: true}`.
|
|
||||||
|
|
||||||
Note that the kitty graphics support is still WIP.
|
|
||||||
|
|
||||||
|
|
||||||
### Storage and Drawing Settings
|
### Storage and Drawing Settings
|
||||||
@@ -132,7 +127,6 @@ The addon exposes two properties to interact with the storage limits at runtime:
|
|||||||
By default the addon will show a placeholder pattern for evicted images that are still part
|
By default the addon will show a placeholder pattern for evicted images that are still part
|
||||||
of the terminal (e.g. in the scrollback). The pattern can be deactivated by toggling `showPlaceholder`.
|
of the terminal (e.g. in the scrollback). The pattern can be deactivated by toggling `showPlaceholder`.
|
||||||
|
|
||||||
|
|
||||||
### Image Data Retrieval
|
### Image Data Retrieval
|
||||||
|
|
||||||
The addon provides the following API endpoints to retrieve raw image data as canvas:
|
The addon provides the following API endpoints to retrieve raw image data as canvas:
|
||||||
@@ -146,7 +140,6 @@ The addon provides the following API endpoints to retrieve raw image data as can
|
|||||||
The buffer position is the 0-based absolute index (including scrollback at top).
|
The buffer position is the 0-based absolute index (including scrollback at top).
|
||||||
Note that the canvas gets created and data copied over for every call, thus it is not suitable for performance critical actions.
|
Note that the canvas gets created and data copied over for every call, thus it is not suitable for performance critical actions.
|
||||||
|
|
||||||
|
|
||||||
### Memory Usage
|
### Memory Usage
|
||||||
|
|
||||||
The addon does most image processing in Javascript and therefore can occupy a rather big amount of memory. To get an idea where the memory gets eaten, lets look at the data flow and processing steps:
|
The addon does most image processing in Javascript and therefore can occupy a rather big amount of memory. To get an idea where the memory gets eaten, lets look at the data flow and processing steps:
|
||||||
@@ -187,8 +180,8 @@ _How can I adjust the memory usage?_
|
|||||||
A constructor setting, thus you would have to anticipate, whether (multiple) terminals in your page gonna do lots of concurrent decoding. Since this is normally not the case and the memory usage is only temporarily peaking, a rather high value should work even with multiple terminals in one page.
|
A constructor setting, thus you would have to anticipate, whether (multiple) terminals in your page gonna do lots of concurrent decoding. Since this is normally not the case and the memory usage is only temporarily peaking, a rather high value should work even with multiple terminals in one page.
|
||||||
- `storageLimit`
|
- `storageLimit`
|
||||||
A constructor and a runtime setting. In conjunction with `storageUsage` you can do runtime checks and adjust the limit to your needs. If you have to lower the limit below the current usage, images will be removed in FIFO order and may turn into a placeholder in the terminal's scrollback (if `showPlaceholder` is set). When adjusting keep in mind to leave enough room for memory peaking for decoding.
|
A constructor and a runtime setting. In conjunction with `storageUsage` you can do runtime checks and adjust the limit to your needs. If you have to lower the limit below the current usage, images will be removed in FIFO order and may turn into a placeholder in the terminal's scrollback (if `showPlaceholder` is set). When adjusting keep in mind to leave enough room for memory peaking for decoding.
|
||||||
- `sixelSizeLimit | iipSizeLimit | kittySizeLimit`
|
- `sixelSizeLimit`
|
||||||
Constructor settings. This has only a small impact on peaking memory during decoding. It is meant to avoid processing of overly big or broken image sequences at an earlier phase, thus may stop the invoked decoders from entering memory intensive tasks for potentially invalid data.
|
A constructor setting. This has only a small impact on peaking memory during decoding. It is meant to avoid processing of overly big or broken SIXEL sequences at an earlier phase, thus may stop the decoder from entering its memory intensive task for potentially invalid data.
|
||||||
|
|
||||||
|
|
||||||
### Terminal Interaction
|
### Terminal Interaction
|
||||||
@@ -218,5 +211,23 @@ _How can I adjust the memory usage?_
|
|||||||
|
|
||||||
### Status
|
### Status
|
||||||
|
|
||||||
- Sixel and IIP support and image handling in xterm.js is considered beta quality.
|
- Sixel support and image handling in xterm.js is considered beta quality.
|
||||||
- Kitty support is in alpha stage. Please file a bug for any awkwardities or missing features.
|
- IIP support is in alpha stage. Please file a bug for any awkwardities.
|
||||||
|
|
||||||
|
|
||||||
|
### Changelog
|
||||||
|
|
||||||
|
- 0.5.0 integrate with xtermjs base repo (at v0.4.3)
|
||||||
|
- 0.4.3 defer canvas creation
|
||||||
|
- 0.4.2 fix image canvas resize
|
||||||
|
- 0.4.1 compat release for xterm.js 5.2.0
|
||||||
|
- 0.4.0 IIP support
|
||||||
|
- 0.3.1 compat release for xterm.js 5.1.0
|
||||||
|
- 0.3.0 important change: worker removed from addon
|
||||||
|
- 0.2.0 compat release for xterm.js 5.0.0
|
||||||
|
- 0.1.3 bugfix: avoid striping
|
||||||
|
- 0.1.2 bugfix: reset clear flag
|
||||||
|
- 0.1.1 bugfixes:
|
||||||
|
- clear sticky image tiles on render
|
||||||
|
- create folder from bootstrap.sh
|
||||||
|
- fix peer dependency in package.json
|
||||||
|
|||||||
Binary file not shown.
@@ -8,7 +8,7 @@ import { IIPImageStorage } from './IIPImageStorage';
|
|||||||
import { CELL_SIZE_DEFAULT } from './ImageStorage';
|
import { CELL_SIZE_DEFAULT } from './ImageStorage';
|
||||||
import Base64Decoder from 'xterm-wasm-parts/lib/base64/Base64Decoder.wasm';
|
import Base64Decoder from 'xterm-wasm-parts/lib/base64/Base64Decoder.wasm';
|
||||||
import QoiDecoder from 'xterm-wasm-parts/lib/qoi/QoiDecoder.wasm';
|
import QoiDecoder from 'xterm-wasm-parts/lib/qoi/QoiDecoder.wasm';
|
||||||
import { HeaderParser, IHeaderFields, HeaderState, SequenceType } from './IIPHeaderParser';
|
import { HeaderParser, IHeaderFields, HeaderState } from './IIPHeaderParser';
|
||||||
import { imageType, UNSUPPORTED_TYPE } from './IIPMetrics';
|
import { imageType, UNSUPPORTED_TYPE } from './IIPMetrics';
|
||||||
|
|
||||||
// Local const enum mirror - esbuild can't inline const enums from external packages
|
// Local const enum mirror - esbuild can't inline const enums from external packages
|
||||||
@@ -23,7 +23,6 @@ const enum DecoderConst {
|
|||||||
|
|
||||||
// default IIP header values
|
// default IIP header values
|
||||||
const DEFAULT_HEADER: IHeaderFields = {
|
const DEFAULT_HEADER: IHeaderFields = {
|
||||||
type: SequenceType.INVALID,
|
|
||||||
name: 'Unnamed file',
|
name: 'Unnamed file',
|
||||||
size: 0,
|
size: 0,
|
||||||
width: 'auto',
|
width: 'auto',
|
||||||
@@ -40,8 +39,6 @@ export class IIPHandler implements IOscHandler, IResetHandler {
|
|||||||
private _dec: Base64Decoder;
|
private _dec: Base64Decoder;
|
||||||
private _qoiDec: QoiDecoder;
|
private _qoiDec: QoiDecoder;
|
||||||
private _metrics = UNSUPPORTED_TYPE;
|
private _metrics = UNSUPPORTED_TYPE;
|
||||||
private _isMultipart = false;
|
|
||||||
private _abortMulti = false;
|
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
private readonly _opts: IImageAddonOptions,
|
private readonly _opts: IImageAddonOptions,
|
||||||
@@ -55,14 +52,11 @@ export class IIPHandler implements IOscHandler, IResetHandler {
|
|||||||
this._qoiDec = new QoiDecoder(DecoderConst.KEEP_DATA);
|
this._qoiDec = new QoiDecoder(DecoderConst.KEEP_DATA);
|
||||||
}
|
}
|
||||||
|
|
||||||
public reset(): void {
|
public reset(): void {}
|
||||||
this._hp.reset();
|
|
||||||
this._dec.release();
|
|
||||||
this._qoiDec.release();
|
|
||||||
}
|
|
||||||
|
|
||||||
public start(): void {
|
public start(): void {
|
||||||
this._aborted = false;
|
this._aborted = false;
|
||||||
|
this._header = DEFAULT_HEADER;
|
||||||
this._metrics = UNSUPPORTED_TYPE;
|
this._metrics = UNSUPPORTED_TYPE;
|
||||||
this._hp.reset();
|
this._hp.reset();
|
||||||
}
|
}
|
||||||
@@ -82,27 +76,15 @@ export class IIPHandler implements IOscHandler, IResetHandler {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (dataPos > 0) {
|
if (dataPos > 0) {
|
||||||
const seqType = this._hp.fields.type;
|
this._header = Object.assign({}, DEFAULT_HEADER, this._hp.fields);
|
||||||
if (seqType === SequenceType.FILE) {
|
if (!this._header.inline || !this._header.size || this._header.size > this._opts.iipSizeLimit) {
|
||||||
if (this._isMultipart) {
|
|
||||||
this._isMultipart = false;
|
|
||||||
this._abortMulti = false;
|
|
||||||
this._dec.release();
|
|
||||||
}
|
|
||||||
this._header = Object.assign({}, DEFAULT_HEADER, this._hp.fields);
|
|
||||||
if (!this._header.inline) {
|
|
||||||
this._aborted = true;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
this._dec.init();
|
|
||||||
} else if (this._abortMulti) {
|
|
||||||
this._aborted = true;
|
this._aborted = true;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
this._dec.init();
|
||||||
if ((this._dec.put(data.subarray(dataPos, end)) as number) !== DecoderConst.OK) {
|
if ((this._dec.put(data.subarray(dataPos, end)) as number) !== DecoderConst.OK) {
|
||||||
this._dec.release();
|
this._dec.release();
|
||||||
this._aborted = true;
|
this._aborted = true;
|
||||||
if (this._isMultipart) this._abortMulti = true;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -111,58 +93,22 @@ export class IIPHandler implements IOscHandler, IResetHandler {
|
|||||||
public end(success: boolean): boolean | Promise<boolean> {
|
public end(success: boolean): boolean | Promise<boolean> {
|
||||||
if (this._aborted) return true;
|
if (this._aborted) return true;
|
||||||
|
|
||||||
if (this._hp.state !== HeaderState.END) {
|
|
||||||
if (this._hp.end()) return true;
|
|
||||||
}
|
|
||||||
const seqType = this._hp.fields.type;
|
|
||||||
|
|
||||||
if (seqType === SequenceType.FILEPART) return true;
|
|
||||||
|
|
||||||
if (seqType === SequenceType.REPORTCELLSIZE) {
|
|
||||||
// OSC 1337 ; ReportCellSize=[height];[width];[scale] ST
|
|
||||||
let width = CELL_SIZE_DEFAULT.width;
|
|
||||||
let height = CELL_SIZE_DEFAULT.height;
|
|
||||||
if (this._renderer.dimensions) {
|
|
||||||
width = this._renderer.dimensions.css.canvas.width / this._coreTerminal.cols;
|
|
||||||
height = this._renderer.dimensions.css.canvas.height / this._coreTerminal.rows;
|
|
||||||
}
|
|
||||||
const scale = this._coreTerminal._core._coreBrowserService?.dpr ?? 1;
|
|
||||||
const report = `\x1b]1337;ReportCellSize=${height.toFixed(3)};${width.toFixed(3)};${scale.toFixed(3)}\x1b\\`;
|
|
||||||
this._coreTerminal.input(report, false);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (seqType === SequenceType.MULTIPARTFILE) {
|
|
||||||
this._header = Object.assign({}, DEFAULT_HEADER, this._hp.fields);
|
|
||||||
this._isMultipart = true;
|
|
||||||
this._abortMulti = false;
|
|
||||||
this._dec.release();
|
|
||||||
this._dec.init();
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (seqType === SequenceType.FILEEND) {
|
|
||||||
if (!this._isMultipart) return true;
|
|
||||||
this._isMultipart = false;
|
|
||||||
if (this._abortMulti || this._header.type !== SequenceType.MULTIPARTFILE) return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
// fallthrough for SequenceType.FILE & SequenceType.FILEEND
|
|
||||||
|
|
||||||
let w = 0;
|
let w = 0;
|
||||||
let h = 0;
|
let h = 0;
|
||||||
|
|
||||||
// early exit condition chain
|
// early exit condition chain
|
||||||
let cond: number | boolean;
|
let cond: number | boolean = true;
|
||||||
if (cond = success) {
|
if (cond = success) {
|
||||||
if (cond = !this._dec.end()) {
|
if (cond = !this._dec.end()) {
|
||||||
this._metrics = imageType(this._dec.data8);
|
if (cond = this._dec.data8.length === this._header.size) {
|
||||||
if (cond = this._metrics.mime !== 'unsupported') {
|
this._metrics = imageType(this._dec.data8);
|
||||||
w = this._metrics.width;
|
if (cond = this._metrics.mime !== 'unsupported') {
|
||||||
h = this._metrics.height;
|
w = this._metrics.width;
|
||||||
if (cond = w && h && w * h < this._opts.pixelLimit) {
|
h = this._metrics.height;
|
||||||
[w, h] = this._resize(w, h).map(Math.floor);
|
if (cond = w && h && w * h < this._opts.pixelLimit) {
|
||||||
cond = w && h && w * h < this._opts.pixelLimit;
|
[w, h] = this._resize(w, h).map(Math.floor);
|
||||||
|
cond = w && h && w * h < this._opts.pixelLimit;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,20 +4,19 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import { assert } from 'chai';
|
import { assert } from 'chai';
|
||||||
import { HeaderParser, HeaderState, IHeaderFields, SequenceType } from './IIPHeaderParser';
|
import { HeaderParser, HeaderState, IHeaderFields } from './IIPHeaderParser';
|
||||||
|
|
||||||
|
|
||||||
const CASES: [string, IHeaderFields][] = [
|
const CASES: [string, IHeaderFields][] = [
|
||||||
['File=size=123456;name=dGVzdA==:', {type: SequenceType.FILE, name: 'test', size: 123456}],
|
['File=size=123456;name=dGVzdA==:', {name: 'test', size: 123456}],
|
||||||
['File=size=123456;name=dGVzdA:', {type: SequenceType.FILE, name: 'test', size: 123456}],
|
['File=size=123456;name=dGVzdA:', {name: 'test', size: 123456}],
|
||||||
// utf-8 encoding in name
|
// utf-8 encoding in name
|
||||||
['File=size=123456;name=w7xtbMOkdXTDnw==:', {type: SequenceType.FILE, name: 'ümläutß', size: 123456}],
|
['File=size=123456;name=w7xtbMOkdXTDnw==:', {name: 'ümläutß', size: 123456}],
|
||||||
['File=size=123456;name=w7xtbMOkdXTDnw:', {type: SequenceType.FILE, name: 'ümläutß', size: 123456}],
|
['File=size=123456;name=w7xtbMOkdXTDnw:', {name: 'ümläutß', size: 123456}],
|
||||||
// full header spec
|
// full header spec
|
||||||
[
|
[
|
||||||
'File=inline=1;width=10px;height=20%;preserveAspectRatio=1;size=123456;name=w7xtbMOkdXTDnw:',
|
'File=inline=1;width=10px;height=20%;preserveAspectRatio=1;size=123456;name=w7xtbMOkdXTDnw:',
|
||||||
{
|
{
|
||||||
type: SequenceType.FILE,
|
|
||||||
inline: 1,
|
inline: 1,
|
||||||
width: '10px',
|
width: '10px',
|
||||||
height: '20%',
|
height: '20%',
|
||||||
@@ -29,7 +28,6 @@ const CASES: [string, IHeaderFields][] = [
|
|||||||
[
|
[
|
||||||
'File=inline=1;width=auto;height=20;preserveAspectRatio=1;size=123456;name=w7xtbMOkdXTDnw:',
|
'File=inline=1;width=auto;height=20;preserveAspectRatio=1;size=123456;name=w7xtbMOkdXTDnw:',
|
||||||
{
|
{
|
||||||
type: SequenceType.FILE,
|
|
||||||
inline: 1,
|
inline: 1,
|
||||||
width: 'auto',
|
width: 'auto',
|
||||||
height: '20',
|
height: '20',
|
||||||
@@ -103,7 +101,7 @@ describe('IIPHeaderParser', () => {
|
|||||||
let res = hp.parse(inp, 0, inp.length);
|
let res = hp.parse(inp, 0, inp.length);
|
||||||
assert.strictEqual(res, inp.length);
|
assert.strictEqual(res, inp.length);
|
||||||
assert.strictEqual(hp.state, HeaderState.END);
|
assert.strictEqual(hp.state, HeaderState.END);
|
||||||
assert.deepEqual(hp.fields, {type: SequenceType.FILE, name: 'test', size: 0});
|
assert.deepEqual(hp.fields, {name: 'test', size: 0});
|
||||||
hp.reset();
|
hp.reset();
|
||||||
inp = fromBs(CASES[0][0]);
|
inp = fromBs(CASES[0][0]);
|
||||||
res = hp.parse(inp, 0, inp.length);
|
res = hp.parse(inp, 0, inp.length);
|
||||||
@@ -117,7 +115,7 @@ describe('IIPHeaderParser', () => {
|
|||||||
let res = hp.parse(inp, 0, inp.length);
|
let res = hp.parse(inp, 0, inp.length);
|
||||||
assert.strictEqual(res, inp.length);
|
assert.strictEqual(res, inp.length);
|
||||||
assert.strictEqual(hp.state, HeaderState.END);
|
assert.strictEqual(hp.state, HeaderState.END);
|
||||||
assert.deepEqual(hp.fields, {type: SequenceType.FILE, name: '', size: 123456});
|
assert.deepEqual(hp.fields, {name: '', size: 123456});
|
||||||
hp.reset();
|
hp.reset();
|
||||||
inp = fromBs(CASES[0][0]);
|
inp = fromBs(CASES[0][0]);
|
||||||
res = hp.parse(inp, 0, inp.length);
|
res = hp.parse(inp, 0, inp.length);
|
||||||
@@ -137,124 +135,4 @@ describe('IIPHeaderParser', () => {
|
|||||||
assert.strictEqual(hp.state, HeaderState.END);
|
assert.strictEqual(hp.state, HeaderState.END);
|
||||||
assert.deepEqual(hp.fields, CASES[0][1]);
|
assert.deepEqual(hp.fields, CASES[0][1]);
|
||||||
});
|
});
|
||||||
it('FilePart sequence', () => {
|
|
||||||
const hp = new HeaderParser();
|
|
||||||
const inp = fromBs('FilePart=w7xtbMOkdXTDnw');
|
|
||||||
const res = hp.parse(inp, 0, inp.length);
|
|
||||||
assert.strictEqual(res, 9);
|
|
||||||
assert.strictEqual(hp.state, HeaderState.END);
|
|
||||||
assert.deepEqual(hp.fields.type, SequenceType.FILEPART);
|
|
||||||
});
|
|
||||||
it('FilePart sequence - bytewise', () => {
|
|
||||||
const hp = new HeaderParser();
|
|
||||||
const inp = fromBs('FilePart=w7xtbMOkdXTDnw');
|
|
||||||
let res = -2;
|
|
||||||
let pos = 0;
|
|
||||||
while (res === -2 && pos < inp.length) {
|
|
||||||
res = hp.parse(new Uint32Array([inp[pos++]]), 0, 1);
|
|
||||||
}
|
|
||||||
assert.strictEqual(res, 1);
|
|
||||||
assert.strictEqual(hp.state, HeaderState.END);
|
|
||||||
assert.deepEqual(hp.fields.type, SequenceType.FILEPART);
|
|
||||||
});
|
|
||||||
it('MultipartFile sequence', () => {
|
|
||||||
const hp = new HeaderParser();
|
|
||||||
const inp = fromBs('MultipartFile=inline=1;width=10px;height=20%;preserveAspectRatio=1;size=123456;name=w7xtbMOkdXTDnw');
|
|
||||||
let res = hp.parse(inp, 0, inp.length);
|
|
||||||
assert.strictEqual(res, -2);
|
|
||||||
assert.strictEqual(hp.state, HeaderState.VALUE);
|
|
||||||
res = hp.end();
|
|
||||||
assert.strictEqual(res, 0);
|
|
||||||
assert.strictEqual(hp.state, HeaderState.END);
|
|
||||||
assert.deepEqual(
|
|
||||||
hp.fields,
|
|
||||||
{
|
|
||||||
type: SequenceType.MULTIPARTFILE,
|
|
||||||
inline: 1,
|
|
||||||
width: '10px',
|
|
||||||
height: '20%',
|
|
||||||
preserveAspectRatio: 1,
|
|
||||||
size: 123456,
|
|
||||||
name: 'ümläutß'
|
|
||||||
}
|
|
||||||
);
|
|
||||||
});
|
|
||||||
it('MultipartFile sequence - bytewise', () => {
|
|
||||||
const hp = new HeaderParser();
|
|
||||||
const inp = fromBs('MultipartFile=inline=1;width=10px;height=20%;preserveAspectRatio=1;size=123456;name=w7xtbMOkdXTDnw');
|
|
||||||
let res = -2;
|
|
||||||
let pos = 0;
|
|
||||||
while (res === -2 && pos < inp.length) {
|
|
||||||
res = hp.parse(new Uint32Array([inp[pos++]]), 0, 1);
|
|
||||||
}
|
|
||||||
assert.strictEqual(res, -2);
|
|
||||||
assert.strictEqual(hp.state, HeaderState.VALUE);
|
|
||||||
res = hp.end();
|
|
||||||
assert.strictEqual(res, 0);
|
|
||||||
assert.strictEqual(hp.state, HeaderState.END);
|
|
||||||
assert.deepEqual(
|
|
||||||
hp.fields,
|
|
||||||
{
|
|
||||||
type: SequenceType.MULTIPARTFILE,
|
|
||||||
inline: 1,
|
|
||||||
width: '10px',
|
|
||||||
height: '20%',
|
|
||||||
preserveAspectRatio: 1,
|
|
||||||
size: 123456,
|
|
||||||
name: 'ümläutß'
|
|
||||||
}
|
|
||||||
);
|
|
||||||
});
|
|
||||||
it('FileEnd sequence', () => {
|
|
||||||
const hp = new HeaderParser();
|
|
||||||
const inp = fromBs('FileEnd');
|
|
||||||
let res = hp.parse(inp, 0, inp.length);
|
|
||||||
assert.strictEqual(res, -2);
|
|
||||||
assert.strictEqual(hp.state, HeaderState.START);
|
|
||||||
res = hp.end();
|
|
||||||
assert.strictEqual(res, 0);
|
|
||||||
assert.strictEqual(hp.state, HeaderState.END);
|
|
||||||
assert.deepEqual(hp.fields.type, SequenceType.FILEEND);
|
|
||||||
});
|
|
||||||
it('FileEnd sequence - bytewise', () => {
|
|
||||||
const hp = new HeaderParser();
|
|
||||||
const inp = fromBs('FileEnd');
|
|
||||||
let res = -2;
|
|
||||||
let pos = 0;
|
|
||||||
while (res === -2 && pos < inp.length) {
|
|
||||||
res = hp.parse(new Uint32Array([inp[pos++]]), 0, 1);
|
|
||||||
}
|
|
||||||
assert.strictEqual(res, -2);
|
|
||||||
assert.strictEqual(hp.state, HeaderState.START);
|
|
||||||
res = hp.end();
|
|
||||||
assert.strictEqual(res, 0);
|
|
||||||
assert.strictEqual(hp.state, HeaderState.END);
|
|
||||||
assert.deepEqual(hp.fields.type, SequenceType.FILEEND);
|
|
||||||
});
|
|
||||||
it('ReportCellSize sequence', () => {
|
|
||||||
const hp = new HeaderParser();
|
|
||||||
const inp = fromBs('ReportCellSize');
|
|
||||||
let res = hp.parse(inp, 0, inp.length);
|
|
||||||
assert.strictEqual(res, -2);
|
|
||||||
assert.strictEqual(hp.state, HeaderState.START);
|
|
||||||
res = hp.end();
|
|
||||||
assert.strictEqual(res, 0);
|
|
||||||
assert.strictEqual(hp.state, HeaderState.END);
|
|
||||||
assert.deepEqual(hp.fields, { type: SequenceType.REPORTCELLSIZE });
|
|
||||||
});
|
|
||||||
it('ReportCellSize sequence - bytewise', () => {
|
|
||||||
const hp = new HeaderParser();
|
|
||||||
const inp = fromBs('ReportCellSize');
|
|
||||||
let res = -2;
|
|
||||||
let pos = 0;
|
|
||||||
while (res === -2 && pos < inp.length) {
|
|
||||||
res = hp.parse(new Uint32Array([inp[pos++]]), 0, 1);
|
|
||||||
}
|
|
||||||
assert.strictEqual(res, -2);
|
|
||||||
assert.strictEqual(hp.state, HeaderState.START);
|
|
||||||
res = hp.end();
|
|
||||||
assert.strictEqual(res, 0);
|
|
||||||
assert.strictEqual(hp.state, HeaderState.END);
|
|
||||||
assert.deepEqual(hp.fields, { type: SequenceType.REPORTCELLSIZE });
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -6,27 +6,9 @@
|
|||||||
// eslint-disable-next-line
|
// eslint-disable-next-line
|
||||||
declare const Buffer: any;
|
declare const Buffer: any;
|
||||||
|
|
||||||
export const enum HeaderState {
|
|
||||||
START = 0,
|
|
||||||
ABORT = 1,
|
|
||||||
KEY = 2,
|
|
||||||
VALUE = 3,
|
|
||||||
END = 4
|
|
||||||
}
|
|
||||||
|
|
||||||
export const enum SequenceType {
|
|
||||||
INVALID = 0,
|
|
||||||
FILE = 1,
|
|
||||||
MULTIPARTFILE = 2,
|
|
||||||
FILEPART = 3,
|
|
||||||
FILEEND = 4,
|
|
||||||
REPORTCELLSIZE = 5
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface IHeaderFields {
|
export interface IHeaderFields {
|
||||||
[key: string]: number | string | Uint32Array | null | undefined;
|
[key: string]: number | string | Uint32Array | null | undefined;
|
||||||
// sequence type
|
|
||||||
type: SequenceType;
|
|
||||||
// base-64 encoded filename. Defaults to "Unnamed file".
|
// base-64 encoded filename. Defaults to "Unnamed file".
|
||||||
name: string;
|
name: string;
|
||||||
// File size in bytes. The file transfer will be canceled if this size is exceeded.
|
// File size in bytes. The file transfer will be canceled if this size is exceeded.
|
||||||
@@ -47,6 +29,14 @@ export interface IHeaderFields {
|
|||||||
inline?: number;
|
inline?: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const enum HeaderState {
|
||||||
|
START = 0,
|
||||||
|
ABORT = 1,
|
||||||
|
KEY = 2,
|
||||||
|
VALUE = 3,
|
||||||
|
END = 4
|
||||||
|
}
|
||||||
|
|
||||||
// field value decoders
|
// field value decoders
|
||||||
|
|
||||||
// ASCII bytes to string
|
// ASCII bytes to string
|
||||||
@@ -102,19 +92,7 @@ const DECODERS: {[key: string]: (v: Uint32Array) => number | string} = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
// sequence type markers
|
|
||||||
// File
|
|
||||||
const FILE_MARKER = [70, 105, 108, 101];
|
const FILE_MARKER = [70, 105, 108, 101];
|
||||||
// MultipartFile
|
|
||||||
const MULTIPARTFILE_MARKER = [77, 117, 108, 116, 105, 112, 97, 114, 116, 70, 105, 108, 101];
|
|
||||||
// FilePart
|
|
||||||
const FILEPART_MARKER = [70, 105, 108, 101, 80, 97, 114, 116];
|
|
||||||
// FileEnd
|
|
||||||
const FILEEND_MARKER = [70, 105, 108, 101, 69, 110, 100];
|
|
||||||
// ReportCellSize
|
|
||||||
const REPORTCELLSIZE_MARKER = [82, 101, 112, 111, 114, 116, 67, 101, 108, 108, 83, 105, 122, 101];
|
|
||||||
|
|
||||||
// max allowed chars for sequence header
|
|
||||||
const MAX_FIELDCHARS = 1024;
|
const MAX_FIELDCHARS = 1024;
|
||||||
|
|
||||||
|
|
||||||
@@ -133,43 +111,12 @@ export class HeaderParser {
|
|||||||
this._key = '';
|
this._key = '';
|
||||||
}
|
}
|
||||||
|
|
||||||
public end(): number {
|
|
||||||
if (this.state === HeaderState.START) {
|
|
||||||
if (this._position === FILEEND_MARKER.length) {
|
|
||||||
for (let k = 0; k < FILEEND_MARKER.length; ++k) {
|
|
||||||
if (this._buffer[k] !== FILEEND_MARKER[k]) return this._a();
|
|
||||||
}
|
|
||||||
this.fields['type'] = SequenceType.FILEEND;
|
|
||||||
this.state = HeaderState.END;
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
if (this._position === REPORTCELLSIZE_MARKER.length) {
|
|
||||||
for (let k = 0; k < REPORTCELLSIZE_MARKER.length; ++k) {
|
|
||||||
if (this._buffer[k] !== REPORTCELLSIZE_MARKER[k]) return this._a();
|
|
||||||
}
|
|
||||||
this.fields['type'] = SequenceType.REPORTCELLSIZE;
|
|
||||||
this.state = HeaderState.END;
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
return this._a();
|
|
||||||
}
|
|
||||||
if (this.state === HeaderState.END) return 0;
|
|
||||||
if (this.state === HeaderState.VALUE
|
|
||||||
&& this.fields.type === SequenceType.MULTIPARTFILE
|
|
||||||
) {
|
|
||||||
if (!this._storeValue(this._position)) return this._a();
|
|
||||||
this.state = HeaderState.END;
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
return this._a();
|
|
||||||
}
|
|
||||||
|
|
||||||
public parse(data: Uint32Array, start: number, end: number): number {
|
public parse(data: Uint32Array, start: number, end: number): number {
|
||||||
let state = this.state;
|
let state = this.state;
|
||||||
let pos = this._position;
|
let pos = this._position;
|
||||||
const buffer = this._buffer;
|
const buffer = this._buffer;
|
||||||
if (state === HeaderState.ABORT || state === HeaderState.END) return -1;
|
if (state === HeaderState.ABORT || state === HeaderState.END) return -1;
|
||||||
if (state === HeaderState.START && pos > 14) return -1;
|
if (state === HeaderState.START && pos > 6) return -1;
|
||||||
for (let i = start; i < end; ++i) {
|
for (let i = start; i < end; ++i) {
|
||||||
const c = data[i];
|
const c = data[i];
|
||||||
switch (c) {
|
switch (c) {
|
||||||
@@ -180,29 +127,8 @@ export class HeaderParser {
|
|||||||
break;
|
break;
|
||||||
case 61: // =
|
case 61: // =
|
||||||
if (state === HeaderState.START) {
|
if (state === HeaderState.START) {
|
||||||
if (buffer[0] === 70) {
|
for (let k = 0; k < FILE_MARKER.length; ++k) {
|
||||||
// 'File' or 'FilePart'
|
if (buffer[k] !== FILE_MARKER[k]) return this._a();
|
||||||
let k = 0;
|
|
||||||
for (; k < FILE_MARKER.length; ++k) {
|
|
||||||
if (buffer[k] !== FILE_MARKER[k]) return this._a();
|
|
||||||
}
|
|
||||||
this.fields['type'] = SequenceType.FILE;
|
|
||||||
if (pos === FILEPART_MARKER.length) {
|
|
||||||
for (; k < FILEPART_MARKER.length; ++k) {
|
|
||||||
if (buffer[k] !== FILEPART_MARKER[k]) return this._a();
|
|
||||||
}
|
|
||||||
this.fields['type'] = SequenceType.FILEPART;
|
|
||||||
this.state = HeaderState.END;
|
|
||||||
return i + 1;
|
|
||||||
}
|
|
||||||
} else if (buffer[0] === 77) {
|
|
||||||
// 'MultipartFile'
|
|
||||||
for (let k = 0; k < MULTIPARTFILE_MARKER.length; ++k) {
|
|
||||||
if (buffer[k] !== MULTIPARTFILE_MARKER[k]) return this._a();
|
|
||||||
}
|
|
||||||
this.fields['type'] = SequenceType.MULTIPARTFILE;
|
|
||||||
} else {
|
|
||||||
return this._a();
|
|
||||||
}
|
}
|
||||||
state = HeaderState.KEY;
|
state = HeaderState.KEY;
|
||||||
pos = 0;
|
pos = 0;
|
||||||
@@ -232,7 +158,6 @@ export class HeaderParser {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private _a(): number {
|
private _a(): number {
|
||||||
this.fields.type = SequenceType.INVALID;
|
|
||||||
this.state = HeaderState.ABORT;
|
this.state = HeaderState.ABORT;
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,7 +3,6 @@
|
|||||||
* @license MIT
|
* @license MIT
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { IAddImageOpts } from 'Types';
|
|
||||||
import { ImageStorage } from './ImageStorage';
|
import { ImageStorage } from './ImageStorage';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -13,7 +12,6 @@ import { ImageStorage } from './ImageStorage';
|
|||||||
* - Always uses scrolling mode (cursor advances with image)
|
* - Always uses scrolling mode (cursor advances with image)
|
||||||
*/
|
*/
|
||||||
export class IIPImageStorage {
|
export class IIPImageStorage {
|
||||||
private _addImageOpts: IAddImageOpts = { scrolling: true, layer: 'top', zIndex: 0, cursorPos: 'iip' };
|
|
||||||
constructor(
|
constructor(
|
||||||
private readonly _storage: ImageStorage
|
private readonly _storage: ImageStorage
|
||||||
) {}
|
) {}
|
||||||
@@ -23,6 +21,6 @@ export class IIPImageStorage {
|
|||||||
* Always uses scrolling mode — cursor advances past the image.
|
* Always uses scrolling mode — cursor advances past the image.
|
||||||
*/
|
*/
|
||||||
public addImage(img: HTMLCanvasElement | ImageBitmap): void {
|
public addImage(img: HTMLCanvasElement | ImageBitmap): void {
|
||||||
this._storage.addImage(img, this._addImageOpts);
|
this._storage.addImage(img, true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -30,8 +30,7 @@ const TEST_IMAGES: [string, IMetrics][] = [
|
|||||||
['iphone_hdr_YES.jpg', { mime: 'image/jpeg', width: 3264, height: 2448 }],
|
['iphone_hdr_YES.jpg', { mime: 'image/jpeg', width: 3264, height: 2448 }],
|
||||||
['nikon-e950.jpg', { mime: 'image/jpeg', width: 800, height: 600 }],
|
['nikon-e950.jpg', { mime: 'image/jpeg', width: 800, height: 600 }],
|
||||||
['agfa-makernotes.jpg', { mime: 'image/jpeg', width: 8, height: 8 }],
|
['agfa-makernotes.jpg', { mime: 'image/jpeg', width: 8, height: 8 }],
|
||||||
['sony-alpha-6000.jpg', { mime: 'image/jpeg', width: 6000, height: 4000 }],
|
['sony-alpha-6000.jpg', { mime: 'image/jpeg', width: 6000, height: 4000 }]
|
||||||
['dice.qoi', { mime: 'image/qoi', width: 800, height: 600 }],
|
|
||||||
];
|
];
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -137,6 +137,11 @@ export class ImageAddon implements ITerminalAddon, IImageApi {
|
|||||||
|
|
||||||
// enable size reports
|
// enable size reports
|
||||||
if (this._opts.enableSizeReports) {
|
if (this._opts.enableSizeReports) {
|
||||||
|
// const windowOptions = terminal.getOption('windowOptions');
|
||||||
|
// windowOptions.getWinSizePixels = true;
|
||||||
|
// windowOptions.getCellSizePixels = true;
|
||||||
|
// windowOptions.getWinSizeChars = true;
|
||||||
|
// terminal.setOption('windowOptions', windowOptions);
|
||||||
const windowOps = terminal.options.windowOptions ?? {};
|
const windowOps = terminal.options.windowOptions ?? {};
|
||||||
windowOps.getWinSizePixels = true;
|
windowOps.getWinSizePixels = true;
|
||||||
windowOps.getCellSizePixels = true;
|
windowOps.getCellSizePixels = true;
|
||||||
@@ -255,7 +260,7 @@ export class ImageAddon implements ITerminalAddon, IImageApi {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private _report(s: string): void {
|
private _report(s: string): void {
|
||||||
this._terminal?._core.input(s, false);
|
this._terminal?._core.coreService.triggerDataEvent(s);
|
||||||
}
|
}
|
||||||
|
|
||||||
private _decset(params: (number | number[])[]): boolean {
|
private _decset(params: (number | number[])[]): boolean {
|
||||||
|
|||||||
@@ -146,7 +146,7 @@ export class ImageRenderer extends Disposable implements IDisposable {
|
|||||||
public clearLines(start: number, end: number, layer?: ImageLayer): void {
|
public clearLines(start: number, end: number, layer?: ImageLayer): void {
|
||||||
const y = start * (this.dimensions?.css.cell.height || 0);
|
const y = start * (this.dimensions?.css.cell.height || 0);
|
||||||
const w = this.dimensions?.css.canvas.width || 0;
|
const w = this.dimensions?.css.canvas.width || 0;
|
||||||
const h = (end + 1 - start) * (this.dimensions?.css.cell.height || 0);
|
const h = (++end - start) * (this.dimensions?.css.cell.height || 0);
|
||||||
if (!layer || layer === 'top') {
|
if (!layer || layer === 'top') {
|
||||||
this._layers.get('top')?.clearRect(0, y, w, h);
|
this._layers.get('top')?.clearRect(0, y, w, h);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,11 +5,7 @@
|
|||||||
|
|
||||||
import { IDisposable } from '@xterm/xterm';
|
import { IDisposable } from '@xterm/xterm';
|
||||||
import { ImageRenderer } from './ImageRenderer';
|
import { ImageRenderer } from './ImageRenderer';
|
||||||
import {
|
import { ITerminalExt, IExtendedAttrsImage, IImageAddonOptions, IImageSpec, IBufferLineExt, BgFlags, Cell, Content, ICellSize, ExtFlags, Attributes, UnderlineStyle, ImageLayer } from './Types';
|
||||||
ITerminalExt, IExtendedAttrsImage, IImageAddonOptions, IImageSpec,
|
|
||||||
IBufferLineExt, BgFlags, Cell, Content, ICellSize, ExtFlags, Attributes,
|
|
||||||
UnderlineStyle, IAddImageOpts
|
|
||||||
} from './Types';
|
|
||||||
|
|
||||||
|
|
||||||
// fallback default cell size
|
// fallback default cell size
|
||||||
@@ -239,15 +235,13 @@ export class ImageStorage implements IDisposable {
|
|||||||
/**
|
/**
|
||||||
* Method to add an image to the storage.
|
* Method to add an image to the storage.
|
||||||
* @param img - The image to add (canvas or bitmap).
|
* @param img - The image to add (canvas or bitmap).
|
||||||
* @param opts - Options for addImage:
|
* @param scrolling - When true, cursor advances with the image (lineFeed per row).
|
||||||
* - scrolling: When true, cursor advances with the image.
|
* When false, image is placed at (0,0) and cursor is restored (DECSET 80 / sixel origin mode).
|
||||||
* When false, image is placed at ORIGIN and cursor does not move.
|
* @param layer - Which canvas layer to render on ('top' or 'bottom').
|
||||||
* - layer: Which canvas layer to render on ('top' or 'bottom').
|
* @param zIndex - Z-index for image layering within the same layer.
|
||||||
* - zIndex: Z-index for image layering within the same layer.
|
|
||||||
* - cursorPos: 'vt340' for bottom-left, 'iip' for bottom.right.
|
|
||||||
* @returns The internal image ID assigned to the stored image.
|
* @returns The internal image ID assigned to the stored image.
|
||||||
*/
|
*/
|
||||||
public addImage(img: HTMLCanvasElement | ImageBitmap, opts: IAddImageOpts): number {
|
public addImage(img: HTMLCanvasElement | ImageBitmap, scrolling: boolean, layer: ImageLayer = 'top', zIndex: number = 0): number {
|
||||||
// never allow storage to exceed memory limit
|
// never allow storage to exceed memory limit
|
||||||
this._evictOldest(img.width * img.height);
|
this._evictOldest(img.width * img.height);
|
||||||
|
|
||||||
@@ -269,7 +263,7 @@ export class ImageStorage implements IDisposable {
|
|||||||
let offset = originX;
|
let offset = originX;
|
||||||
let tileCount = 0;
|
let tileCount = 0;
|
||||||
|
|
||||||
if (!opts.scrolling) {
|
if (!scrolling) {
|
||||||
buffer.x = 0;
|
buffer.x = 0;
|
||||||
buffer.y = 0;
|
buffer.y = 0;
|
||||||
offset = 0;
|
offset = 0;
|
||||||
@@ -283,7 +277,7 @@ export class ImageStorage implements IDisposable {
|
|||||||
this._writeToCell(line as IBufferLineExt, offset + col, imageId, row * cols + col);
|
this._writeToCell(line as IBufferLineExt, offset + col, imageId, row * cols + col);
|
||||||
tileCount++;
|
tileCount++;
|
||||||
}
|
}
|
||||||
if (opts.scrolling) {
|
if (scrolling) {
|
||||||
if (row < rows - 1) this._terminal._core._inputHandler.lineFeed();
|
if (row < rows - 1) this._terminal._core._inputHandler.lineFeed();
|
||||||
} else {
|
} else {
|
||||||
if (++buffer.y >= termRows) break;
|
if (++buffer.y >= termRows) break;
|
||||||
@@ -293,12 +287,8 @@ export class ImageStorage implements IDisposable {
|
|||||||
this._terminal._core._inputHandler._dirtyRowTracker.markDirty(buffer.y);
|
this._terminal._core._inputHandler._dirtyRowTracker.markDirty(buffer.y);
|
||||||
|
|
||||||
// cursor positioning modes
|
// cursor positioning modes
|
||||||
if (opts.scrolling) {
|
if (scrolling) {
|
||||||
if (opts.cursorPos === 'iip') {
|
buffer.x = offset;
|
||||||
buffer.x = Math.min(offset + cols, termCols);
|
|
||||||
} else {
|
|
||||||
buffer.x = offset;
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
buffer.x = originX;
|
buffer.x = originX;
|
||||||
buffer.y = originY;
|
buffer.y = originY;
|
||||||
@@ -341,8 +331,8 @@ export class ImageStorage implements IDisposable {
|
|||||||
marker: endMarker || undefined,
|
marker: endMarker || undefined,
|
||||||
tileCount,
|
tileCount,
|
||||||
bufferType: this._terminal.buffer.active.type,
|
bufferType: this._terminal.buffer.active.type,
|
||||||
layer: opts.layer,
|
layer,
|
||||||
zIndex: opts.zIndex
|
zIndex
|
||||||
};
|
};
|
||||||
|
|
||||||
// finally add the image
|
// finally add the image
|
||||||
@@ -465,6 +455,7 @@ export class ImageStorage implements IDisposable {
|
|||||||
if (!nextE || nextE.imageId !== imageId || nextE.tileId !== startTile + count) {
|
if (!nextE || nextE.imageId !== imageId || nextE.tileId !== startTile + count) {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
e = nextE;
|
||||||
count++;
|
count++;
|
||||||
}
|
}
|
||||||
col--;
|
col--;
|
||||||
|
|||||||
@@ -91,7 +91,7 @@ export class SixelHandler implements IDcsHandler, IResetHandler {
|
|||||||
const height = this._dec.height;
|
const height = this._dec.height;
|
||||||
|
|
||||||
// partial fix for https://github.com/jerch/xterm-addon-image/issues/37
|
// partial fix for https://github.com/jerch/xterm-addon-image/issues/37
|
||||||
if (!width || !height) {
|
if (!width || ! height) {
|
||||||
if (height) {
|
if (height) {
|
||||||
this._storage.advanceCursor(height);
|
this._storage.advanceCursor(height);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,7 +4,7 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import { ImageStorage, CELL_SIZE_DEFAULT } from './ImageStorage';
|
import { ImageStorage, CELL_SIZE_DEFAULT } from './ImageStorage';
|
||||||
import { IImageAddonOptions, ITerminalExt, IAddImageOpts } from './Types';
|
import { IImageAddonOptions, ITerminalExt } from './Types';
|
||||||
import { ImageRenderer } from './ImageRenderer';
|
import { ImageRenderer } from './ImageRenderer';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -15,7 +15,6 @@ import { ImageRenderer } from './ImageRenderer';
|
|||||||
* - advanceCursor for empty sixels carrying only height
|
* - advanceCursor for empty sixels carrying only height
|
||||||
*/
|
*/
|
||||||
export class SixelImageStorage {
|
export class SixelImageStorage {
|
||||||
private _addImageOpts: IAddImageOpts = { scrolling: true, layer: 'top', zIndex: 0, cursorPos: 'vt340' };
|
|
||||||
constructor(
|
constructor(
|
||||||
private readonly _storage: ImageStorage,
|
private readonly _storage: ImageStorage,
|
||||||
private readonly _opts: IImageAddonOptions,
|
private readonly _opts: IImageAddonOptions,
|
||||||
@@ -28,8 +27,7 @@ export class SixelImageStorage {
|
|||||||
* Cursor behavior depends on the sixelScrolling option (DECSET 80).
|
* Cursor behavior depends on the sixelScrolling option (DECSET 80).
|
||||||
*/
|
*/
|
||||||
public addImage(img: HTMLCanvasElement | ImageBitmap): void {
|
public addImage(img: HTMLCanvasElement | ImageBitmap): void {
|
||||||
this._addImageOpts.scrolling = this._opts.sixelScrolling;
|
this._storage.addImage(img, this._opts.sixelScrolling);
|
||||||
this._storage.addImage(img, this._addImageOpts);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -10,9 +10,9 @@ import { Attributes, BgFlags, Content, ExtFlags, UnderlineStyle } from 'common/b
|
|||||||
import type { AttributeData } from 'common/buffer/AttributeData';
|
import type { AttributeData } from 'common/buffer/AttributeData';
|
||||||
import type { IParams, IDcsHandler, IOscHandler, IApcHandler, IEscapeSequenceParser } from 'common/parser/Types';
|
import type { IParams, IDcsHandler, IOscHandler, IApcHandler, IEscapeSequenceParser } from 'common/parser/Types';
|
||||||
import type { IBufferLine, IExtendedAttrs, IInputHandler } from 'common/Types';
|
import type { IBufferLine, IExtendedAttrs, IInputHandler } from 'common/Types';
|
||||||
import type { ITerminal, ReadonlyColorSet } from 'target-browser/Types';
|
import type { ITerminal, ReadonlyColorSet } from 'browser/Types';
|
||||||
import type { IRenderDimensions } from 'target-browser/renderer/shared/Types';
|
import type { IRenderDimensions } from 'browser/renderer/shared/Types';
|
||||||
import type { ICoreBrowserService, IRenderService, IThemeService } from 'target-browser/services/Services';
|
import type { ICoreBrowserService, IRenderService, IThemeService } from 'browser/services/Services';
|
||||||
|
|
||||||
export const enum Cell {
|
export const enum Cell {
|
||||||
CONTENT = 0, // codepoint and wcwidth information (enum Content)
|
CONTENT = 0, // codepoint and wcwidth information (enum Content)
|
||||||
@@ -100,14 +100,6 @@ export interface ICellSize {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export type ImageLayer = 'top' | 'bottom';
|
export type ImageLayer = 'top' | 'bottom';
|
||||||
export type CursorPos = 'vt340' | 'iip';
|
|
||||||
|
|
||||||
export interface IAddImageOpts {
|
|
||||||
scrolling: boolean;
|
|
||||||
layer: ImageLayer;
|
|
||||||
zIndex: number;
|
|
||||||
cursorPos: CursorPos;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface IImageSpec {
|
export interface IImageSpec {
|
||||||
orig: HTMLCanvasElement | ImageBitmap | undefined;
|
orig: HTMLCanvasElement | ImageBitmap | undefined;
|
||||||
|
|||||||
@@ -533,8 +533,8 @@ export class KittyGraphicsHandler implements IApcHandler, IResetHandler, IDispos
|
|||||||
|
|
||||||
private _sendResponse(id: number, message: string, quiet: number, placementId?: number): void {
|
private _sendResponse(id: number, message: string, quiet: number, placementId?: number): void {
|
||||||
const isOk = message === 'OK';
|
const isOk = message === 'OK';
|
||||||
if (isOk && quiet >= 1) return;
|
if (isOk && quiet === 1) return;
|
||||||
if (!isOk && quiet >= 2) return;
|
if (!isOk && quiet === 2) return;
|
||||||
|
|
||||||
const pPart = placementId ? `,p=${placementId}` : '';
|
const pPart = placementId ? `,p=${placementId}` : '';
|
||||||
const response = `\x1b_Gi=${id}${pPart};${message}\x1b\\`;
|
const response = `\x1b_Gi=${id}${pPart};${message}\x1b\\`;
|
||||||
|
|||||||
@@ -5,7 +5,7 @@
|
|||||||
|
|
||||||
import { IDisposable } from '@xterm/xterm';
|
import { IDisposable } from '@xterm/xterm';
|
||||||
import { ImageStorage } from '../ImageStorage';
|
import { ImageStorage } from '../ImageStorage';
|
||||||
import { ImageLayer, IAddImageOpts } from '../Types';
|
import { ImageLayer } from '../Types';
|
||||||
import { IKittyImageData } from './KittyGraphicsTypes';
|
import { IKittyImageData } from './KittyGraphicsTypes';
|
||||||
|
|
||||||
// Kitty-specific image storage controller.
|
// Kitty-specific image storage controller.
|
||||||
@@ -42,7 +42,6 @@ export class KittyImageStorage implements IDisposable {
|
|||||||
this._images.delete(kittyId);
|
this._images.delete(kittyId);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
private _addImageOpts: IAddImageOpts = { scrolling: true, layer: 'top', zIndex: 0, cursorPos: 'iip' };
|
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
private readonly _storage: ImageStorage
|
private readonly _storage: ImageStorage
|
||||||
@@ -99,10 +98,7 @@ export class KittyImageStorage implements IDisposable {
|
|||||||
if (oldStorageId !== undefined) {
|
if (oldStorageId !== undefined) {
|
||||||
this._storageIdToKittyId.delete(oldStorageId);
|
this._storageIdToKittyId.delete(oldStorageId);
|
||||||
}
|
}
|
||||||
this._addImageOpts.scrolling = scrolling;
|
const storageId = this._storage.addImage(image, scrolling, layer, zIndex);
|
||||||
this._addImageOpts.layer = layer;
|
|
||||||
this._addImageOpts.zIndex = zIndex;
|
|
||||||
const storageId = this._storage.addImage(image, this._addImageOpts);
|
|
||||||
this._kittyIdToStorageId.set(kittyId, storageId);
|
this._kittyIdToStorageId.set(kittyId, storageId);
|
||||||
this._storageIdToKittyId.set(storageId, kittyId);
|
this._storageIdToKittyId.set(storageId, kittyId);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,11 +12,8 @@
|
|||||||
"../../../node_modules/@types/mocha"
|
"../../../node_modules/@types/mocha"
|
||||||
],
|
],
|
||||||
"paths": {
|
"paths": {
|
||||||
"target-browser/*": [ "../../../src/target-browser/*" ],
|
"browser/*": [ "../../../src/browser/*" ],
|
||||||
"common/*": [
|
"common/*": [ "../../../src/common/*" ],
|
||||||
"../../../src/primitives/*",
|
|
||||||
"../../../src/runtime/*",
|
|
||||||
],
|
|
||||||
"@xterm/addon-image": [ "../typings/addon-image.d.ts" ],
|
"@xterm/addon-image": [ "../typings/addon-image.d.ts" ],
|
||||||
"*": [ "./*" ]
|
"*": [ "./*" ]
|
||||||
}
|
}
|
||||||
@@ -26,7 +23,7 @@
|
|||||||
"../../../typings/xterm.d.ts"
|
"../../../typings/xterm.d.ts"
|
||||||
],
|
],
|
||||||
"references": [
|
"references": [
|
||||||
{ "path": "../../../src/target-browser" },
|
{ "path": "../../../src/browser" },
|
||||||
{ "path": "../../../src" }
|
{ "path": "../../../src/common" }
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -290,67 +290,6 @@ test.describe('ImageAddon', () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
test.describe('IIP support', () => {
|
|
||||||
test('size params is optional/informative', async () => {
|
|
||||||
// too small size param
|
|
||||||
await ctx.proxy.write(`\x1b]1337;File=inline=1;size=5:${PALETTE_PNG_BASE64}\x07`);
|
|
||||||
deepStrictEqual(await getOrigSize(1), [640, 80]);
|
|
||||||
// no size aparm at all
|
|
||||||
await ctx.proxy.write(`\x1b[10H\x1b]1337;File=inline=1:${PALETTE_QOI_BASE64}\x07`);
|
|
||||||
deepStrictEqual(await getOrigSize(2), [640, 80]);
|
|
||||||
});
|
|
||||||
test.skip('FilePart support - bytewise', async () => {
|
|
||||||
// opener
|
|
||||||
await ctx.proxy.write(`\x1b]1337;MultipartFile=inline=1\x07`);
|
|
||||||
// byte-wise
|
|
||||||
for (let i = 0; i < PALETTE_PNG_BASE64.length; ++i) {
|
|
||||||
await ctx.proxy.write(`\x1b]1337;FilePart=${PALETTE_PNG_BASE64[i]}\x07`);
|
|
||||||
}
|
|
||||||
// finalizer
|
|
||||||
await ctx.proxy.write(`\x1b]1337;FileEnd\x07`);
|
|
||||||
deepStrictEqual(await getOrigSize(1), [640, 80]);
|
|
||||||
});
|
|
||||||
test('FilePart support - chunks', async () => {
|
|
||||||
// opener
|
|
||||||
await ctx.proxy.write(`\x1b]1337;MultipartFile=inline=1\x07`);
|
|
||||||
// chunks
|
|
||||||
for (let i = 0; i < PALETTE_PNG_BASE64.length; i += 256) {
|
|
||||||
await ctx.proxy.write(`\x1b]1337;FilePart=${PALETTE_PNG_BASE64.slice(i, i + 256)}\x07`);
|
|
||||||
}
|
|
||||||
// finalizer
|
|
||||||
await ctx.proxy.write(`\x1b]1337;FileEnd\x07`);
|
|
||||||
deepStrictEqual(await getOrigSize(1), [640, 80]);
|
|
||||||
});
|
|
||||||
test('FilePart support - edgecases', async () => {
|
|
||||||
// multiple opener do not harm
|
|
||||||
for (let i = 0; i < 10; ++i) {
|
|
||||||
await ctx.proxy.write(`\x1b]1337;MultipartFile=inline=1\x07`);
|
|
||||||
}
|
|
||||||
// multiple finalizer do not harm
|
|
||||||
for (let i = 0; i < 5; ++i) {
|
|
||||||
await ctx.proxy.write(`\x1b]1337;FileEnd\x07`);
|
|
||||||
}
|
|
||||||
// chunks w'o opener are ignored
|
|
||||||
for (let i = 0; i < PALETTE_PNG_BASE64.length; i += 256) {
|
|
||||||
await ctx.proxy.write(`\x1b]1337;FilePart=${PALETTE_PNG_BASE64.slice(i, i + 256)}\x07`);
|
|
||||||
}
|
|
||||||
await ctx.proxy.write(`\x1b]1337;FileEnd\x07`);
|
|
||||||
pollFor(ctx.page, 'window.imageAddon._storage._images.size', 0);
|
|
||||||
});
|
|
||||||
test('FilePart support - other IIP sequence cancels chunked image', async () => {
|
|
||||||
// opener
|
|
||||||
await ctx.proxy.write(`\x1b]1337;MultipartFile=inline=1\x07`);
|
|
||||||
// chunks
|
|
||||||
for (let i = 0; i < PALETTE_PNG_BASE64.length; i += 256) {
|
|
||||||
await ctx.proxy.write(`\x1b]1337;FilePart=${PALETTE_PNG_BASE64.slice(i, i + 256)}\x07`);
|
|
||||||
}
|
|
||||||
// finalizer skipped
|
|
||||||
// write spinfox.png
|
|
||||||
await ctx.proxy.write(TESTDATA_IIP[1][0]);
|
|
||||||
deepStrictEqual(await getOrigSize(1), TESTDATA_IIP[1][1]);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
test.describe('IIP support - testimages', () => {
|
test.describe('IIP support - testimages', () => {
|
||||||
test('palette.png', async () => {
|
test('palette.png', async () => {
|
||||||
await ctx.proxy.write(TESTDATA_IIP[0][0]);
|
await ctx.proxy.write(TESTDATA_IIP[0][0]);
|
||||||
|
|||||||
@@ -539,6 +539,7 @@ test.describe('Kitty Graphics Protocol', () => {
|
|||||||
|
|
||||||
test.describe('Query support (a=q)', () => {
|
test.describe('Query support (a=q)', () => {
|
||||||
test('responds with OK for capability query without payload', async () => {
|
test('responds with OK for capability query without payload', async () => {
|
||||||
|
let response = '';
|
||||||
await ctx.page.evaluate(() => {
|
await ctx.page.evaluate(() => {
|
||||||
(window as any).kittyResponse = '';
|
(window as any).kittyResponse = '';
|
||||||
(window as any).term.onData((data: string) => { (window as any).kittyResponse = data; });
|
(window as any).term.onData((data: string) => { (window as any).kittyResponse = data; });
|
||||||
@@ -547,11 +548,12 @@ test.describe('Kitty Graphics Protocol', () => {
|
|||||||
await ctx.proxy.write('\x1b_Gi=31,a=q;\x1b\\');
|
await ctx.proxy.write('\x1b_Gi=31,a=q;\x1b\\');
|
||||||
await timeout(100);
|
await timeout(100);
|
||||||
|
|
||||||
const response = await ctx.page.evaluate('window.kittyResponse');
|
response = await ctx.page.evaluate('window.kittyResponse');
|
||||||
strictEqual(response, '\x1b_Gi=31;OK\x1b\\');
|
strictEqual(response, '\x1b_Gi=31;OK\x1b\\');
|
||||||
});
|
});
|
||||||
|
|
||||||
test('responds with OK for valid PNG query', async () => {
|
test('responds with OK for valid PNG query', async () => {
|
||||||
|
let response = '';
|
||||||
await ctx.page.evaluate(() => {
|
await ctx.page.evaluate(() => {
|
||||||
(window as any).kittyResponse = '';
|
(window as any).kittyResponse = '';
|
||||||
(window as any).term.onData((data: string) => { (window as any).kittyResponse = data; });
|
(window as any).term.onData((data: string) => { (window as any).kittyResponse = data; });
|
||||||
@@ -560,7 +562,7 @@ test.describe('Kitty Graphics Protocol', () => {
|
|||||||
await ctx.proxy.write(`\x1b_Gi=42,a=q,f=100;${KITTY_BLACK_1X1_BASE64}\x1b\\`);
|
await ctx.proxy.write(`\x1b_Gi=42,a=q,f=100;${KITTY_BLACK_1X1_BASE64}\x1b\\`);
|
||||||
await timeout(100);
|
await timeout(100);
|
||||||
|
|
||||||
const response = await ctx.page.evaluate('window.kittyResponse');
|
response = await ctx.page.evaluate('window.kittyResponse');
|
||||||
strictEqual(response, '\x1b_Gi=42;OK\x1b\\');
|
strictEqual(response, '\x1b_Gi=42;OK\x1b\\');
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -576,6 +578,7 @@ test.describe('Kitty Graphics Protocol', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
test('responds with error for invalid base64', async () => {
|
test('responds with error for invalid base64', async () => {
|
||||||
|
let response = '';
|
||||||
await ctx.page.evaluate(() => {
|
await ctx.page.evaluate(() => {
|
||||||
(window as any).kittyResponse = '';
|
(window as any).kittyResponse = '';
|
||||||
(window as any).term.onData((data: string) => { (window as any).kittyResponse = data; });
|
(window as any).term.onData((data: string) => { (window as any).kittyResponse = data; });
|
||||||
@@ -584,11 +587,12 @@ test.describe('Kitty Graphics Protocol', () => {
|
|||||||
await ctx.proxy.write('\x1b_Gi=60,a=q,f=100;!!!invalid!!!\x1b\\');
|
await ctx.proxy.write('\x1b_Gi=60,a=q,f=100;!!!invalid!!!\x1b\\');
|
||||||
await timeout(100);
|
await timeout(100);
|
||||||
|
|
||||||
const response: string = await ctx.page.evaluate('window.kittyResponse');
|
response = await ctx.page.evaluate('window.kittyResponse');
|
||||||
strictEqual(response.startsWith('\x1b_Gi=60;EINVAL:'), true);
|
strictEqual(response.startsWith('\x1b_Gi=60;EINVAL:'), true);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('responds with error for RGB data without dimensions', async () => {
|
test('responds with error for RGB data without dimensions', async () => {
|
||||||
|
let response = '';
|
||||||
await ctx.page.evaluate(() => {
|
await ctx.page.evaluate(() => {
|
||||||
(window as any).kittyResponse = '';
|
(window as any).kittyResponse = '';
|
||||||
(window as any).term.onData((data: string) => { (window as any).kittyResponse = data; });
|
(window as any).term.onData((data: string) => { (window as any).kittyResponse = data; });
|
||||||
@@ -597,7 +601,7 @@ test.describe('Kitty Graphics Protocol', () => {
|
|||||||
await ctx.proxy.write('\x1b_Gi=70,a=q,f=24;AAAA\x1b\\');
|
await ctx.proxy.write('\x1b_Gi=70,a=q,f=24;AAAA\x1b\\');
|
||||||
await timeout(100);
|
await timeout(100);
|
||||||
|
|
||||||
const response = await ctx.page.evaluate('window.kittyResponse');
|
response = await ctx.page.evaluate('window.kittyResponse');
|
||||||
strictEqual(response, '\x1b_Gi=70;EINVAL:width and height required for raw pixel data\x1b\\');
|
strictEqual(response, '\x1b_Gi=70;EINVAL:width and height required for raw pixel data\x1b\\');
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -613,18 +617,6 @@ test.describe('Kitty Graphics Protocol', () => {
|
|||||||
strictEqual(await ctx.page.evaluate('window.kittyGotResponse'), false);
|
strictEqual(await ctx.page.evaluate('window.kittyGotResponse'), false);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('suppresses OK response when q=2', async () => {
|
|
||||||
await ctx.page.evaluate(() => {
|
|
||||||
(window as any).kittyGotResponse = false;
|
|
||||||
(window as any).term.onData(() => { (window as any).kittyGotResponse = true; });
|
|
||||||
});
|
|
||||||
|
|
||||||
await ctx.proxy.write(`\x1b_Gi=81,a=q,q=2,f=100;${KITTY_BLACK_1X1_BASE64}\x1b\\`);
|
|
||||||
await timeout(100);
|
|
||||||
|
|
||||||
strictEqual(await ctx.page.evaluate('window.kittyGotResponse'), false);
|
|
||||||
});
|
|
||||||
|
|
||||||
test('suppresses error response when q=2', async () => {
|
test('suppresses error response when q=2', async () => {
|
||||||
await ctx.page.evaluate(() => {
|
await ctx.page.evaluate(() => {
|
||||||
(window as any).kittyGotResponse = false;
|
(window as any).kittyGotResponse = false;
|
||||||
@@ -638,6 +630,7 @@ test.describe('Kitty Graphics Protocol', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
test('responds with EINVAL when both i and I keys are specified', async () => {
|
test('responds with EINVAL when both i and I keys are specified', async () => {
|
||||||
|
let response = '';
|
||||||
await ctx.page.evaluate(() => {
|
await ctx.page.evaluate(() => {
|
||||||
(window as any).kittyResponse = '';
|
(window as any).kittyResponse = '';
|
||||||
(window as any).term.onData((data: string) => { (window as any).kittyResponse = data; });
|
(window as any).term.onData((data: string) => { (window as any).kittyResponse = data; });
|
||||||
@@ -647,11 +640,12 @@ test.describe('Kitty Graphics Protocol', () => {
|
|||||||
await ctx.proxy.write(`\x1b_Gi=100,I=200,a=q,f=100;${KITTY_BLACK_1X1_BASE64}\x1b\\`);
|
await ctx.proxy.write(`\x1b_Gi=100,I=200,a=q,f=100;${KITTY_BLACK_1X1_BASE64}\x1b\\`);
|
||||||
await timeout(100);
|
await timeout(100);
|
||||||
|
|
||||||
const response = await ctx.page.evaluate('window.kittyResponse');
|
response = await ctx.page.evaluate('window.kittyResponse');
|
||||||
strictEqual(response, '\x1b_Gi=100;EINVAL:cannot specify both i and I keys\x1b\\');
|
strictEqual(response, '\x1b_Gi=100;EINVAL:cannot specify both i and I keys\x1b\\');
|
||||||
});
|
});
|
||||||
|
|
||||||
test('responds with EINVAL for i+I conflict even without payload', async () => {
|
test('responds with EINVAL for i+I conflict even without payload', async () => {
|
||||||
|
let response = '';
|
||||||
await ctx.page.evaluate(() => {
|
await ctx.page.evaluate(() => {
|
||||||
(window as any).kittyResponse = '';
|
(window as any).kittyResponse = '';
|
||||||
(window as any).term.onData((data: string) => { (window as any).kittyResponse = data; });
|
(window as any).term.onData((data: string) => { (window as any).kittyResponse = data; });
|
||||||
@@ -661,7 +655,7 @@ test.describe('Kitty Graphics Protocol', () => {
|
|||||||
await ctx.proxy.write('\x1b_Gi=101,I=201,a=d\x1b\\');
|
await ctx.proxy.write('\x1b_Gi=101,I=201,a=d\x1b\\');
|
||||||
await timeout(100);
|
await timeout(100);
|
||||||
|
|
||||||
const response = await ctx.page.evaluate('window.kittyResponse');
|
response = await ctx.page.evaluate('window.kittyResponse');
|
||||||
strictEqual(response, '\x1b_Gi=101;EINVAL:cannot specify both i and I keys\x1b\\');
|
strictEqual(response, '\x1b_Gi=101;EINVAL:cannot specify both i and I keys\x1b\\');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@@ -803,30 +797,6 @@ test.describe('Kitty Graphics Protocol', () => {
|
|||||||
|
|
||||||
strictEqual(await ctx.page.evaluate('window.kittyGotResponse'), false);
|
strictEqual(await ctx.page.evaluate('window.kittyGotResponse'), false);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('a=t OK suppressed by q=2', async () => {
|
|
||||||
await ctx.page.evaluate(() => {
|
|
||||||
(window as any).kittyGotResponse = false;
|
|
||||||
(window as any).term.onData(() => { (window as any).kittyGotResponse = true; });
|
|
||||||
});
|
|
||||||
|
|
||||||
await ctx.proxy.write(`\x1b_Gi=181,a=t,q=2,f=100;${KITTY_BLACK_1X1_BASE64}\x1b\\`);
|
|
||||||
await timeout(100);
|
|
||||||
|
|
||||||
strictEqual(await ctx.page.evaluate('window.kittyGotResponse'), false);
|
|
||||||
});
|
|
||||||
|
|
||||||
test('a=T OK suppressed by q=2', async () => {
|
|
||||||
await ctx.page.evaluate(() => {
|
|
||||||
(window as any).kittyGotResponse = false;
|
|
||||||
(window as any).term.onData(() => { (window as any).kittyGotResponse = true; });
|
|
||||||
});
|
|
||||||
|
|
||||||
await ctx.proxy.write(`\x1b_Gi=191,a=T,q=2,f=100;${KITTY_BLACK_1X1_BASE64}\x1b\\`);
|
|
||||||
await timeout(100);
|
|
||||||
|
|
||||||
strictEqual(await ctx.page.evaluate('window.kittyGotResponse'), false);
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|
||||||
test.describe('Transmission medium rejection', () => {
|
test.describe('Transmission medium rejection', () => {
|
||||||
@@ -1176,22 +1146,6 @@ test.describe('Kitty Graphics Protocol', () => {
|
|||||||
strictEqual(await getImageStorageLength(), 1);
|
strictEqual(await getImageStorageLength(), 1);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('OK response suppressed by q=2', async () => {
|
|
||||||
await ctx.proxy.write(`\x1b_Ga=t,f=100,i=221;${KITTY_BLACK_1X1_BASE64}\x1b\\`);
|
|
||||||
await timeout(100);
|
|
||||||
|
|
||||||
await ctx.page.evaluate(() => {
|
|
||||||
(window as any).kittyGotResponse = false;
|
|
||||||
(window as any).term.onData(() => { (window as any).kittyGotResponse = true; });
|
|
||||||
});
|
|
||||||
|
|
||||||
await ctx.proxy.write(`\x1b_Ga=p,i=221,q=2\x1b\\`);
|
|
||||||
await timeout(100);
|
|
||||||
|
|
||||||
strictEqual(await ctx.page.evaluate('window.kittyGotResponse'), false);
|
|
||||||
strictEqual(await getImageStorageLength(), 1);
|
|
||||||
});
|
|
||||||
|
|
||||||
test('ENOENT error suppressed by q=2', async () => {
|
test('ENOENT error suppressed by q=2', async () => {
|
||||||
await ctx.page.evaluate(() => {
|
await ctx.page.evaluate(() => {
|
||||||
(window as any).kittyGotResponse = false;
|
(window as any).kittyGotResponse = false;
|
||||||
|
|||||||
@@ -11,11 +11,10 @@
|
|||||||
"removeComments": true,
|
"removeComments": true,
|
||||||
"paths": {
|
"paths": {
|
||||||
"common/*": [
|
"common/*": [
|
||||||
"../../../src/primitives/*",
|
"../../../src/common/*"
|
||||||
"../../../src/runtime/*",
|
|
||||||
],
|
],
|
||||||
"target-browser/*": [
|
"browser/*": [
|
||||||
"../../../src/target-browser/*"
|
"../../../src/browser/*"
|
||||||
],
|
],
|
||||||
"*": [
|
"*": [
|
||||||
"./*"
|
"./*"
|
||||||
@@ -32,10 +31,10 @@
|
|||||||
],
|
],
|
||||||
"references": [
|
"references": [
|
||||||
{
|
{
|
||||||
"path": "../../../src"
|
"path": "../../../src/common"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"path": "../../../src/target-browser"
|
"path": "../../../src/browser"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"path": "../../../test/playwright"
|
"path": "../../../test/playwright"
|
||||||
|
|||||||
@@ -25,11 +25,8 @@ const addon = {
|
|||||||
modules: ['./node_modules'],
|
modules: ['./node_modules'],
|
||||||
extensions: [ '.js' ],
|
extensions: [ '.js' ],
|
||||||
alias: {
|
alias: {
|
||||||
common: [
|
common: path.resolve('../../out/common'),
|
||||||
path.resolve('../../out/runtime'),
|
browser: path.resolve('../../out/browser'),
|
||||||
path.resolve('../../out/primitives'),
|
|
||||||
],
|
|
||||||
browser: path.resolve('../../out/target-browser'),
|
|
||||||
vs: path.resolve('../../out/vs')
|
vs: path.resolve('../../out/vs')
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -20,7 +20,7 @@
|
|||||||
"package": "webpack",
|
"package": "webpack",
|
||||||
"pretest": "npm run build",
|
"pretest": "npm run build",
|
||||||
"test": "nyc mocha out/**/*.test.js",
|
"test": "nyc mocha out/**/*.test.js",
|
||||||
"prepublishOnly": "npm run package"
|
"prepublish": "npm run package"
|
||||||
},
|
},
|
||||||
"keywords": [
|
"keywords": [
|
||||||
"font",
|
"font",
|
||||||
@@ -31,11 +31,15 @@
|
|||||||
],
|
],
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"lru-cache": "^11.3.6",
|
"lru-cache": "^6.0.0",
|
||||||
"opentype.js": "^2.0.0"
|
"opentype.js": "^0.8.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/opentype.js": "^1.3.9",
|
"@types/lru-cache": "^5.1.0",
|
||||||
"font-finder": "^1.1.0"
|
"@types/opentype.js": "^0.7.0",
|
||||||
|
"axios": "^1.15.0",
|
||||||
|
"font-finder": "^1.1.0",
|
||||||
|
"mkdirp": "0.5.5",
|
||||||
|
"yauzl": "^3.2.1"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import * as opentype from 'opentype.js';
|
import * as opentype from 'opentype.js';
|
||||||
import { LRUCache } from 'lru-cache';
|
import LRUCache = require('lru-cache');
|
||||||
|
|
||||||
import { IFont, ILigatureData, IFlattenedLookupTree, ILookupTree, IOptions } from './types';
|
import { IFont, ILigatureData, IFlattenedLookupTree, ILookupTree, IOptions } from './types';
|
||||||
import mergeTrees from './merge';
|
import mergeTrees from './merge';
|
||||||
@@ -23,8 +23,8 @@ class FontImpl implements IFont {
|
|||||||
|
|
||||||
if (options.cacheSize > 0) {
|
if (options.cacheSize > 0) {
|
||||||
this._cache = new LRUCache({
|
this._cache = new LRUCache({
|
||||||
maxSize: options.cacheSize,
|
max: options.cacheSize,
|
||||||
sizeCalculation: ((val: ILigatureData | [number, number][], key: string) => key.length) as any
|
length: ((val: ILigatureData | [number, number][], key: string) => key.length) as any
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -44,6 +44,7 @@ export default function mergeRange(ranges: [number, number][], newRangeStart: nu
|
|||||||
// current range
|
// current range
|
||||||
ranges[i - 1][1] = Math.max(newRangeEnd, range[1]);
|
ranges[i - 1][1] = Math.max(newRangeEnd, range[1]);
|
||||||
ranges.splice(i, 1);
|
ranges.splice(i, 1);
|
||||||
|
inRange = false;
|
||||||
return ranges;
|
return ranges;
|
||||||
}
|
}
|
||||||
// Case 7: New range extends from previous range past the
|
// Case 7: New range extends from previous range past the
|
||||||
|
|||||||
@@ -16,11 +16,10 @@
|
|||||||
],
|
],
|
||||||
"paths": {
|
"paths": {
|
||||||
"common/*": [
|
"common/*": [
|
||||||
"../../../src/primitives/*",
|
"../../../src/common/*"
|
||||||
"../../../src/runtime/*",
|
|
||||||
],
|
],
|
||||||
"target-browser/*": [
|
"browser/*": [
|
||||||
"../../../src/target-browser/*"
|
"../../../src/browser/*"
|
||||||
],
|
],
|
||||||
"@xterm/addon-progress": [
|
"@xterm/addon-progress": [
|
||||||
"../typings/addon-progress.d.ts"
|
"../typings/addon-progress.d.ts"
|
||||||
@@ -33,10 +32,10 @@
|
|||||||
],
|
],
|
||||||
"references": [
|
"references": [
|
||||||
{
|
{
|
||||||
"path": "../../../src/target-browser"
|
"path": "../../../src/browser"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"path": "../../../src"
|
"path": "../../../src/common"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,18 +9,15 @@ import { ITestContext, createTestContext, openTerminal } from '../../../test/pla
|
|||||||
|
|
||||||
|
|
||||||
let ctx: ITestContext;
|
let ctx: ITestContext;
|
||||||
|
test.beforeAll(async ({ browser }) => {
|
||||||
|
ctx = await createTestContext(browser);
|
||||||
|
ctx.page.setViewportSize({ width: 1024, height: 768 });
|
||||||
|
await openTerminal(ctx);
|
||||||
|
});
|
||||||
|
test.afterAll(async () => await ctx.page.close());
|
||||||
|
|
||||||
|
|
||||||
test.describe('ProgressAddon', () => {
|
test.describe('ProgressAddon', () => {
|
||||||
test.describe.configure({ mode: 'serial' });
|
|
||||||
|
|
||||||
test.beforeAll(async ({ browser }) => {
|
|
||||||
ctx = await createTestContext(browser);
|
|
||||||
await openTerminal(ctx);
|
|
||||||
});
|
|
||||||
test.afterAll(async () => {
|
|
||||||
await ctx?.page?.close();
|
|
||||||
});
|
|
||||||
|
|
||||||
test.beforeEach(async function(): Promise<any> {
|
test.beforeEach(async function(): Promise<any> {
|
||||||
await ctx.page.evaluate(`
|
await ctx.page.evaluate(`
|
||||||
window.progressStack = [];
|
window.progressStack = [];
|
||||||
|
|||||||
@@ -11,11 +11,10 @@
|
|||||||
"removeComments": true,
|
"removeComments": true,
|
||||||
"paths": {
|
"paths": {
|
||||||
"common/*": [
|
"common/*": [
|
||||||
"../../../src/primitives/*",
|
"../../../src/common/*"
|
||||||
"../../../src/runtime/*",
|
|
||||||
],
|
],
|
||||||
"target-browser/*": [
|
"browser/*": [
|
||||||
"../../../src/target-browser/*"
|
"../../../src/browser/*"
|
||||||
],
|
],
|
||||||
"*": [
|
"*": [
|
||||||
"./*"
|
"./*"
|
||||||
@@ -32,10 +31,10 @@
|
|||||||
],
|
],
|
||||||
"references": [
|
"references": [
|
||||||
{
|
{
|
||||||
"path": "../../../src"
|
"path": "../../../src/common"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"path": "../../../src/target-browser"
|
"path": "../../../src/browser"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"path": "../../../test/playwright"
|
"path": "../../../test/playwright"
|
||||||
|
|||||||
@@ -25,11 +25,8 @@ module.exports = {
|
|||||||
modules: ['./node_modules'],
|
modules: ['./node_modules'],
|
||||||
extensions: [ '.js' ],
|
extensions: [ '.js' ],
|
||||||
alias: {
|
alias: {
|
||||||
common: [
|
common: path.resolve('../../out/common'),
|
||||||
path.resolve('../../out/runtime'),
|
browser: path.resolve('../../out/browser'),
|
||||||
path.resolve('../../out/primitives'),
|
|
||||||
],
|
|
||||||
browser: path.resolve('../../out/target-browser'),
|
|
||||||
vs: path.resolve('../../out/vs')
|
vs: path.resolve('../../out/vs')
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -5,7 +5,7 @@
|
|||||||
import { assert } from 'chai';
|
import { assert } from 'chai';
|
||||||
import { SearchEngine } from './SearchEngine';
|
import { SearchEngine } from './SearchEngine';
|
||||||
import { SearchLineCache } from './SearchLineCache';
|
import { SearchLineCache } from './SearchLineCache';
|
||||||
import { Terminal } from 'target-browser/public/Terminal';
|
import { Terminal } from 'browser/public/Terminal';
|
||||||
import type { ISearchOptions } from '@xterm/addon-search';
|
import type { ISearchOptions } from '@xterm/addon-search';
|
||||||
import { DisposableStore } from 'common/Lifecycle';
|
import { DisposableStore } from 'common/Lifecycle';
|
||||||
|
|
||||||
|
|||||||
@@ -174,7 +174,7 @@ export class SearchEngine {
|
|||||||
this._terminal.clearSelection();
|
this._terminal.clearSelection();
|
||||||
|
|
||||||
let startRow = this._terminal.buffer.active.baseY + this._terminal.rows - 1;
|
let startRow = this._terminal.buffer.active.baseY + this._terminal.rows - 1;
|
||||||
const startCol = this._terminal.cols;
|
let startCol = this._terminal.cols;
|
||||||
const isReverseSearch = true;
|
const isReverseSearch = true;
|
||||||
|
|
||||||
this._lineCache.initLinesCache();
|
this._lineCache.initLinesCache();
|
||||||
@@ -186,14 +186,14 @@ export class SearchEngine {
|
|||||||
let result: ISearchResult | undefined;
|
let result: ISearchResult | undefined;
|
||||||
if (prevSelectedPos) {
|
if (prevSelectedPos) {
|
||||||
searchPosition.startRow = startRow = prevSelectedPos.start.y;
|
searchPosition.startRow = startRow = prevSelectedPos.start.y;
|
||||||
searchPosition.startCol = prevSelectedPos.start.x;
|
searchPosition.startCol = startCol = prevSelectedPos.start.x;
|
||||||
if (cachedSearchTerm !== term) {
|
if (cachedSearchTerm !== term) {
|
||||||
// Try to expand selection to right first.
|
// Try to expand selection to right first.
|
||||||
result = this._findInLine(term, searchPosition, searchOptions, false);
|
result = this._findInLine(term, searchPosition, searchOptions, false);
|
||||||
if (!result) {
|
if (!result) {
|
||||||
// If selection was not able to be expanded to the right, then try reverse search
|
// If selection was not able to be expanded to the right, then try reverse search
|
||||||
searchPosition.startRow = startRow = prevSelectedPos.end.y;
|
searchPosition.startRow = startRow = prevSelectedPos.end.y;
|
||||||
searchPosition.startCol = prevSelectedPos.end.x;
|
searchPosition.startCol = startCol = prevSelectedPos.end.x;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,7 +4,7 @@
|
|||||||
*/
|
*/
|
||||||
import { assert } from 'chai';
|
import { assert } from 'chai';
|
||||||
import { SearchLineCache, LineCacheEntry } from './SearchLineCache';
|
import { SearchLineCache, LineCacheEntry } from './SearchLineCache';
|
||||||
import { Terminal } from 'target-browser/public/Terminal';
|
import { Terminal } from 'browser/public/Terminal';
|
||||||
import { timeout } from 'common/Async';
|
import { timeout } from 'common/Async';
|
||||||
|
|
||||||
function writeP(terminal: Terminal, data: string): Promise<void> {
|
function writeP(terminal: Terminal, data: string): Promise<void> {
|
||||||
|
|||||||
@@ -16,11 +16,10 @@
|
|||||||
],
|
],
|
||||||
"paths": {
|
"paths": {
|
||||||
"common/*": [
|
"common/*": [
|
||||||
"../../../src/primitives/*",
|
"../../../src/common/*"
|
||||||
"../../../src/runtime/*",
|
|
||||||
],
|
],
|
||||||
"target-browser/*": [
|
"browser/*": [
|
||||||
"../../../src/target-browser/*"
|
"../../../src/browser/*"
|
||||||
],
|
],
|
||||||
"@xterm/addon-search" : [
|
"@xterm/addon-search" : [
|
||||||
"../typings/addon-search.d.ts"
|
"../typings/addon-search.d.ts"
|
||||||
@@ -33,10 +32,10 @@
|
|||||||
],
|
],
|
||||||
"references": [
|
"references": [
|
||||||
{
|
{
|
||||||
"path": "../../../src"
|
"path": "../../../src/common"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"path": "../../../src/target-browser"
|
"path": "../../../src/browser"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,11 +11,10 @@
|
|||||||
"removeComments": true,
|
"removeComments": true,
|
||||||
"paths": {
|
"paths": {
|
||||||
"common/*": [
|
"common/*": [
|
||||||
"../../../src/primitives/*",
|
"../../../src/common/*"
|
||||||
"../../../src/runtime/*",
|
|
||||||
],
|
],
|
||||||
"target-browser/*": [
|
"browser/*": [
|
||||||
"../../../src/target-browser/*"
|
"../../../src/browser/*"
|
||||||
],
|
],
|
||||||
"*": [
|
"*": [
|
||||||
"./*"
|
"./*"
|
||||||
@@ -32,10 +31,10 @@
|
|||||||
],
|
],
|
||||||
"references": [
|
"references": [
|
||||||
{
|
{
|
||||||
"path": "../../../src"
|
"path": "../../../src/common"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"path": "../../../src/target-browser"
|
"path": "../../../src/browser"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"path": "../../../test/playwright"
|
"path": "../../../test/playwright"
|
||||||
|
|||||||
@@ -25,10 +25,7 @@ module.exports = {
|
|||||||
modules: ['./node_modules'],
|
modules: ['./node_modules'],
|
||||||
extensions: [ '.js' ],
|
extensions: [ '.js' ],
|
||||||
alias: {
|
alias: {
|
||||||
common: [
|
common: path.resolve('../../out/common'),
|
||||||
path.resolve('../../out/runtime'),
|
|
||||||
path.resolve('../../out/primitives'),
|
|
||||||
],
|
|
||||||
vs: path.resolve('../../out/vs')
|
vs: path.resolve('../../out/vs')
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ import { perfContext, before, ThroughputRuntimeCase } from 'xterm-benchmark';
|
|||||||
|
|
||||||
import { spawn } from 'node-pty';
|
import { spawn } from 'node-pty';
|
||||||
import { Utf8ToUtf32, stringFromCodePoint } from 'common/input/TextDecoder';
|
import { Utf8ToUtf32, stringFromCodePoint } from 'common/input/TextDecoder';
|
||||||
import { Terminal } from 'target-browser/public/Terminal';
|
import { Terminal } from 'browser/public/Terminal';
|
||||||
import { SerializeAddon } from 'SerializeAddon';
|
import { SerializeAddon } from 'SerializeAddon';
|
||||||
|
|
||||||
class TestTerminal extends Terminal {
|
class TestTerminal extends Terminal {
|
||||||
|
|||||||
@@ -7,16 +7,13 @@
|
|||||||
"rootDir": "..",
|
"rootDir": "..",
|
||||||
"outDir": "../out-benchmark",
|
"outDir": "../out-benchmark",
|
||||||
"types": ["../../../node_modules/@types/node"],
|
"types": ["../../../node_modules/@types/node"],
|
||||||
"moduleResolution": "nodenext",
|
"moduleResolution": "node",
|
||||||
"strict": false,
|
"strict": false,
|
||||||
"target": "es2021",
|
"target": "es2021",
|
||||||
"module": "nodenext",
|
"module": "commonjs",
|
||||||
"paths": {
|
"paths": {
|
||||||
"common/*": [
|
"common/*": ["../../../src/common/*"],
|
||||||
"../../../src/primitives/*",
|
"browser/*": ["../../../src/browser/*"],
|
||||||
"../../../src/runtime/*",
|
|
||||||
],
|
|
||||||
"target-browser/*": ["../../../src/target-browser/*"],
|
|
||||||
"SerializeAddon": ["../src/SerializeAddon"],
|
"SerializeAddon": ["../src/SerializeAddon"],
|
||||||
"@xterm/addon-serialize": [
|
"@xterm/addon-serialize": [
|
||||||
"../typings/addon-serialize.d.ts"
|
"../typings/addon-serialize.d.ts"
|
||||||
@@ -26,7 +23,7 @@
|
|||||||
"include": ["../**/*", "../../../typings/xterm.d.ts"],
|
"include": ["../**/*", "../../../typings/xterm.d.ts"],
|
||||||
"exclude": ["../../../**/*test.ts", "../../**/*api.ts"],
|
"exclude": ["../../../**/*test.ts", "../../**/*api.ts"],
|
||||||
"references": [
|
"references": [
|
||||||
{ "path": "../../../src" },
|
{ "path": "../../../src/common" },
|
||||||
{ "path": "../../../src/target-browser" }
|
{ "path": "../../../src/browser" }
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,10 +6,10 @@
|
|||||||
import jsdom = require('jsdom');
|
import jsdom = require('jsdom');
|
||||||
import { assert } from 'chai';
|
import { assert } from 'chai';
|
||||||
import { SerializeAddon } from './SerializeAddon';
|
import { SerializeAddon } from './SerializeAddon';
|
||||||
import { Terminal } from 'target-browser/public/Terminal';
|
import { Terminal } from 'browser/public/Terminal';
|
||||||
import { SelectionModel } from 'target-browser/selection/SelectionModel';
|
import { SelectionModel } from 'browser/selection/SelectionModel';
|
||||||
import { IBufferService } from 'common/services/Services';
|
import { IBufferService } from 'common/services/Services';
|
||||||
import { ThemeService } from 'target-browser/services/ThemeService';
|
import { ThemeService } from 'browser/services/ThemeService';
|
||||||
|
|
||||||
function sgr(...seq: string[]): string {
|
function sgr(...seq: string[]): string {
|
||||||
return `\x1b[${seq.join(';')}m`;
|
return `\x1b[${seq.join(';')}m`;
|
||||||
|
|||||||
@@ -8,7 +8,7 @@
|
|||||||
import type { IBuffer, IBufferCell, IBufferRange, ITerminalAddon, Terminal } from '@xterm/xterm';
|
import type { IBuffer, IBufferCell, IBufferRange, ITerminalAddon, Terminal } from '@xterm/xterm';
|
||||||
import type { IHTMLSerializeOptions, SerializeAddon as ISerializeApi, ISerializeOptions, ISerializeRange } from '@xterm/addon-serialize';
|
import type { IHTMLSerializeOptions, SerializeAddon as ISerializeApi, ISerializeOptions, ISerializeRange } from '@xterm/addon-serialize';
|
||||||
import { IAttributeData, IColor } from 'common/Types';
|
import { IAttributeData, IColor } from 'common/Types';
|
||||||
import { DEFAULT_ANSI_COLORS } from 'target-browser/Types';
|
import { DEFAULT_ANSI_COLORS } from 'browser/Types';
|
||||||
import { UnderlineStyle } from 'common/buffer/Constants';
|
import { UnderlineStyle } from 'common/buffer/Constants';
|
||||||
|
|
||||||
function constrain(value: number, low: number, high: number): number {
|
function constrain(value: number, low: number, high: number): number {
|
||||||
|
|||||||
@@ -12,11 +12,10 @@
|
|||||||
"removeComments": true,
|
"removeComments": true,
|
||||||
"paths": {
|
"paths": {
|
||||||
"common/*": [
|
"common/*": [
|
||||||
"../../../src/primitives/*",
|
"../../../src/common/*"
|
||||||
"../../../src/runtime/*",
|
|
||||||
],
|
],
|
||||||
"target-browser/*": [
|
"browser/*": [
|
||||||
"../../../src/target-browser/*"
|
"../../../src/browser/*"
|
||||||
],
|
],
|
||||||
"@xterm/addon-serialize": [
|
"@xterm/addon-serialize": [
|
||||||
"../typings/addon-serialize.d.ts"
|
"../typings/addon-serialize.d.ts"
|
||||||
@@ -34,10 +33,10 @@
|
|||||||
],
|
],
|
||||||
"references": [
|
"references": [
|
||||||
{
|
{
|
||||||
"path": "../../../src"
|
"path": "../../../src/common"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"path": "../../../src/target-browser"
|
"path": "../../../src/browser"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,11 +11,10 @@
|
|||||||
"removeComments": true,
|
"removeComments": true,
|
||||||
"paths": {
|
"paths": {
|
||||||
"common/*": [
|
"common/*": [
|
||||||
"../../../src/primitives/*",
|
"../../../src/common/*"
|
||||||
"../../../src/runtime/*",
|
|
||||||
],
|
],
|
||||||
"target-browser/*": [
|
"browser/*": [
|
||||||
"../../../src/target-browser/*"
|
"../../../src/browser/*"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"strict": true,
|
"strict": true,
|
||||||
@@ -29,10 +28,10 @@
|
|||||||
],
|
],
|
||||||
"references": [
|
"references": [
|
||||||
{
|
{
|
||||||
"path": "../../../src"
|
"path": "../../../src/common"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"path": "../../../src/target-browser"
|
"path": "../../../src/browser"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"path": "../../../test/playwright"
|
"path": "../../../test/playwright"
|
||||||
|
|||||||
@@ -25,11 +25,8 @@ module.exports = {
|
|||||||
modules: ['./node_modules'],
|
modules: ['./node_modules'],
|
||||||
extensions: [ '.js' ],
|
extensions: [ '.js' ],
|
||||||
alias: {
|
alias: {
|
||||||
common: [
|
common: path.resolve('../../out/common'),
|
||||||
path.resolve('../../out/runtime'),
|
browser: path.resolve('../../out/browser')
|
||||||
path.resolve('../../out/primitives'),
|
|
||||||
],
|
|
||||||
browser: path.resolve('../../out/target-browser')
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
output: {
|
output: {
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ import { perfContext, before, ThroughputRuntimeCase } from 'xterm-benchmark';
|
|||||||
|
|
||||||
import { spawn } from 'node-pty';
|
import { spawn } from 'node-pty';
|
||||||
import { Utf8ToUtf32, stringFromCodePoint } from 'common/input/TextDecoder';
|
import { Utf8ToUtf32, stringFromCodePoint } from 'common/input/TextDecoder';
|
||||||
import { CoreBrowserTerminal } from 'target-browser/CoreBrowserTerminal';
|
import { CoreBrowserTerminal } from 'browser/CoreBrowserTerminal';
|
||||||
import { UnicodeGraphemeProvider } from 'UnicodeGraphemeProvider';
|
import { UnicodeGraphemeProvider } from 'UnicodeGraphemeProvider';
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -4,16 +4,13 @@
|
|||||||
"rootDir": "..",
|
"rootDir": "..",
|
||||||
"outDir": "../out-benchmark",
|
"outDir": "../out-benchmark",
|
||||||
"types": ["../../../node_modules/@types/node"],
|
"types": ["../../../node_modules/@types/node"],
|
||||||
"moduleResolution": "nodenext",
|
"moduleResolution": "node",
|
||||||
"strict": false,
|
"strict": false,
|
||||||
"target": "es2015",
|
"target": "es2015",
|
||||||
"module": "nodenext",
|
"module": "commonjs",
|
||||||
"paths": {
|
"paths": {
|
||||||
"common/*": [
|
"common/*": ["../../../src/common/*"],
|
||||||
"../../../src/primitives/*",
|
"browser/*": ["../../../src/browser/*"],
|
||||||
"../../../src/runtime/*",
|
|
||||||
],
|
|
||||||
"target-browser/*": ["../../../src/target-browser/*"],
|
|
||||||
"UnicodeGraphemeProvider": ["../src/UnicodeGraphemeProvider"],
|
"UnicodeGraphemeProvider": ["../src/UnicodeGraphemeProvider"],
|
||||||
"@xterm/addon-unicode-graphemes": [
|
"@xterm/addon-unicode-graphemes": [
|
||||||
"../typings/addon-unicode-graphemes.d.ts"
|
"../typings/addon-unicode-graphemes.d.ts"
|
||||||
@@ -23,7 +20,7 @@
|
|||||||
"include": ["../**/*", "../../../typings/xterm.d.ts"],
|
"include": ["../**/*", "../../../typings/xterm.d.ts"],
|
||||||
"exclude": ["../../../**/*test.ts", "../../**/*api.ts"],
|
"exclude": ["../../../**/*test.ts", "../../**/*api.ts"],
|
||||||
"references": [
|
"references": [
|
||||||
{ "path": "../../../src" },
|
{ "path": "../../../src/common" },
|
||||||
{ "path": "../../../src/target-browser" }
|
{ "path": "../../../src/browser" }
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,8 +4,8 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import { IUnicodeVersionProvider } from '@xterm/xterm';
|
import { IUnicodeVersionProvider } from '@xterm/xterm';
|
||||||
import { createPropertyValue, extractCharKind, extractWidth } from 'common/input/UnicodeProperties';
|
import { UnicodeCharProperties, UnicodeCharWidth } from 'common/services/Services';
|
||||||
import { UnicodeCharProperties, UnicodeCharWidth } from 'common/input/UnicodeTypes';
|
import { UnicodeService } from 'common/services/UnicodeService';
|
||||||
import * as UC from './third-party/UnicodeProperties';
|
import * as UC from './third-party/UnicodeProperties';
|
||||||
|
|
||||||
export class UnicodeGraphemeProvider implements IUnicodeVersionProvider {
|
export class UnicodeGraphemeProvider implements IUnicodeVersionProvider {
|
||||||
@@ -19,7 +19,7 @@ export class UnicodeGraphemeProvider implements IUnicodeVersionProvider {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private static readonly _plainNarrowProperties: UnicodeCharProperties
|
private static readonly _plainNarrowProperties: UnicodeCharProperties
|
||||||
= createPropertyValue(UC.GRAPHEME_BREAK_Other, 1, false);
|
= UnicodeService.createPropertyValue(UC.GRAPHEME_BREAK_Other, 1, false);
|
||||||
|
|
||||||
public charProperties(codepoint: number, preceding: UnicodeCharProperties): UnicodeCharProperties {
|
public charProperties(codepoint: number, preceding: UnicodeCharProperties): UnicodeCharProperties {
|
||||||
// Optimize the simple ASCII case, under the condition that
|
// Optimize the simple ASCII case, under the condition that
|
||||||
@@ -39,9 +39,9 @@ export class UnicodeGraphemeProvider implements IUnicodeVersionProvider {
|
|||||||
w = 1;
|
w = 1;
|
||||||
}
|
}
|
||||||
if (preceding !== 0) {
|
if (preceding !== 0) {
|
||||||
const oldWidth = extractWidth(preceding);
|
const oldWidth = UnicodeService.extractWidth(preceding);
|
||||||
if (this.handleGraphemes) {
|
if (this.handleGraphemes) {
|
||||||
charInfo = UC.shouldJoin(extractCharKind(preceding), charInfo);
|
charInfo = UC.shouldJoin(UnicodeService.extractCharKind(preceding), charInfo);
|
||||||
} else {
|
} else {
|
||||||
charInfo = w === 0 ? 1 : 0;
|
charInfo = w === 0 ? 1 : 0;
|
||||||
}
|
}
|
||||||
@@ -54,7 +54,7 @@ export class UnicodeGraphemeProvider implements IUnicodeVersionProvider {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return createPropertyValue(charInfo, w, shouldJoin);
|
return UnicodeService.createPropertyValue(charInfo, w, shouldJoin);
|
||||||
}
|
}
|
||||||
|
|
||||||
public wcwidth(codepoint: number): UnicodeCharWidth {
|
public wcwidth(codepoint: number): UnicodeCharWidth {
|
||||||
|
|||||||
@@ -13,8 +13,7 @@
|
|||||||
"strict": true,
|
"strict": true,
|
||||||
"paths": {
|
"paths": {
|
||||||
"common/*": [
|
"common/*": [
|
||||||
"../../../src/primitives/*",
|
"../../../src/common/*"
|
||||||
"../../../src/runtime/*",
|
|
||||||
],
|
],
|
||||||
"@xterm/addon-unicode-graphemes": [
|
"@xterm/addon-unicode-graphemes": [
|
||||||
"../typings/addon-unicode-graphemes.d.ts"
|
"../typings/addon-unicode-graphemes.d.ts"
|
||||||
@@ -30,7 +29,7 @@
|
|||||||
],
|
],
|
||||||
"references": [
|
"references": [
|
||||||
{
|
{
|
||||||
"path": "../../../src"
|
"path": "../../../src/common"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,11 +11,10 @@
|
|||||||
"removeComments": true,
|
"removeComments": true,
|
||||||
"paths": {
|
"paths": {
|
||||||
"common/*": [
|
"common/*": [
|
||||||
"../../../src/primitives/*",
|
"../../../src/common/*"
|
||||||
"../../../src/runtime/*",
|
|
||||||
],
|
],
|
||||||
"target-browser/*": [
|
"browser/*": [
|
||||||
"../../../src/target-browser/*"
|
"../../../src/browser/*"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"strict": true,
|
"strict": true,
|
||||||
@@ -29,10 +28,10 @@
|
|||||||
],
|
],
|
||||||
"references": [
|
"references": [
|
||||||
{
|
{
|
||||||
"path": "../../../src"
|
"path": "../../../src/common"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"path": "../../../src/target-browser"
|
"path": "../../../src/browser"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"path": "../../../test/playwright"
|
"path": "../../../test/playwright"
|
||||||
|
|||||||
@@ -25,10 +25,7 @@ module.exports = {
|
|||||||
modules: ['./node_modules'],
|
modules: ['./node_modules'],
|
||||||
extensions: [ '.js' ],
|
extensions: [ '.js' ],
|
||||||
alias: {
|
alias: {
|
||||||
common: [
|
common: path.resolve('../../out/common'),
|
||||||
path.resolve('../../out/runtime'),
|
|
||||||
path.resolve('../../out/primitives'),
|
|
||||||
],
|
|
||||||
vs: path.resolve('../../out/vs')
|
vs: path.resolve('../../out/vs')
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -4,8 +4,8 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import { IUnicodeVersionProvider } from '@xterm/xterm';
|
import { IUnicodeVersionProvider } from '@xterm/xterm';
|
||||||
import { createPropertyValue, extractWidth } from 'common/input/UnicodeProperties';
|
import { UnicodeCharProperties, UnicodeCharWidth } from 'common/services/Services';
|
||||||
import { UnicodeCharProperties, UnicodeCharWidth } from 'common/input/UnicodeTypes';
|
import { UnicodeService } from 'common/services/UnicodeService';
|
||||||
|
|
||||||
const BMP_COMBINING = [
|
const BMP_COMBINING = [
|
||||||
[0x0300, 0x036F], [0x0483, 0x0489], [0x0591, 0x05BD],
|
[0x0300, 0x036F], [0x0483, 0x0489], [0x0591, 0x05BD],
|
||||||
@@ -223,13 +223,13 @@ export class UnicodeV11 implements IUnicodeVersionProvider {
|
|||||||
let width = this.wcwidth(codepoint);
|
let width = this.wcwidth(codepoint);
|
||||||
let shouldJoin = width === 0 && preceding !== 0;
|
let shouldJoin = width === 0 && preceding !== 0;
|
||||||
if (shouldJoin) {
|
if (shouldJoin) {
|
||||||
const oldWidth = extractWidth(preceding);
|
const oldWidth = UnicodeService.extractWidth(preceding);
|
||||||
if (oldWidth === 0) {
|
if (oldWidth === 0) {
|
||||||
shouldJoin = false;
|
shouldJoin = false;
|
||||||
} else if (oldWidth > width) {
|
} else if (oldWidth > width) {
|
||||||
width = oldWidth;
|
width = oldWidth;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return createPropertyValue(0, width, shouldJoin);
|
return UnicodeService.createPropertyValue(0, width, shouldJoin);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,8 +13,7 @@
|
|||||||
"strict": true,
|
"strict": true,
|
||||||
"paths": {
|
"paths": {
|
||||||
"common/*": [
|
"common/*": [
|
||||||
"../../../src/primitives/*",
|
"../../../src/common/*"
|
||||||
"../../../src/runtime/*",
|
|
||||||
],
|
],
|
||||||
"@xterm/addon-unicode11": [
|
"@xterm/addon-unicode11": [
|
||||||
"../typings/addon-unicode11.d.ts"
|
"../typings/addon-unicode11.d.ts"
|
||||||
@@ -33,7 +32,7 @@
|
|||||||
],
|
],
|
||||||
"references": [
|
"references": [
|
||||||
{
|
{
|
||||||
"path": "../../../src"
|
"path": "../../../src/common"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,11 +11,10 @@
|
|||||||
"removeComments": true,
|
"removeComments": true,
|
||||||
"paths": {
|
"paths": {
|
||||||
"common/*": [
|
"common/*": [
|
||||||
"../../../src/primitives/*",
|
"../../../src/common/*"
|
||||||
"../../../src/runtime/*",
|
|
||||||
],
|
],
|
||||||
"target-browser/*": [
|
"browser/*": [
|
||||||
"../../../src/target-browser/*"
|
"../../../src/browser/*"
|
||||||
],
|
],
|
||||||
"*": [
|
"*": [
|
||||||
"./*"
|
"./*"
|
||||||
@@ -32,10 +31,10 @@
|
|||||||
],
|
],
|
||||||
"references": [
|
"references": [
|
||||||
{
|
{
|
||||||
"path": "../../../src"
|
"path": "../../../src/common"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"path": "../../../src/target-browser"
|
"path": "../../../src/browser"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"path": "../../../test/playwright"
|
"path": "../../../test/playwright"
|
||||||
|
|||||||
@@ -25,10 +25,7 @@ module.exports = {
|
|||||||
modules: ['./node_modules'],
|
modules: ['./node_modules'],
|
||||||
extensions: [ '.js' ],
|
extensions: [ '.js' ],
|
||||||
alias: {
|
alias: {
|
||||||
common: [
|
common: path.resolve('../../out/common'),
|
||||||
path.resolve('../../out/runtime'),
|
|
||||||
path.resolve('../../out/primitives'),
|
|
||||||
],
|
|
||||||
vs: path.resolve('../../out/vs')
|
vs: path.resolve('../../out/vs')
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -78,22 +78,7 @@ test.describe('WebFontsAddon', () => {
|
|||||||
const data = await ctx.page.evaluate(`
|
const data = await ctx.page.evaluate(`
|
||||||
document.styleSheets[0].insertRule("@font-face {font-family: Kongtext; src: url(/fonts/kongtext.regular.ttf) format('truetype'); unicode-range: U+00A0-00FF}", 0);
|
document.styleSheets[0].insertRule("@font-face {font-family: Kongtext; src: url(/fonts/kongtext.regular.ttf) format('truetype'); unicode-range: U+00A0-00FF}", 0);
|
||||||
`);
|
`);
|
||||||
let fonts = await getDocumentFonts();
|
deepStrictEqual(await getDocumentFonts(), [{ family: 'Kongtext', status: 'unloaded' }]);
|
||||||
// safari may have loaded the font, firefox & chrome never load it
|
|
||||||
if (browser.browserType().name() === 'webkit') {
|
|
||||||
try {
|
|
||||||
deepStrictEqual(fonts, [{ family: 'Kongtext', status: 'loading' }]);
|
|
||||||
} catch (e) {
|
|
||||||
try {
|
|
||||||
deepStrictEqual(fonts, [{ family: 'Kongtext', status: 'loaded' }]);
|
|
||||||
} catch (e) {
|
|
||||||
deepStrictEqual(fonts, [{ family: 'Kongtext', status: 'unloaded' }]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
deepStrictEqual(fonts, [{ family: 'Kongtext', status: 'unloaded' }]);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
// broken case: webfont in ctor without addon usage
|
// broken case: webfont in ctor without addon usage
|
||||||
await ctx.page.evaluate(`
|
await ctx.page.evaluate(`
|
||||||
@@ -101,20 +86,15 @@ test.describe('WebFontsAddon', () => {
|
|||||||
window.helperTerm.open(term.element);
|
window.helperTerm.open(term.element);
|
||||||
`);
|
`);
|
||||||
|
|
||||||
fonts = await getDocumentFonts();
|
// safari loads the font, firefox & chrome dont
|
||||||
// safari might load the font, firefox & chrome dont
|
|
||||||
if (browser.browserType().name() === 'webkit') {
|
if (browser.browserType().name() === 'webkit') {
|
||||||
try {
|
try {
|
||||||
deepStrictEqual(fonts, [{ family: 'Kongtext', status: 'loading' }]);
|
deepStrictEqual(await getDocumentFonts(), [{ family: 'Kongtext', status: 'loading' }]);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
try {
|
deepStrictEqual(await getDocumentFonts(), [{ family: 'Kongtext', status: 'loaded' }]);
|
||||||
deepStrictEqual(fonts, [{ family: 'Kongtext', status: 'loaded' }]);
|
|
||||||
} catch (e) {
|
|
||||||
deepStrictEqual(fonts, [{ family: 'Kongtext', status: 'unloaded' }]);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
deepStrictEqual(fonts, [{ family: 'Kongtext', status: 'unloaded' }]);
|
deepStrictEqual(await getDocumentFonts(), [{ family: 'Kongtext', status: 'unloaded' }]);
|
||||||
}
|
}
|
||||||
|
|
||||||
// good case: addon fixes layout for webfont in ctor
|
// good case: addon fixes layout for webfont in ctor
|
||||||
|
|||||||
@@ -5,17 +5,17 @@
|
|||||||
"lib": [
|
"lib": [
|
||||||
"es2021",
|
"es2021",
|
||||||
],
|
],
|
||||||
|
// "downlevelIteration": true,
|
||||||
"rootDir": ".",
|
"rootDir": ".",
|
||||||
"outDir": "../out-test",
|
"outDir": "../out-test",
|
||||||
"sourceMap": true,
|
"sourceMap": true,
|
||||||
"removeComments": true,
|
"removeComments": true,
|
||||||
"paths": {
|
"paths": {
|
||||||
"common/*": [
|
"common/*": [
|
||||||
"../../../src/primitives/*",
|
"../../../src/common/*"
|
||||||
"../../../src/runtime/*",
|
|
||||||
],
|
],
|
||||||
"target-browser/*": [
|
"browser/*": [
|
||||||
"../../../src/target-browser/*"
|
"../../../src/browser/*"
|
||||||
],
|
],
|
||||||
"*": [
|
"*": [
|
||||||
"./*"
|
"./*"
|
||||||
@@ -32,10 +32,10 @@
|
|||||||
],
|
],
|
||||||
"references": [
|
"references": [
|
||||||
{
|
{
|
||||||
"path": "../../../src"
|
"path": "../../../src/common"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"path": "../../../src/target-browser"
|
"path": "../../../src/browser"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"path": "../../../test/playwright"
|
"path": "../../../test/playwright"
|
||||||
|
|||||||
@@ -113,8 +113,8 @@ export class LinkComputer {
|
|||||||
let line: IBufferLine | undefined;
|
let line: IBufferLine | undefined;
|
||||||
let topIdx = lineIndex;
|
let topIdx = lineIndex;
|
||||||
let bottomIdx = lineIndex;
|
let bottomIdx = lineIndex;
|
||||||
let length;
|
let length = 0;
|
||||||
let content;
|
let content = '';
|
||||||
const lines: string[] = [];
|
const lines: string[] = [];
|
||||||
|
|
||||||
if ((line = terminal.buffer.active.getLine(lineIndex))) {
|
if ((line = terminal.buffer.active.getLine(lineIndex))) {
|
||||||
|
|||||||
@@ -5,17 +5,17 @@
|
|||||||
"lib": [
|
"lib": [
|
||||||
"es2021",
|
"es2021",
|
||||||
],
|
],
|
||||||
|
// "downlevelIteration": true,
|
||||||
"rootDir": ".",
|
"rootDir": ".",
|
||||||
"outDir": "../out-test",
|
"outDir": "../out-test",
|
||||||
"sourceMap": true,
|
"sourceMap": true,
|
||||||
"removeComments": true,
|
"removeComments": true,
|
||||||
"paths": {
|
"paths": {
|
||||||
"common/*": [
|
"common/*": [
|
||||||
"../../../src/primitives/*",
|
"../../../src/common/*"
|
||||||
"../../../src/runtime/*",
|
|
||||||
],
|
],
|
||||||
"target-browser/*": [
|
"browser/*": [
|
||||||
"../../../src/target-browser/*"
|
"../../../src/browser/*"
|
||||||
],
|
],
|
||||||
"*": [
|
"*": [
|
||||||
"./*"
|
"./*"
|
||||||
@@ -32,10 +32,10 @@
|
|||||||
],
|
],
|
||||||
"references": [
|
"references": [
|
||||||
{
|
{
|
||||||
"path": "../../../src"
|
"path": "../../../src/common"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"path": "../../../src/target-browser"
|
"path": "../../../src/browser"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"path": "../../../test/playwright"
|
"path": "../../../test/playwright"
|
||||||
|
|||||||
@@ -1,12 +1,12 @@
|
|||||||
import { ISelectionRenderModel } from 'target-browser/renderer/shared/Types';
|
import { ISelectionRenderModel } from 'browser/renderer/shared/Types';
|
||||||
import { ICoreBrowserService, IThemeService } from 'target-browser/services/Services';
|
import { ICoreBrowserService, IThemeService } from 'browser/services/Services';
|
||||||
import { ReadonlyColorSet } from 'target-browser/Types';
|
import { ReadonlyColorSet } from 'browser/Types';
|
||||||
import { Attributes, BgFlags, ExtFlags, FgFlags, NULL_CELL_CODE, UnderlineStyle } from 'common/buffer/Constants';
|
import { Attributes, BgFlags, ExtFlags, FgFlags, NULL_CELL_CODE, UnderlineStyle } from 'common/buffer/Constants';
|
||||||
import { IDecorationService, IOptionsService } from 'common/services/Services';
|
import { IDecorationService, IOptionsService } from 'common/services/Services';
|
||||||
import { ICellData } from 'common/Types';
|
import { ICellData } from 'common/Types';
|
||||||
import { Terminal } from '@xterm/xterm';
|
import { Terminal } from '@xterm/xterm';
|
||||||
import { rgba } from 'common/Color';
|
import { rgba } from 'common/Color';
|
||||||
import { treatGlyphAsBackgroundColor } from 'target-browser/renderer/shared/RendererUtils';
|
import { treatGlyphAsBackgroundColor } from 'browser/renderer/shared/RendererUtils';
|
||||||
import { blockPatternCodepoints } from './customGlyphs/CustomGlyphDefinitions';
|
import { blockPatternCodepoints } from './customGlyphs/CustomGlyphDefinitions';
|
||||||
|
|
||||||
// Work variables to avoid garbage collection
|
// Work variables to avoid garbage collection
|
||||||
|
|||||||
@@ -5,7 +5,7 @@
|
|||||||
|
|
||||||
import { TextureAtlas } from './TextureAtlas';
|
import { TextureAtlas } from './TextureAtlas';
|
||||||
import { ITerminalOptions, Terminal } from '@xterm/xterm';
|
import { ITerminalOptions, Terminal } from '@xterm/xterm';
|
||||||
import { ITerminal, ReadonlyColorSet } from 'target-browser/Types';
|
import { ITerminal, ReadonlyColorSet } from 'browser/Types';
|
||||||
import { ICharAtlasConfig, ITextureAtlas } from './Types';
|
import { ICharAtlasConfig, ITextureAtlas } from './Types';
|
||||||
import { generateConfig, configEquals } from './CharAtlasUtils';
|
import { generateConfig, configEquals } from './CharAtlasUtils';
|
||||||
import type { ILogService } from 'common/services/Services';
|
import type { ILogService } from 'common/services/Services';
|
||||||
|
|||||||
@@ -1,87 +0,0 @@
|
|||||||
/**
|
|
||||||
* Copyright (c) 2026 The xterm.js authors. All rights reserved.
|
|
||||||
* @license MIT
|
|
||||||
*/
|
|
||||||
|
|
||||||
import { assert } from 'chai';
|
|
||||||
import { configEquals } from './CharAtlasUtils';
|
|
||||||
import { ICharAtlasConfig } from './Types';
|
|
||||||
import { NULL_COLOR } from 'common/Color';
|
|
||||||
import { IColor } from 'common/Types';
|
|
||||||
|
|
||||||
function createTestConfig(overrides: Partial<ICharAtlasConfig> = {}): ICharAtlasConfig {
|
|
||||||
const color: IColor = { css: '#ffffff', rgba: 0xffffffff };
|
|
||||||
const contrastCache = {
|
|
||||||
clear: () => {},
|
|
||||||
setCss: () => {},
|
|
||||||
getCss: () => undefined,
|
|
||||||
setColor: () => {},
|
|
||||||
getColor: () => undefined
|
|
||||||
};
|
|
||||||
const colors = {
|
|
||||||
foreground: color,
|
|
||||||
background: color,
|
|
||||||
cursor: NULL_COLOR,
|
|
||||||
cursorAccent: NULL_COLOR,
|
|
||||||
selectionForeground: undefined,
|
|
||||||
selectionBackgroundTransparent: NULL_COLOR,
|
|
||||||
selectionBackgroundOpaque: NULL_COLOR,
|
|
||||||
selectionInactiveBackgroundTransparent: NULL_COLOR,
|
|
||||||
selectionInactiveBackgroundOpaque: NULL_COLOR,
|
|
||||||
overviewRulerBorder: NULL_COLOR,
|
|
||||||
scrollbarSliderBackground: NULL_COLOR,
|
|
||||||
scrollbarSliderHoverBackground: NULL_COLOR,
|
|
||||||
scrollbarSliderActiveBackground: NULL_COLOR,
|
|
||||||
ansi: new Array(256).fill(color),
|
|
||||||
contrastCache,
|
|
||||||
halfContrastCache: contrastCache
|
|
||||||
};
|
|
||||||
return {
|
|
||||||
customGlyphs: true,
|
|
||||||
devicePixelRatio: 1,
|
|
||||||
deviceMaxTextureSize: 4096,
|
|
||||||
letterSpacing: 0,
|
|
||||||
lineHeight: 1,
|
|
||||||
fontSize: 15,
|
|
||||||
fontFamily: 'monospace',
|
|
||||||
fontWeight: 'normal',
|
|
||||||
fontWeightBold: 'bold',
|
|
||||||
deviceCellWidth: 10,
|
|
||||||
deviceCellHeight: 20,
|
|
||||||
deviceCharWidth: 8,
|
|
||||||
deviceCharHeight: 16,
|
|
||||||
allowTransparency: false,
|
|
||||||
drawBoldTextInBrightColors: true,
|
|
||||||
minimumContrastRatio: 1,
|
|
||||||
colors,
|
|
||||||
...overrides
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
describe('CharAtlasUtils', () => {
|
|
||||||
describe('configEquals', () => {
|
|
||||||
it('should return true for identical configs', () => {
|
|
||||||
const a = createTestConfig();
|
|
||||||
const b = createTestConfig();
|
|
||||||
assert.ok(configEquals(a, b));
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should return false when deviceMaxTextureSize differs', () => {
|
|
||||||
const a = createTestConfig();
|
|
||||||
const b = createTestConfig({ deviceMaxTextureSize: 8192 });
|
|
||||||
assert.ok(!configEquals(a, b));
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should return false when deviceCellWidth differs', () => {
|
|
||||||
const a = createTestConfig();
|
|
||||||
const b = createTestConfig({ deviceCellWidth: 11 });
|
|
||||||
assert.ok(!configEquals(a, b));
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should return false when deviceCellHeight differs', () => {
|
|
||||||
const a = createTestConfig();
|
|
||||||
const b = createTestConfig({ deviceCellHeight: 21 });
|
|
||||||
assert.ok(!configEquals(a, b));
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
@@ -6,7 +6,7 @@
|
|||||||
import { ICharAtlasConfig } from './Types';
|
import { ICharAtlasConfig } from './Types';
|
||||||
import { Attributes } from 'common/buffer/Constants';
|
import { Attributes } from 'common/buffer/Constants';
|
||||||
import { ITerminalOptions } from '@xterm/xterm';
|
import { ITerminalOptions } from '@xterm/xterm';
|
||||||
import { IColorSet, ReadonlyColorSet } from 'target-browser/Types';
|
import { IColorSet, ReadonlyColorSet } from 'browser/Types';
|
||||||
import { NULL_COLOR } from 'common/Color';
|
import { NULL_COLOR } from 'common/Color';
|
||||||
|
|
||||||
export function generateConfig(deviceCellWidth: number, deviceCellHeight: number, deviceCharWidth: number, deviceCharHeight: number, options: Required<ITerminalOptions>, colors: ReadonlyColorSet, devicePixelRatio: number, deviceMaxTextureSize: number, customGlyphs: boolean = true): ICharAtlasConfig {
|
export function generateConfig(deviceCellWidth: number, deviceCellHeight: number, deviceCharWidth: number, deviceCharHeight: number, options: Required<ITerminalOptions>, colors: ReadonlyColorSet, devicePixelRatio: number, deviceMaxTextureSize: number, customGlyphs: boolean = true): ICharAtlasConfig {
|
||||||
@@ -59,7 +59,6 @@ export function configEquals(a: ICharAtlasConfig, b: ICharAtlasConfig): boolean
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
return a.devicePixelRatio === b.devicePixelRatio &&
|
return a.devicePixelRatio === b.devicePixelRatio &&
|
||||||
a.deviceMaxTextureSize === b.deviceMaxTextureSize &&
|
|
||||||
a.customGlyphs === b.customGlyphs &&
|
a.customGlyphs === b.customGlyphs &&
|
||||||
a.lineHeight === b.lineHeight &&
|
a.lineHeight === b.lineHeight &&
|
||||||
a.letterSpacing === b.letterSpacing &&
|
a.letterSpacing === b.letterSpacing &&
|
||||||
@@ -68,8 +67,6 @@ export function configEquals(a: ICharAtlasConfig, b: ICharAtlasConfig): boolean
|
|||||||
a.fontWeight === b.fontWeight &&
|
a.fontWeight === b.fontWeight &&
|
||||||
a.fontWeightBold === b.fontWeightBold &&
|
a.fontWeightBold === b.fontWeightBold &&
|
||||||
a.allowTransparency === b.allowTransparency &&
|
a.allowTransparency === b.allowTransparency &&
|
||||||
a.deviceCellWidth === b.deviceCellWidth &&
|
|
||||||
a.deviceCellHeight === b.deviceCellHeight &&
|
|
||||||
a.deviceCharWidth === b.deviceCharWidth &&
|
a.deviceCharWidth === b.deviceCharWidth &&
|
||||||
a.deviceCharHeight === b.deviceCharHeight &&
|
a.deviceCharHeight === b.deviceCharHeight &&
|
||||||
a.drawBoldTextInBrightColors === b.drawBoldTextInBrightColors &&
|
a.drawBoldTextInBrightColors === b.drawBoldTextInBrightColors &&
|
||||||
|
|||||||
@@ -3,8 +3,8 @@
|
|||||||
* @license MIT
|
* @license MIT
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { RendererConstants } from 'target-browser/renderer/shared/Constants';
|
import { RendererConstants } from 'browser/renderer/shared/Constants';
|
||||||
import { ICoreBrowserService } from 'target-browser/services/Services';
|
import { ICoreBrowserService } from 'browser/services/Services';
|
||||||
|
|
||||||
const enum Constants {
|
const enum Constants {
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -3,14 +3,14 @@
|
|||||||
* @license MIT
|
* @license MIT
|
||||||
*/
|
*/
|
||||||
import { TextureAtlas } from './TextureAtlas';
|
import { TextureAtlas } from './TextureAtlas';
|
||||||
import { IRenderDimensions } from 'target-browser/renderer/shared/Types';
|
import { IRenderDimensions } from 'browser/renderer/shared/Types';
|
||||||
import { NULL_CELL_CODE } from 'common/buffer/Constants';
|
import { NULL_CELL_CODE } from 'common/buffer/Constants';
|
||||||
import { Disposable, toDisposable } from 'common/Lifecycle';
|
import { Disposable, toDisposable } from 'common/Lifecycle';
|
||||||
import { Terminal } from '@xterm/xterm';
|
import { Terminal } from '@xterm/xterm';
|
||||||
import { IRenderModel, IWebGL2RenderingContext, IWebGLVertexArrayObject, type IRasterizedGlyph, type ITextureAtlas } from './Types';
|
import { IRenderModel, IWebGL2RenderingContext, IWebGLVertexArrayObject, type IRasterizedGlyph, type ITextureAtlas } from './Types';
|
||||||
import { createProgram, GLTexture, PROJECTION_MATRIX } from './WebglUtils';
|
import { createProgram, GLTexture, PROJECTION_MATRIX } from './WebglUtils';
|
||||||
import type { ILogService, IOptionsService } from 'common/services/Services';
|
import type { IOptionsService } from 'common/services/Services';
|
||||||
import { allowRescaling, throwIfFalsy } from 'target-browser/renderer/shared/RendererUtils';
|
import { allowRescaling, throwIfFalsy } from 'browser/renderer/shared/RendererUtils';
|
||||||
|
|
||||||
interface IVertices {
|
interface IVertices {
|
||||||
attributes: Float32Array;
|
attributes: Float32Array;
|
||||||
@@ -114,8 +114,7 @@ export class GlyphRenderer extends Disposable {
|
|||||||
private readonly _terminal: Terminal,
|
private readonly _terminal: Terminal,
|
||||||
private readonly _gl: IWebGL2RenderingContext,
|
private readonly _gl: IWebGL2RenderingContext,
|
||||||
private _dimensions: IRenderDimensions,
|
private _dimensions: IRenderDimensions,
|
||||||
private readonly _optionsService: IOptionsService,
|
private readonly _optionsService: IOptionsService
|
||||||
private readonly _logService: ILogService
|
|
||||||
) {
|
) {
|
||||||
super();
|
super();
|
||||||
|
|
||||||
@@ -128,7 +127,7 @@ export class GlyphRenderer extends Disposable {
|
|||||||
TextureAtlas.maxTextureSize = throwIfFalsy(gl.getParameter(gl.MAX_TEXTURE_SIZE) as number | null);
|
TextureAtlas.maxTextureSize = throwIfFalsy(gl.getParameter(gl.MAX_TEXTURE_SIZE) as number | null);
|
||||||
}
|
}
|
||||||
|
|
||||||
this._program = throwIfFalsy(createProgram(gl, vertexShaderSource, createFragmentShaderSource(TextureAtlas.maxAtlasPages), this._logService));
|
this._program = throwIfFalsy(createProgram(gl, vertexShaderSource, createFragmentShaderSource(TextureAtlas.maxAtlasPages)));
|
||||||
this._register(toDisposable(() => gl.deleteProgram(this._program)));
|
this._register(toDisposable(() => gl.deleteProgram(this._program)));
|
||||||
|
|
||||||
// Uniform locations
|
// Uniform locations
|
||||||
@@ -137,9 +136,8 @@ export class GlyphRenderer extends Disposable {
|
|||||||
this._textureLocation = throwIfFalsy(gl.getUniformLocation(this._program, 'u_texture'));
|
this._textureLocation = throwIfFalsy(gl.getUniformLocation(this._program, 'u_texture'));
|
||||||
|
|
||||||
// Create and set the vertex array object
|
// Create and set the vertex array object
|
||||||
const vertexArrayObject = this._vertexArrayObject = gl.createVertexArray();
|
this._vertexArrayObject = gl.createVertexArray();
|
||||||
this._register(toDisposable(() => gl.deleteVertexArray(vertexArrayObject)));
|
gl.bindVertexArray(this._vertexArrayObject);
|
||||||
gl.bindVertexArray(vertexArrayObject);
|
|
||||||
|
|
||||||
// Setup a_unitquad, this defines the 4 vertices of a rectangle
|
// Setup a_unitquad, this defines the 4 vertices of a rectangle
|
||||||
const unitQuadVertices = new Float32Array([0, 0, 1, 0, 0, 1, 1, 1]);
|
const unitQuadVertices = new Float32Array([0, 0, 1, 0, 0, 1, 1, 1]);
|
||||||
@@ -360,9 +358,7 @@ export class GlyphRenderer extends Disposable {
|
|||||||
gl.bindBuffer(gl.ARRAY_BUFFER, this._attributesBuffer);
|
gl.bindBuffer(gl.ARRAY_BUFFER, this._attributesBuffer);
|
||||||
gl.bufferData(gl.ARRAY_BUFFER, activeBuffer.subarray(0, bufferLength), gl.STREAM_DRAW);
|
gl.bufferData(gl.ARRAY_BUFFER, activeBuffer.subarray(0, bufferLength), gl.STREAM_DRAW);
|
||||||
|
|
||||||
// Bind the atlas page texture if they have changed. AtlasPage.version is globally
|
// Bind the atlas page texture if they have changed
|
||||||
// monotonic, so a page object swap at the same index (which happens after a page merge)
|
|
||||||
// is detected by the same comparison.
|
|
||||||
for (let i = 0; i < this._atlas.pages.length; i++) {
|
for (let i = 0; i < this._atlas.pages.length; i++) {
|
||||||
if (this._atlas.pages[i].version !== this._atlasTextures[i].version) {
|
if (this._atlas.pages[i].version !== this._atlasTextures[i].version) {
|
||||||
this._bindAtlasPageTexture(gl, this._atlas, i);
|
this._bindAtlasPageTexture(gl, this._atlas, i);
|
||||||
@@ -375,10 +371,6 @@ export class GlyphRenderer extends Disposable {
|
|||||||
|
|
||||||
public setAtlas(atlas: ITextureAtlas): void {
|
public setAtlas(atlas: ITextureAtlas): void {
|
||||||
this._atlas = atlas;
|
this._atlas = atlas;
|
||||||
this.invalidateAtlasTextures();
|
|
||||||
}
|
|
||||||
|
|
||||||
public invalidateAtlasTextures(): void {
|
|
||||||
for (const glTexture of this._atlasTextures) {
|
for (const glTexture of this._atlasTextures) {
|
||||||
glTexture.version = -1;
|
glTexture.version = -1;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,9 +3,9 @@
|
|||||||
* @license MIT
|
* @license MIT
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { IRenderDimensions } from 'target-browser/renderer/shared/Types';
|
import { IRenderDimensions } from 'browser/renderer/shared/Types';
|
||||||
import { IThemeService } from 'target-browser/services/Services';
|
import { IThemeService } from 'browser/services/Services';
|
||||||
import { ReadonlyColorSet } from 'target-browser/Types';
|
import { ReadonlyColorSet } from 'browser/Types';
|
||||||
import { Attributes, FgFlags } from 'common/buffer/Constants';
|
import { Attributes, FgFlags } from 'common/buffer/Constants';
|
||||||
import { Disposable, toDisposable } from 'common/Lifecycle';
|
import { Disposable, toDisposable } from 'common/Lifecycle';
|
||||||
import { IColor } from 'common/Types';
|
import { IColor } from 'common/Types';
|
||||||
@@ -13,8 +13,7 @@ import { Terminal } from '@xterm/xterm';
|
|||||||
import { RenderModelConstants } from './RenderModel';
|
import { RenderModelConstants } from './RenderModel';
|
||||||
import { IRenderModel, IWebGL2RenderingContext, IWebGLVertexArrayObject } from './Types';
|
import { IRenderModel, IWebGL2RenderingContext, IWebGLVertexArrayObject } from './Types';
|
||||||
import { createProgram, expandFloat32Array, PROJECTION_MATRIX } from './WebglUtils';
|
import { createProgram, expandFloat32Array, PROJECTION_MATRIX } from './WebglUtils';
|
||||||
import { throwIfFalsy } from 'target-browser/renderer/shared/RendererUtils';
|
import { throwIfFalsy } from 'browser/renderer/shared/RendererUtils';
|
||||||
import type { ILogService } from 'common/services/Services';
|
|
||||||
|
|
||||||
const enum VertexAttribLocations {
|
const enum VertexAttribLocations {
|
||||||
POSITION = 0,
|
POSITION = 0,
|
||||||
@@ -90,23 +89,21 @@ export class RectangleRenderer extends Disposable {
|
|||||||
private _terminal: Terminal,
|
private _terminal: Terminal,
|
||||||
private _gl: IWebGL2RenderingContext,
|
private _gl: IWebGL2RenderingContext,
|
||||||
private _dimensions: IRenderDimensions,
|
private _dimensions: IRenderDimensions,
|
||||||
private readonly _themeService: IThemeService,
|
private readonly _themeService: IThemeService
|
||||||
private readonly _logService: ILogService
|
|
||||||
) {
|
) {
|
||||||
super();
|
super();
|
||||||
|
|
||||||
const gl = this._gl;
|
const gl = this._gl;
|
||||||
|
|
||||||
this._program = throwIfFalsy(createProgram(gl, vertexShaderSource, fragmentShaderSource, this._logService));
|
this._program = throwIfFalsy(createProgram(gl, vertexShaderSource, fragmentShaderSource));
|
||||||
this._register(toDisposable(() => gl.deleteProgram(this._program)));
|
this._register(toDisposable(() => gl.deleteProgram(this._program)));
|
||||||
|
|
||||||
// Uniform locations
|
// Uniform locations
|
||||||
this._projectionLocation = throwIfFalsy(gl.getUniformLocation(this._program, 'u_projection'));
|
this._projectionLocation = throwIfFalsy(gl.getUniformLocation(this._program, 'u_projection'));
|
||||||
|
|
||||||
// Create and set the vertex array object
|
// Create and set the vertex array object
|
||||||
const vertexArrayObject = this._vertexArrayObject = gl.createVertexArray();
|
this._vertexArrayObject = gl.createVertexArray();
|
||||||
this._register(toDisposable(() => gl.deleteVertexArray(vertexArrayObject)));
|
gl.bindVertexArray(this._vertexArrayObject);
|
||||||
gl.bindVertexArray(vertexArrayObject);
|
|
||||||
|
|
||||||
// Setup a_unitquad, this defines the 4 vertices of a rectangle
|
// Setup a_unitquad, this defines the 4 vertices of a rectangle
|
||||||
const unitQuadVertices = new Float32Array([0, 0, 1, 0, 0, 1, 1, 1]);
|
const unitQuadVertices = new Float32Array([0, 0, 1, 0, 0, 1, 1, 1]);
|
||||||
@@ -339,9 +336,8 @@ export class RectangleRenderer extends Disposable {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (vertices.attributes.length < offset + INDICES_PER_RECTANGLE) {
|
if (vertices.attributes.length < offset + 4) {
|
||||||
// +1 for the viewport-clear rectangle at offset 0.
|
vertices.attributes = expandFloat32Array(vertices.attributes, this._terminal.rows * this._terminal.cols * INDICES_PER_RECTANGLE);
|
||||||
vertices.attributes = expandFloat32Array(vertices.attributes, (this._terminal.rows * this._terminal.cols + 1) * INDICES_PER_RECTANGLE);
|
|
||||||
}
|
}
|
||||||
$x1 = startX * this._dimensions.device.cell.width;
|
$x1 = startX * this._dimensions.device.cell.width;
|
||||||
$y1 = y * this._dimensions.device.cell.height;
|
$y1 = y * this._dimensions.device.cell.height;
|
||||||
|
|||||||
@@ -4,8 +4,8 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import { ICursorRenderModel, IRenderModel } from './Types';
|
import { ICursorRenderModel, IRenderModel } from './Types';
|
||||||
import { ISelectionRenderModel } from 'target-browser/renderer/shared/Types';
|
import { ISelectionRenderModel } from 'browser/renderer/shared/Types';
|
||||||
import { createSelectionRenderModel } from 'target-browser/renderer/shared/SelectionRenderModel';
|
import { createSelectionRenderModel } from 'browser/renderer/shared/SelectionRenderModel';
|
||||||
|
|
||||||
export const enum RenderModelConstants {
|
export const enum RenderModelConstants {
|
||||||
INDICIES_PER_CELL = 4,
|
INDICIES_PER_CELL = 4,
|
||||||
|
|||||||
@@ -3,10 +3,10 @@
|
|||||||
* @license MIT
|
* @license MIT
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { IColorContrastCache } from 'target-browser/Types';
|
import { IColorContrastCache } from 'browser/Types';
|
||||||
import { DIM_OPACITY, TEXT_BASELINE } from './Constants';
|
import { DIM_OPACITY, TEXT_BASELINE } from './Constants';
|
||||||
import { tryDrawCustomGlyph } from './customGlyphs/CustomGlyphRasterizer';
|
import { tryDrawCustomGlyph } from './customGlyphs/CustomGlyphRasterizer';
|
||||||
import { computeNextVariantOffset, treatGlyphAsBackgroundColor, isPowerlineGlyph, isRestrictedPowerlineGlyph, throwIfFalsy } from 'target-browser/renderer/shared/RendererUtils';
|
import { computeNextVariantOffset, treatGlyphAsBackgroundColor, isPowerlineGlyph, isRestrictedPowerlineGlyph, throwIfFalsy } from 'browser/renderer/shared/RendererUtils';
|
||||||
import { IBoundingBox, ICharAtlasConfig, IRasterizedGlyph, ITextureAtlas } from './Types';
|
import { IBoundingBox, ICharAtlasConfig, IRasterizedGlyph, ITextureAtlas } from './Types';
|
||||||
import { NULL_COLOR, channels, color, rgba } from 'common/Color';
|
import { NULL_COLOR, channels, color, rgba } from 'common/Color';
|
||||||
import { FourKeyMap } from 'common/MultiKeyMap';
|
import { FourKeyMap } from 'common/MultiKeyMap';
|
||||||
@@ -109,7 +109,6 @@ export class TextureAtlas implements ITextureAtlas {
|
|||||||
page.canvas.remove();
|
page.canvas.remove();
|
||||||
}
|
}
|
||||||
this._onAddTextureAtlasCanvas.dispose();
|
this._onAddTextureAtlasCanvas.dispose();
|
||||||
this._onRemoveTextureAtlasCanvas.dispose();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public warmUp(): void {
|
public warmUp(): void {
|
||||||
@@ -134,9 +133,7 @@ export class TextureAtlas implements ITextureAtlas {
|
|||||||
|
|
||||||
private _requestClearModel = false;
|
private _requestClearModel = false;
|
||||||
public beginFrame(): boolean {
|
public beginFrame(): boolean {
|
||||||
const result = this._requestClearModel;
|
return this._requestClearModel;
|
||||||
this._requestClearModel = false;
|
|
||||||
return result;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public clearTexture(): void {
|
public clearTexture(): void {
|
||||||
@@ -196,7 +193,7 @@ export class TextureAtlas implements ITextureAtlas {
|
|||||||
|
|
||||||
// Merge into the new page
|
// Merge into the new page
|
||||||
const mergedPage = this._mergePages(mergingPages, mergedPageIndex);
|
const mergedPage = this._mergePages(mergingPages, mergedPageIndex);
|
||||||
mergedPage.version = ++AtlasPage.nextVersion;
|
mergedPage.version++;
|
||||||
|
|
||||||
// Delete the pages, shifting glyph texture pages as needed
|
// Delete the pages, shifting glyph texture pages as needed
|
||||||
for (let i = sortedMergingPagesIndexes.length - 1; i >= 0; i--) {
|
for (let i = sortedMergingPagesIndexes.length - 1; i >= 0; i--) {
|
||||||
@@ -254,7 +251,7 @@ export class TextureAtlas implements ITextureAtlas {
|
|||||||
for (const g of adjustingPage.glyphs) {
|
for (const g of adjustingPage.glyphs) {
|
||||||
g.texturePage--;
|
g.texturePage--;
|
||||||
}
|
}
|
||||||
adjustingPage.version = ++AtlasPage.nextVersion;
|
adjustingPage.version++;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -530,7 +527,7 @@ export class TextureAtlas implements ITextureAtlas {
|
|||||||
let customGlyph = false;
|
let customGlyph = false;
|
||||||
if (this._config.customGlyphs !== false) {
|
if (this._config.customGlyphs !== false) {
|
||||||
const variantOffset = this._workAttributeData.getUnderlineVariantOffset();
|
const variantOffset = this._workAttributeData.getUnderlineVariantOffset();
|
||||||
customGlyph = tryDrawCustomGlyph(this._tmpCtx, chars, padding, padding, this._config.deviceCellWidth, this._config.deviceCellHeight, this._config.deviceCharWidth, this._config.deviceCharHeight, this._config.fontSize, this._config.devicePixelRatio, this._logService, backgroundColor.css, variantOffset);
|
customGlyph = tryDrawCustomGlyph(this._tmpCtx, chars, padding, padding, this._config.deviceCellWidth, this._config.deviceCellHeight, this._config.deviceCharWidth, this._config.deviceCharHeight, this._config.fontSize, this._config.devicePixelRatio, backgroundColor.css, variantOffset);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Whether to clear pixels based on a threshold difference between the glyph color and the
|
// Whether to clear pixels based on a threshold difference between the glyph color and the
|
||||||
@@ -940,7 +937,7 @@ export class TextureAtlas implements ITextureAtlas {
|
|||||||
rasterizedGlyph.size.y
|
rasterizedGlyph.size.y
|
||||||
);
|
);
|
||||||
activePage.addGlyph(rasterizedGlyph);
|
activePage.addGlyph(rasterizedGlyph);
|
||||||
activePage.version = ++AtlasPage.nextVersion;
|
activePage.version++;
|
||||||
|
|
||||||
return rasterizedGlyph;
|
return rasterizedGlyph;
|
||||||
}
|
}
|
||||||
@@ -1050,13 +1047,9 @@ class AtlasPage {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Monotonically increasing across all atlas pages globally. Used to detect when the texture
|
* Used to check whether the canvas of the atlas page has changed.
|
||||||
* unit at a given index needs to be re-uploaded — both for content changes within the same
|
|
||||||
* page and for a page object swap at the same index (which happens after a page merge,
|
|
||||||
* where a per-page counter could coincide with the previously-bound page's value).
|
|
||||||
*/
|
*/
|
||||||
public static nextVersion: number = 0;
|
public version = 0;
|
||||||
public version = ++AtlasPage.nextVersion;
|
|
||||||
|
|
||||||
// Texture atlas current positioning data. The texture packing strategy used is to fill from
|
// Texture atlas current positioning data. The texture packing strategy used is to fill from
|
||||||
// left-to-right and top-to-bottom. When the glyph being written is less than half of the current
|
// left-to-right and top-to-bottom. When the glyph being written is less than half of the current
|
||||||
@@ -1099,7 +1092,7 @@ class AtlasPage {
|
|||||||
this.currentRow.y = 0;
|
this.currentRow.y = 0;
|
||||||
this.currentRow.height = 0;
|
this.currentRow.height = 0;
|
||||||
this.fixedRows.length = 0;
|
this.fixedRows.length = 0;
|
||||||
this.version = ++AtlasPage.nextVersion;
|
this.version++;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -4,8 +4,8 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import { FontWeight } from '@xterm/xterm';
|
import { FontWeight } from '@xterm/xterm';
|
||||||
import { IColorSet } from 'target-browser/Types';
|
import { IColorSet } from 'browser/Types';
|
||||||
import { ISelectionRenderModel } from 'target-browser/renderer/shared/Types';
|
import { ISelectionRenderModel } from 'browser/renderer/shared/Types';
|
||||||
import { CursorInactiveStyle, CursorStyle, type IDisposable } from 'common/Types';
|
import { CursorInactiveStyle, CursorStyle, type IDisposable } from 'common/Types';
|
||||||
import type { IEvent } from 'common/Event';
|
import type { IEvent } from 'common/Event';
|
||||||
|
|
||||||
@@ -28,7 +28,6 @@ export interface ICursorRenderModel {
|
|||||||
export interface IWebGL2RenderingContext extends WebGLRenderingContext {
|
export interface IWebGL2RenderingContext extends WebGLRenderingContext {
|
||||||
vertexAttribDivisor(index: number, divisor: number): void;
|
vertexAttribDivisor(index: number, divisor: number): void;
|
||||||
createVertexArray(): IWebGLVertexArrayObject;
|
createVertexArray(): IWebGLVertexArrayObject;
|
||||||
deleteVertexArray(vao: IWebGLVertexArrayObject): void;
|
|
||||||
bindVertexArray(vao: IWebGLVertexArrayObject): void;
|
bindVertexArray(vao: IWebGLVertexArrayObject): void;
|
||||||
drawElementsInstanced(mode: number, count: number, type: number, offset: number, instanceCount: number): void;
|
drawElementsInstanced(mode: number, count: number, type: number, offset: number, instanceCount: number): void;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,11 +5,11 @@
|
|||||||
|
|
||||||
import type { ITerminalAddon, Terminal } from '@xterm/xterm';
|
import type { ITerminalAddon, Terminal } from '@xterm/xterm';
|
||||||
import type { IWebglAddonOptions, WebglAddon as IWebglApi } from '@xterm/addon-webgl';
|
import type { IWebglAddonOptions, WebglAddon as IWebglApi } from '@xterm/addon-webgl';
|
||||||
import { ICharacterJoinerService, ICharSizeService, ICoreBrowserService, IRenderService, IThemeService } from 'target-browser/services/Services';
|
import { ICharacterJoinerService, ICharSizeService, ICoreBrowserService, IRenderService, IThemeService } from 'browser/services/Services';
|
||||||
import { ITerminal } from 'target-browser/Types';
|
import { ITerminal } from 'browser/Types';
|
||||||
import { Disposable, toDisposable } from 'common/Lifecycle';
|
import { Disposable, toDisposable } from 'common/Lifecycle';
|
||||||
import { getSafariVersion, isSafari } from 'common/Platform';
|
import { getSafariVersion, isSafari } from 'common/Platform';
|
||||||
import { ICoreService, IDecorationService, ILogService, IOptionsService } from 'common/services/Services';
|
import { ICoreService, IDecorationService, IOptionsService } from 'common/services/Services';
|
||||||
import { IWebGL2RenderingContext } from './Types';
|
import { IWebGL2RenderingContext } from './Types';
|
||||||
import { WebglRenderer } from './WebglRenderer';
|
import { WebglRenderer } from './WebglRenderer';
|
||||||
import { Emitter, EventUtils } from 'common/Event';
|
import { Emitter, EventUtils } from 'common/Event';
|
||||||
@@ -65,7 +65,6 @@ export class WebglAddon extends Disposable implements ITerminalAddon, IWebglApi
|
|||||||
const charSizeService: ICharSizeService = unsafeCore._charSizeService;
|
const charSizeService: ICharSizeService = unsafeCore._charSizeService;
|
||||||
const coreBrowserService: ICoreBrowserService = unsafeCore._coreBrowserService;
|
const coreBrowserService: ICoreBrowserService = unsafeCore._coreBrowserService;
|
||||||
const decorationService: IDecorationService = unsafeCore._decorationService;
|
const decorationService: IDecorationService = unsafeCore._decorationService;
|
||||||
const logService: ILogService = unsafeCore._logService;
|
|
||||||
const themeService: IThemeService = unsafeCore._themeService;
|
const themeService: IThemeService = unsafeCore._themeService;
|
||||||
|
|
||||||
this._renderer = this._register(new WebglRenderer(
|
this._renderer = this._register(new WebglRenderer(
|
||||||
@@ -75,7 +74,6 @@ export class WebglAddon extends Disposable implements ITerminalAddon, IWebglApi
|
|||||||
coreBrowserService,
|
coreBrowserService,
|
||||||
coreService,
|
coreService,
|
||||||
decorationService,
|
decorationService,
|
||||||
logService,
|
|
||||||
optionsService,
|
optionsService,
|
||||||
themeService,
|
themeService,
|
||||||
this._customGlyphs,
|
this._customGlyphs,
|
||||||
|
|||||||
@@ -3,19 +3,19 @@
|
|||||||
* @license MIT
|
* @license MIT
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { ITerminal } from 'target-browser/Types';
|
import { ITerminal } from 'browser/Types';
|
||||||
import { CellColorResolver } from './CellColorResolver';
|
import { CellColorResolver } from './CellColorResolver';
|
||||||
import { acquireTextureAtlas, removeTerminalFromCache } from './CharAtlasCache';
|
import { acquireTextureAtlas, removeTerminalFromCache } from './CharAtlasCache';
|
||||||
import { CursorBlinkStateManager } from './CursorBlinkStateManager';
|
import { CursorBlinkStateManager } from './CursorBlinkStateManager';
|
||||||
import { observeDevicePixelDimensions } from './DevicePixelObserver';
|
import { observeDevicePixelDimensions } from './DevicePixelObserver';
|
||||||
import { IRenderDimensions, IRenderer, IRequestRedrawEvent } from 'target-browser/renderer/shared/Types';
|
import { IRenderDimensions, IRenderer, IRequestRedrawEvent } from 'browser/renderer/shared/Types';
|
||||||
import { ICharSizeService, ICharacterJoinerService, ICoreBrowserService, IThemeService } from 'target-browser/services/Services';
|
import { ICharSizeService, ICharacterJoinerService, ICoreBrowserService, IThemeService } from 'browser/services/Services';
|
||||||
import { CharData, IBufferLine, ICellData } from 'common/Types';
|
import { CharData, IBufferLine, ICellData } from 'common/Types';
|
||||||
import { AttributeData } from 'common/buffer/AttributeData';
|
import { AttributeData } from 'common/buffer/AttributeData';
|
||||||
import { CellData } from 'common/buffer/CellData';
|
import { CellData } from 'common/buffer/CellData';
|
||||||
import { Attributes, Content, FgFlags, NULL_CELL_CHAR, NULL_CELL_CODE } from 'common/buffer/Constants';
|
import { Attributes, Content, FgFlags, NULL_CELL_CHAR, NULL_CELL_CODE } from 'common/buffer/Constants';
|
||||||
import { TextBlinkStateManager } from 'target-browser/renderer/shared/TextBlinkStateManager';
|
import { TextBlinkStateManager } from 'browser/renderer/shared/TextBlinkStateManager';
|
||||||
import { ICoreService, IDecorationService, ILogService, IOptionsService } from 'common/services/Services';
|
import { ICoreService, IDecorationService, IOptionsService } from 'common/services/Services';
|
||||||
import { Terminal } from '@xterm/xterm';
|
import { Terminal } from '@xterm/xterm';
|
||||||
import { GlyphRenderer } from './GlyphRenderer';
|
import { GlyphRenderer } from './GlyphRenderer';
|
||||||
import { RectangleRenderer } from './RectangleRenderer';
|
import { RectangleRenderer } from './RectangleRenderer';
|
||||||
@@ -24,13 +24,9 @@ import { IWebGL2RenderingContext, type ITextureAtlas } from './Types';
|
|||||||
import { LinkRenderLayer } from './renderLayer/LinkRenderLayer';
|
import { LinkRenderLayer } from './renderLayer/LinkRenderLayer';
|
||||||
import { IRenderLayer } from './renderLayer/Types';
|
import { IRenderLayer } from './renderLayer/Types';
|
||||||
import { Emitter, EventUtils } from 'common/Event';
|
import { Emitter, EventUtils } from 'common/Event';
|
||||||
import { addDisposableListener } from 'target-browser/Dom';
|
import { addDisposableListener } from 'browser/Dom';
|
||||||
import { combinedDisposable, Disposable, MutableDisposable, toDisposable } from 'common/Lifecycle';
|
import { combinedDisposable, Disposable, MutableDisposable, toDisposable } from 'common/Lifecycle';
|
||||||
import { createRenderDimensions } from 'target-browser/renderer/shared/RendererUtils';
|
import { createRenderDimensions } from 'browser/renderer/shared/RendererUtils';
|
||||||
|
|
||||||
const enum Constants {
|
|
||||||
MERGE_RETRY_LIMIT = 32
|
|
||||||
}
|
|
||||||
|
|
||||||
export class WebglRenderer extends Disposable implements IRenderer {
|
export class WebglRenderer extends Disposable implements IRenderer {
|
||||||
private _renderLayers: IRenderLayer[];
|
private _renderLayers: IRenderLayer[];
|
||||||
@@ -77,7 +73,6 @@ export class WebglRenderer extends Disposable implements IRenderer {
|
|||||||
private readonly _coreBrowserService: ICoreBrowserService,
|
private readonly _coreBrowserService: ICoreBrowserService,
|
||||||
private readonly _coreService: ICoreService,
|
private readonly _coreService: ICoreService,
|
||||||
private readonly _decorationService: IDecorationService,
|
private readonly _decorationService: IDecorationService,
|
||||||
private readonly _logService: ILogService,
|
|
||||||
private readonly _optionsService: IOptionsService,
|
private readonly _optionsService: IOptionsService,
|
||||||
private readonly _themeService: IThemeService,
|
private readonly _themeService: IThemeService,
|
||||||
private readonly _customGlyphs: boolean = true,
|
private readonly _customGlyphs: boolean = true,
|
||||||
@@ -123,19 +118,19 @@ export class WebglRenderer extends Disposable implements IRenderer {
|
|||||||
this._deviceMaxTextureSize = this._gl.getParameter(this._gl.MAX_TEXTURE_SIZE);
|
this._deviceMaxTextureSize = this._gl.getParameter(this._gl.MAX_TEXTURE_SIZE);
|
||||||
|
|
||||||
this._register(addDisposableListener(this._canvas, 'webglcontextlost', (e) => {
|
this._register(addDisposableListener(this._canvas, 'webglcontextlost', (e) => {
|
||||||
this._logService.debug('webglcontextlost event received');
|
console.log('webglcontextlost event received');
|
||||||
// Prevent the default behavior in order to enable WebGL context restoration.
|
// Prevent the default behavior in order to enable WebGL context restoration.
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
// Wait a few seconds to see if the 'webglcontextrestored' event is fired.
|
// Wait a few seconds to see if the 'webglcontextrestored' event is fired.
|
||||||
// If not, dispatch the onContextLoss notification to observers.
|
// If not, dispatch the onContextLoss notification to observers.
|
||||||
this._contextRestorationTimeout = setTimeout(() => {
|
this._contextRestorationTimeout = setTimeout(() => {
|
||||||
this._contextRestorationTimeout = undefined;
|
this._contextRestorationTimeout = undefined;
|
||||||
this._logService.warn('webgl context not restored; firing onContextLoss');
|
console.warn('webgl context not restored; firing onContextLoss');
|
||||||
this._onContextLoss.fire(e);
|
this._onContextLoss.fire(e);
|
||||||
}, 3000 /* ms */);
|
}, 3000 /* ms */);
|
||||||
}));
|
}));
|
||||||
this._register(addDisposableListener(this._canvas, 'webglcontextrestored', (e) => {
|
this._register(addDisposableListener(this._canvas, 'webglcontextrestored', (e) => {
|
||||||
this._logService.warn('webglcontextrestored event received');
|
console.warn('webglcontextrestored event received');
|
||||||
clearTimeout(this._contextRestorationTimeout);
|
clearTimeout(this._contextRestorationTimeout);
|
||||||
this._contextRestorationTimeout = undefined;
|
this._contextRestorationTimeout = undefined;
|
||||||
// The texture atlas and glyph renderer must be fully reinitialized
|
// The texture atlas and glyph renderer must be fully reinitialized
|
||||||
@@ -159,8 +154,6 @@ export class WebglRenderer extends Disposable implements IRenderer {
|
|||||||
this._isAttached = this._core.screenElement!.isConnected;
|
this._isAttached = this._core.screenElement!.isConnected;
|
||||||
|
|
||||||
this._register(toDisposable(() => {
|
this._register(toDisposable(() => {
|
||||||
clearTimeout(this._contextRestorationTimeout);
|
|
||||||
this._contextRestorationTimeout = undefined;
|
|
||||||
for (const l of this._renderLayers) {
|
for (const l of this._renderLayers) {
|
||||||
l.dispose();
|
l.dispose();
|
||||||
}
|
}
|
||||||
@@ -277,8 +270,8 @@ export class WebglRenderer extends Disposable implements IRenderer {
|
|||||||
* Initializes members dependent on WebGL context state.
|
* Initializes members dependent on WebGL context state.
|
||||||
*/
|
*/
|
||||||
private _initializeWebGLState(): [RectangleRenderer, GlyphRenderer] {
|
private _initializeWebGLState(): [RectangleRenderer, GlyphRenderer] {
|
||||||
this._rectangleRenderer.value = new RectangleRenderer(this._terminal, this._gl, this.dimensions, this._themeService, this._logService);
|
this._rectangleRenderer.value = new RectangleRenderer(this._terminal, this._gl, this.dimensions, this._themeService);
|
||||||
this._glyphRenderer.value = new GlyphRenderer(this._terminal, this._gl, this.dimensions, this._optionsService, this._logService);
|
this._glyphRenderer.value = new GlyphRenderer(this._terminal, this._gl, this.dimensions, this._optionsService);
|
||||||
|
|
||||||
// Update dimensions and acquire char atlas
|
// Update dimensions and acquire char atlas
|
||||||
this.handleCharSizeChanged();
|
this.handleCharSizeChanged();
|
||||||
@@ -382,19 +375,6 @@ export class WebglRenderer extends Disposable implements IRenderer {
|
|||||||
this._updateModel(start, end);
|
this._updateModel(start, end);
|
||||||
}
|
}
|
||||||
|
|
||||||
// A mid-update atlas page merge invalidates vertex data and may not bump the host
|
|
||||||
// page's version, so re-run the update and force a full texture rebind.
|
|
||||||
let merged = false;
|
|
||||||
let mergeRetries = 0;
|
|
||||||
while (this._charAtlas && this._glyphRenderer.value.beginFrame() && mergeRetries++ < Constants.MERGE_RETRY_LIMIT) {
|
|
||||||
merged = true;
|
|
||||||
this._clearModel(true);
|
|
||||||
this._updateModel(0, this._terminal.rows - 1);
|
|
||||||
}
|
|
||||||
if (merged) {
|
|
||||||
this._glyphRenderer.value.invalidateAtlasTextures();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Render
|
// Render
|
||||||
this._rectangleRenderer.value.renderBackgrounds();
|
this._rectangleRenderer.value.renderBackgrounds();
|
||||||
this._glyphRenderer.value.render(this._model);
|
this._glyphRenderer.value.render(this._model);
|
||||||
@@ -427,8 +407,8 @@ export class WebglRenderer extends Disposable implements IRenderer {
|
|||||||
let line: IBufferLine;
|
let line: IBufferLine;
|
||||||
let joinedRanges: [number, number][];
|
let joinedRanges: [number, number][];
|
||||||
let isJoined: boolean;
|
let isJoined: boolean;
|
||||||
let skipJoinedCheckUntilX: number;
|
let skipJoinedCheckUntilX: number = 0;
|
||||||
let isValidJoinRange: boolean;
|
let isValidJoinRange: boolean = true;
|
||||||
let lastCharX: number;
|
let lastCharX: number;
|
||||||
let range: [number, number];
|
let range: [number, number];
|
||||||
let isCursorRow: boolean;
|
let isCursorRow: boolean;
|
||||||
|
|||||||
@@ -3,8 +3,7 @@
|
|||||||
* @license MIT
|
* @license MIT
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { throwIfFalsy } from 'target-browser/renderer/shared/RendererUtils';
|
import { throwIfFalsy } from 'browser/renderer/shared/RendererUtils';
|
||||||
import type { ILogService } from 'common/services/Services';
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A matrix that when multiplies will translate 0-1 coordinates (left to right,
|
* A matrix that when multiplies will translate 0-1 coordinates (left to right,
|
||||||
@@ -17,21 +16,21 @@ export const PROJECTION_MATRIX = new Float32Array([
|
|||||||
-1, 1, 0, 1
|
-1, 1, 0, 1
|
||||||
]);
|
]);
|
||||||
|
|
||||||
export function createProgram(gl: WebGLRenderingContext, vertexSource: string, fragmentSource: string, logService: ILogService): WebGLProgram | undefined {
|
export function createProgram(gl: WebGLRenderingContext, vertexSource: string, fragmentSource: string): WebGLProgram | undefined {
|
||||||
const program = throwIfFalsy(gl.createProgram());
|
const program = throwIfFalsy(gl.createProgram());
|
||||||
gl.attachShader(program, throwIfFalsy(createShader(gl, gl.VERTEX_SHADER, vertexSource, logService)));
|
gl.attachShader(program, throwIfFalsy(createShader(gl, gl.VERTEX_SHADER, vertexSource)));
|
||||||
gl.attachShader(program, throwIfFalsy(createShader(gl, gl.FRAGMENT_SHADER, fragmentSource, logService)));
|
gl.attachShader(program, throwIfFalsy(createShader(gl, gl.FRAGMENT_SHADER, fragmentSource)));
|
||||||
gl.linkProgram(program);
|
gl.linkProgram(program);
|
||||||
const success = gl.getProgramParameter(program, gl.LINK_STATUS);
|
const success = gl.getProgramParameter(program, gl.LINK_STATUS);
|
||||||
if (success) {
|
if (success) {
|
||||||
return program;
|
return program;
|
||||||
}
|
}
|
||||||
|
|
||||||
logService.error(gl.getProgramInfoLog(program));
|
console.error(gl.getProgramInfoLog(program));
|
||||||
gl.deleteProgram(program);
|
gl.deleteProgram(program);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function createShader(gl: WebGLRenderingContext, type: number, source: string, logService: ILogService): WebGLShader | undefined {
|
export function createShader(gl: WebGLRenderingContext, type: number, source: string): WebGLShader | undefined {
|
||||||
const shader = throwIfFalsy(gl.createShader(type));
|
const shader = throwIfFalsy(gl.createShader(type));
|
||||||
gl.shaderSource(shader, source);
|
gl.shaderSource(shader, source);
|
||||||
gl.compileShader(shader);
|
gl.compileShader(shader);
|
||||||
@@ -40,7 +39,7 @@ export function createShader(gl: WebGLRenderingContext, type: number, source: st
|
|||||||
return shader;
|
return shader;
|
||||||
}
|
}
|
||||||
|
|
||||||
logService.error(gl.getShaderInfoLog(shader));
|
console.error(gl.getShaderInfoLog(shader));
|
||||||
gl.deleteShader(shader);
|
gl.deleteShader(shader);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -3,8 +3,7 @@
|
|||||||
* @license MIT
|
* @license MIT
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { throwIfFalsy } from 'target-browser/renderer/shared/RendererUtils';
|
import { throwIfFalsy } from 'browser/renderer/shared/RendererUtils';
|
||||||
import type { ILogService } from 'common/services/Services';
|
|
||||||
import { customGlyphDefinitions } from './CustomGlyphDefinitions';
|
import { customGlyphDefinitions } from './CustomGlyphDefinitions';
|
||||||
import { CustomGlyphDefinitionType, CustomGlyphScaleType, CustomGlyphVectorType, type CustomGlyphDefinitionPart, type CustomGlyphPathDrawFunctionDefinition, type CustomGlyphPatternDefinition, type ICustomGlyphSolidOctantBlockVector, type ICustomGlyphVectorShape } from './Types';
|
import { CustomGlyphDefinitionType, CustomGlyphScaleType, CustomGlyphVectorType, type CustomGlyphDefinitionPart, type CustomGlyphPathDrawFunctionDefinition, type CustomGlyphPatternDefinition, type ICustomGlyphSolidOctantBlockVector, type ICustomGlyphVectorShape } from './Types';
|
||||||
|
|
||||||
@@ -23,7 +22,6 @@ export function tryDrawCustomGlyph(
|
|||||||
deviceCharHeight: number,
|
deviceCharHeight: number,
|
||||||
fontSize: number,
|
fontSize: number,
|
||||||
devicePixelRatio: number,
|
devicePixelRatio: number,
|
||||||
logService: ILogService,
|
|
||||||
backgroundColor?: string,
|
backgroundColor?: string,
|
||||||
variantOffset: number = 0
|
variantOffset: number = 0
|
||||||
): boolean {
|
): boolean {
|
||||||
@@ -32,7 +30,7 @@ export function tryDrawCustomGlyph(
|
|||||||
// Normalize to array for uniform handling
|
// Normalize to array for uniform handling
|
||||||
const parts = Array.isArray(unifiedCharDefinition) ? unifiedCharDefinition : [unifiedCharDefinition];
|
const parts = Array.isArray(unifiedCharDefinition) ? unifiedCharDefinition : [unifiedCharDefinition];
|
||||||
for (const part of parts) {
|
for (const part of parts) {
|
||||||
drawDefinitionPart(ctx, part, xOffset, yOffset, deviceCellWidth, deviceCellHeight, deviceCharWidth, deviceCharHeight, fontSize, devicePixelRatio, logService, backgroundColor, variantOffset);
|
drawDefinitionPart(ctx, part, xOffset, yOffset, deviceCellWidth, deviceCellHeight, deviceCharWidth, deviceCharHeight, fontSize, devicePixelRatio, backgroundColor, variantOffset);
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@@ -51,7 +49,6 @@ function drawDefinitionPart(
|
|||||||
deviceCharHeight: number,
|
deviceCharHeight: number,
|
||||||
fontSize: number,
|
fontSize: number,
|
||||||
devicePixelRatio: number,
|
devicePixelRatio: number,
|
||||||
logService: ILogService,
|
|
||||||
backgroundColor?: string,
|
backgroundColor?: string,
|
||||||
variantOffset: number = 0
|
variantOffset: number = 0
|
||||||
): void {
|
): void {
|
||||||
@@ -82,7 +79,7 @@ function drawDefinitionPart(
|
|||||||
drawPatternChar(ctx, part.data, drawXOffset, drawYOffset, drawWidth, drawHeight, variantOffset);
|
drawPatternChar(ctx, part.data, drawXOffset, drawYOffset, drawWidth, drawHeight, variantOffset);
|
||||||
break;
|
break;
|
||||||
case CustomGlyphDefinitionType.PATH_FUNCTION:
|
case CustomGlyphDefinitionType.PATH_FUNCTION:
|
||||||
drawPathFunctionCharacter(ctx, part.data, drawXOffset, drawYOffset, drawWidth, drawHeight, devicePixelRatio, logService, part.strokeWidth);
|
drawPathFunctionCharacter(ctx, part.data, drawXOffset, drawYOffset, drawWidth, drawHeight, devicePixelRatio, part.strokeWidth);
|
||||||
break;
|
break;
|
||||||
case CustomGlyphDefinitionType.PATH:
|
case CustomGlyphDefinitionType.PATH:
|
||||||
drawPathDefinitionCharacter(ctx, part.data, drawXOffset, drawYOffset, drawWidth, drawHeight, devicePixelRatio, part.strokeWidth);
|
drawPathDefinitionCharacter(ctx, part.data, drawXOffset, drawYOffset, drawWidth, drawHeight, devicePixelRatio, part.strokeWidth);
|
||||||
@@ -91,7 +88,7 @@ function drawDefinitionPart(
|
|||||||
drawPathNegativeDefinitionCharacter(ctx, part.data, drawXOffset, drawYOffset, drawWidth, drawHeight, devicePixelRatio, backgroundColor);
|
drawPathNegativeDefinitionCharacter(ctx, part.data, drawXOffset, drawYOffset, drawWidth, drawHeight, devicePixelRatio, backgroundColor);
|
||||||
break;
|
break;
|
||||||
case CustomGlyphDefinitionType.VECTOR_SHAPE:
|
case CustomGlyphDefinitionType.VECTOR_SHAPE:
|
||||||
drawVectorShape(ctx, part.data, drawXOffset, drawYOffset, drawWidth, drawHeight, fontSize, devicePixelRatio, logService);
|
drawVectorShape(ctx, part.data, drawXOffset, drawYOffset, drawWidth, drawHeight, fontSize, devicePixelRatio);
|
||||||
break;
|
break;
|
||||||
case CustomGlyphDefinitionType.BRAILLE:
|
case CustomGlyphDefinitionType.BRAILLE:
|
||||||
drawBrailleCharacter(ctx, part.data, drawXOffset, drawYOffset, drawWidth, drawHeight);
|
drawBrailleCharacter(ctx, part.data, drawXOffset, drawYOffset, drawWidth, drawHeight);
|
||||||
@@ -513,7 +510,6 @@ function drawPathFunctionCharacter(
|
|||||||
deviceCellWidth: number,
|
deviceCellWidth: number,
|
||||||
deviceCellHeight: number,
|
deviceCellHeight: number,
|
||||||
devicePixelRatio: number,
|
devicePixelRatio: number,
|
||||||
logService: ILogService,
|
|
||||||
strokeWidth?: number
|
strokeWidth?: number
|
||||||
): void {
|
): void {
|
||||||
ctx.save();
|
ctx.save();
|
||||||
@@ -540,7 +536,7 @@ function drawPathFunctionCharacter(
|
|||||||
}
|
}
|
||||||
const f = svgToCanvasInstructionMap[type];
|
const f = svgToCanvasInstructionMap[type];
|
||||||
if (!f) {
|
if (!f) {
|
||||||
logService.error(`Could not find drawing instructions for "${type}"`);
|
console.error(`Could not find drawing instructions for "${type}"`);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
const args: string[] = instruction.substring(1).split(',');
|
const args: string[] = instruction.substring(1).split(',');
|
||||||
@@ -602,8 +598,7 @@ function drawVectorShape(
|
|||||||
deviceCellWidth: number,
|
deviceCellWidth: number,
|
||||||
deviceCellHeight: number,
|
deviceCellHeight: number,
|
||||||
fontSize: number,
|
fontSize: number,
|
||||||
devicePixelRatio: number,
|
devicePixelRatio: number
|
||||||
logService: ILogService
|
|
||||||
): void {
|
): void {
|
||||||
// Clip the cell to make sure drawing doesn't occur beyond bounds
|
// Clip the cell to make sure drawing doesn't occur beyond bounds
|
||||||
const clipRegion = new Path2D();
|
const clipRegion = new Path2D();
|
||||||
@@ -624,7 +619,7 @@ function drawVectorShape(
|
|||||||
}
|
}
|
||||||
const f = svgToCanvasInstructionMap[type];
|
const f = svgToCanvasInstructionMap[type];
|
||||||
if (!f) {
|
if (!f) {
|
||||||
logService.error(`Could not find drawing instructions for "${type}"`);
|
console.error(`Could not find drawing instructions for "${type}"`);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
const args: string[] = instruction.substring(1).split(',');
|
const args: string[] = instruction.substring(1).split(',');
|
||||||
|
|||||||
@@ -3,16 +3,16 @@
|
|||||||
* @license MIT
|
* @license MIT
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { ReadonlyColorSet } from 'target-browser/Types';
|
import { ReadonlyColorSet } from 'browser/Types';
|
||||||
import { acquireTextureAtlas } from '../CharAtlasCache';
|
import { acquireTextureAtlas } from '../CharAtlasCache';
|
||||||
import { IRenderDimensions } from 'target-browser/renderer/shared/Types';
|
import { IRenderDimensions } from 'browser/renderer/shared/Types';
|
||||||
import { ICoreBrowserService, IThemeService } from 'target-browser/services/Services';
|
import { ICoreBrowserService, IThemeService } from 'browser/services/Services';
|
||||||
import { Disposable, toDisposable } from 'common/Lifecycle';
|
import { Disposable, toDisposable } from 'common/Lifecycle';
|
||||||
import { CellData } from 'common/buffer/CellData';
|
import { CellData } from 'common/buffer/CellData';
|
||||||
import { IOptionsService } from 'common/services/Services';
|
import { IOptionsService } from 'common/services/Services';
|
||||||
import { Terminal } from '@xterm/xterm';
|
import { Terminal } from '@xterm/xterm';
|
||||||
import { IRenderLayer } from './Types';
|
import { IRenderLayer } from './Types';
|
||||||
import { throwIfFalsy } from 'target-browser/renderer/shared/RendererUtils';
|
import { throwIfFalsy } from 'browser/renderer/shared/RendererUtils';
|
||||||
import { TEXT_BASELINE } from '../Constants';
|
import { TEXT_BASELINE } from '../Constants';
|
||||||
import type { ITextureAtlas } from '../Types';
|
import type { ITextureAtlas } from '../Types';
|
||||||
|
|
||||||
|
|||||||
@@ -4,10 +4,10 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import { is256Color } from '../CharAtlasUtils';
|
import { is256Color } from '../CharAtlasUtils';
|
||||||
import { INVERTED_DEFAULT_COLOR } from 'target-browser/renderer/shared/Constants';
|
import { INVERTED_DEFAULT_COLOR } from 'browser/renderer/shared/Constants';
|
||||||
import { IRenderDimensions } from 'target-browser/renderer/shared/Types';
|
import { IRenderDimensions } from 'browser/renderer/shared/Types';
|
||||||
import { ICoreBrowserService, IThemeService } from 'target-browser/services/Services';
|
import { ICoreBrowserService, IThemeService } from 'browser/services/Services';
|
||||||
import { ILinkifier2, ILinkifierEvent } from 'target-browser/Types';
|
import { ILinkifier2, ILinkifierEvent } from 'browser/Types';
|
||||||
import { IOptionsService } from 'common/services/Services';
|
import { IOptionsService } from 'common/services/Services';
|
||||||
import { Terminal } from '@xterm/xterm';
|
import { Terminal } from '@xterm/xterm';
|
||||||
import { BaseRenderLayer } from './BaseRenderLayer';
|
import { BaseRenderLayer } from './BaseRenderLayer';
|
||||||
|
|||||||
@@ -4,7 +4,7 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import { IDisposable, Terminal } from '@xterm/xterm';
|
import { IDisposable, Terminal } from '@xterm/xterm';
|
||||||
import { IRenderDimensions } from 'target-browser/renderer/shared/Types';
|
import { IRenderDimensions } from 'browser/renderer/shared/Types';
|
||||||
|
|
||||||
export interface IRenderLayer extends IDisposable {
|
export interface IRenderLayer extends IDisposable {
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -12,11 +12,10 @@
|
|||||||
"removeComments": true,
|
"removeComments": true,
|
||||||
"paths": {
|
"paths": {
|
||||||
"common/*": [
|
"common/*": [
|
||||||
"../../../src/primitives/*",
|
"../../../src/common/*"
|
||||||
"../../../src/runtime/*",
|
|
||||||
],
|
],
|
||||||
"target-browser/*": [
|
"browser/*": [
|
||||||
"../../../src/target-browser/*"
|
"../../../src/browser/*"
|
||||||
],
|
],
|
||||||
"@xterm/addon-webgl": [
|
"@xterm/addon-webgl": [
|
||||||
"../typings/addon-webgl.d.ts"
|
"../typings/addon-webgl.d.ts"
|
||||||
@@ -26,6 +25,7 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
"strict": true,
|
"strict": true,
|
||||||
|
"downlevelIteration": true,
|
||||||
"experimentalDecorators": true,
|
"experimentalDecorators": true,
|
||||||
"types": [
|
"types": [
|
||||||
"../../../node_modules/@types/mocha"
|
"../../../node_modules/@types/mocha"
|
||||||
@@ -37,10 +37,10 @@
|
|||||||
],
|
],
|
||||||
"references": [
|
"references": [
|
||||||
{
|
{
|
||||||
"path": "../../../src"
|
"path": "../../../src/common"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"path": "../../../src/target-browser"
|
"path": "../../../src/browser"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,11 +11,10 @@
|
|||||||
"removeComments": true,
|
"removeComments": true,
|
||||||
"paths": {
|
"paths": {
|
||||||
"common/*": [
|
"common/*": [
|
||||||
"../../../src/primitives/*",
|
"../../../src/common/*"
|
||||||
"../../../src/runtime/*",
|
|
||||||
],
|
],
|
||||||
"target-browser/*": [
|
"browser/*": [
|
||||||
"../../../src/target-browser/*"
|
"../../../src/browser/*"
|
||||||
],
|
],
|
||||||
"*": [
|
"*": [
|
||||||
"./*"
|
"./*"
|
||||||
@@ -33,10 +32,10 @@
|
|||||||
],
|
],
|
||||||
"references": [
|
"references": [
|
||||||
{
|
{
|
||||||
"path": "../../../src"
|
"path": "../../../src/common"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"path": "../../../src/target-browser"
|
"path": "../../../src/browser"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"path": "../../../test/playwright"
|
"path": "../../../test/playwright"
|
||||||
|
|||||||
@@ -25,11 +25,8 @@ module.exports = {
|
|||||||
modules: ['./node_modules'],
|
modules: ['./node_modules'],
|
||||||
extensions: [ '.js' ],
|
extensions: [ '.js' ],
|
||||||
alias: {
|
alias: {
|
||||||
common: [
|
common: path.resolve('../../out/common'),
|
||||||
path.resolve('../../out/runtime'),
|
browser: path.resolve('../../out/browser'),
|
||||||
path.resolve('../../out/primitives'),
|
|
||||||
],
|
|
||||||
browser: path.resolve('../../out/target-browser'),
|
|
||||||
vs: path.resolve('../../out/vs')
|
vs: path.resolve('../../out/vs')
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|||||||
+13
-37
@@ -92,9 +92,6 @@ let outConfig = {
|
|||||||
}
|
}
|
||||||
let skipOut = false;
|
let skipOut = false;
|
||||||
|
|
||||||
/** @type {esbuild.BuildOptions[] | undefined} */
|
|
||||||
let outBuilds;
|
|
||||||
|
|
||||||
/** @type {esbuild.BuildOptions} */
|
/** @type {esbuild.BuildOptions} */
|
||||||
let outTestConfig = {
|
let outTestConfig = {
|
||||||
format: 'cjs',
|
format: 'cjs',
|
||||||
@@ -174,7 +171,7 @@ if (config.addon) {
|
|||||||
} else if (config.isHeadless) {
|
} else if (config.isHeadless) {
|
||||||
bundleConfig = {
|
bundleConfig = {
|
||||||
...bundleConfig,
|
...bundleConfig,
|
||||||
entryPoints: [`src/target-headless/public/Terminal.ts`],
|
entryPoints: [`src/headless/public/Terminal.ts`],
|
||||||
outfile: `headless/lib-headless/xterm-headless.mjs`
|
outfile: `headless/lib-headless/xterm-headless.mjs`
|
||||||
};
|
};
|
||||||
outConfig = {
|
outConfig = {
|
||||||
@@ -186,35 +183,18 @@ if (config.addon) {
|
|||||||
} else {
|
} else {
|
||||||
bundleConfig = {
|
bundleConfig = {
|
||||||
...bundleConfig,
|
...bundleConfig,
|
||||||
entryPoints: [`src/target-browser/public/Terminal.ts`],
|
entryPoints: [`src/browser/public/Terminal.ts`],
|
||||||
outfile: `lib/xterm.mjs`
|
outfile: `lib/xterm.mjs`
|
||||||
};
|
};
|
||||||
outBuilds = [
|
outConfig = {
|
||||||
{
|
...outConfig,
|
||||||
...outConfig,
|
entryPoints: [
|
||||||
entryPoints: ['src/primitives/**/*.ts'],
|
'src/browser/**/*.ts',
|
||||||
outbase: 'src/primitives',
|
'src/common/**/*.ts',
|
||||||
outdir: 'out-esbuild/common',
|
'src/headless/**/*.ts'
|
||||||
},
|
],
|
||||||
{
|
outdir: 'out-esbuild/'
|
||||||
...outConfig,
|
};
|
||||||
entryPoints: ['src/runtime/**/*.ts'],
|
|
||||||
outbase: 'src/runtime',
|
|
||||||
outdir: 'out-esbuild/common',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
...outConfig,
|
|
||||||
entryPoints: ['src/target-browser/**/*.ts'],
|
|
||||||
outbase: 'src',
|
|
||||||
outdir: 'out-esbuild',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
...outConfig,
|
|
||||||
entryPoints: ['src/target-headless/**/*.ts'],
|
|
||||||
outbase: 'src',
|
|
||||||
outdir: 'out-esbuild',
|
|
||||||
},
|
|
||||||
];
|
|
||||||
outTestConfig = {
|
outTestConfig = {
|
||||||
...outConfig,
|
...outConfig,
|
||||||
entryPoints: ['test/**/*.ts'],
|
entryPoints: ['test/**/*.ts'],
|
||||||
@@ -225,9 +205,7 @@ if (config.addon) {
|
|||||||
if (config.isWatch) {
|
if (config.isWatch) {
|
||||||
context(bundleConfig).then(e => e.watch());
|
context(bundleConfig).then(e => e.watch());
|
||||||
if (!skipOut) {
|
if (!skipOut) {
|
||||||
for (const buildConfig of outBuilds ?? [outConfig]) {
|
context(outConfig).then(e => e.watch());
|
||||||
context(buildConfig).then(e => e.watch());
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
if (!skipOutTest) {
|
if (!skipOutTest) {
|
||||||
context(outTestConfig).then(e => e.watch());
|
context(outTestConfig).then(e => e.watch());
|
||||||
@@ -235,9 +213,7 @@ if (config.isWatch) {
|
|||||||
} else {
|
} else {
|
||||||
await build(bundleConfig);
|
await build(bundleConfig);
|
||||||
if (!skipOut) {
|
if (!skipOut) {
|
||||||
for (const buildConfig of outBuilds ?? [outConfig]) {
|
await build(outConfig);
|
||||||
await build(buildConfig);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
if (!skipOutTest) {
|
if (!skipOutTest) {
|
||||||
await build(outTestConfig);
|
await build(outTestConfig);
|
||||||
|
|||||||
@@ -0,0 +1,196 @@
|
|||||||
|
from termios import tcgetattr, tcsetattr, TCSADRAIN, TIOCGWINSZ, TCSAFLUSH
|
||||||
|
from tty import setcbreak, setraw
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
from contextlib import contextmanager
|
||||||
|
from select import select
|
||||||
|
from time import sleep
|
||||||
|
from json import dumps, loads
|
||||||
|
from typing import Optional, Tuple, Union
|
||||||
|
|
||||||
|
|
||||||
|
class TerminalContext:
|
||||||
|
def __init__(self, fd: int, close_fd=False) -> None:
|
||||||
|
if not os.isatty(fd):
|
||||||
|
raise TypeError('fd is not a terminal')
|
||||||
|
self.close_fd = close_fd
|
||||||
|
self.fd = fd
|
||||||
|
self.is_cbreak = False
|
||||||
|
self.is_raw = False
|
||||||
|
self._initial_attr = self.termios_attributes
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def from_cterm(cls):
|
||||||
|
fd = os.open(os.ctermid(), os.O_RDWR)
|
||||||
|
return TerminalContext(fd, True)
|
||||||
|
|
||||||
|
def close(self) -> None:
|
||||||
|
tcsetattr(self.fd, TCSADRAIN, self._initial_attr)
|
||||||
|
if self.close_fd:
|
||||||
|
os.close(self.fd)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def termios_attributes(self) -> list:
|
||||||
|
return tcgetattr(self.fd)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def ttyname(self) -> str:
|
||||||
|
return os.ttyname(self.fd)
|
||||||
|
|
||||||
|
@contextmanager
|
||||||
|
def cbreak_mode(self):
|
||||||
|
"""
|
||||||
|
Enter cbreak mode context.
|
||||||
|
"""
|
||||||
|
if self.is_cbreak:
|
||||||
|
yield
|
||||||
|
return
|
||||||
|
tattr = self.termios_attributes
|
||||||
|
try:
|
||||||
|
setcbreak(self.fd, TCSADRAIN)
|
||||||
|
self.is_cbreak = True
|
||||||
|
yield
|
||||||
|
finally:
|
||||||
|
tcsetattr(self.fd, TCSADRAIN, tattr)
|
||||||
|
self.is_cbreak = False
|
||||||
|
|
||||||
|
@contextmanager
|
||||||
|
def raw_mode(self):
|
||||||
|
"""
|
||||||
|
Enter cbreak mode context.
|
||||||
|
"""
|
||||||
|
if self.is_raw:
|
||||||
|
yield
|
||||||
|
return
|
||||||
|
tattr = self.termios_attributes
|
||||||
|
try:
|
||||||
|
setraw(self.fd, TCSADRAIN)
|
||||||
|
self.is_raw = True
|
||||||
|
yield
|
||||||
|
finally:
|
||||||
|
tcsetattr(self.fd, TCSADRAIN, tattr)
|
||||||
|
self.is_raw = False
|
||||||
|
|
||||||
|
@contextmanager
|
||||||
|
def custom_state(self, undo=None):
|
||||||
|
"""
|
||||||
|
Enter custom terminal state, that needs to to be undone by ``undo``.
|
||||||
|
Useful, if you want to apply a custom terminal state and
|
||||||
|
have to make sure, that it gets properly reset to previous state.
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
yield
|
||||||
|
finally:
|
||||||
|
if undo:
|
||||||
|
undo()
|
||||||
|
|
||||||
|
def write(self, data: Union[str, bytes]) -> None:
|
||||||
|
"""
|
||||||
|
Write string or bytes directly to the terminal.
|
||||||
|
"""
|
||||||
|
if isinstance(data, str):
|
||||||
|
data = data.encode('utf-8')
|
||||||
|
sent = os.write(self.fd, data)
|
||||||
|
while sent:
|
||||||
|
data = data[sent:]
|
||||||
|
sent = os.write(self.fd, data)
|
||||||
|
|
||||||
|
def read(self, amount: int = 1024, timeout: Optional[float] = None) -> bytes:
|
||||||
|
"""
|
||||||
|
Blocking read from the terminal.
|
||||||
|
If nothing was sent from the terminal within ``timeout``,
|
||||||
|
empty bytes are returned.
|
||||||
|
"""
|
||||||
|
can_read, _, _ = select([self.fd], [], [], timeout)
|
||||||
|
if can_read:
|
||||||
|
return os.read(self.fd, amount)
|
||||||
|
return b''
|
||||||
|
|
||||||
|
|
||||||
|
@contextmanager
|
||||||
|
def cterminal_context():
|
||||||
|
t = TerminalContext.from_cterm()
|
||||||
|
try:
|
||||||
|
yield t
|
||||||
|
finally:
|
||||||
|
t.close()
|
||||||
|
|
||||||
|
|
||||||
|
def extract_events(data: list[str]):
|
||||||
|
if len(data) < 5:
|
||||||
|
raise Exception('not enough reports')
|
||||||
|
types = set(data)
|
||||||
|
if len(types) == 1:
|
||||||
|
return {'PRESS': data[0], 'REPEAT': data[0], 'RELEASE': None}
|
||||||
|
if len(types) == 2:
|
||||||
|
last = data.pop()
|
||||||
|
if last != data[0] and len(set(data)) == 1:
|
||||||
|
return {'PRESS': data[0], 'REPEAT': data[0], 'RELEASE': last}
|
||||||
|
raise Exception('weird reports, 2 types')
|
||||||
|
if len(types) == 3:
|
||||||
|
first = data.pop(0)
|
||||||
|
last = data.pop()
|
||||||
|
if first != last and first != data[0] and last != data[0]:
|
||||||
|
return {'PRESS': first, 'REPEAT': data[0], 'RELEASE': last}
|
||||||
|
raise Exception('weird reports, 3 types')
|
||||||
|
raise Exception('more than 3 types')
|
||||||
|
|
||||||
|
|
||||||
|
def query(term: TerminalContext, mode: int):
|
||||||
|
with term.custom_state(undo=lambda:term.write('\x1b[<u')):
|
||||||
|
term.write(f'\x1b[>{mode}u')
|
||||||
|
term.read(timeout=.1)
|
||||||
|
print('PRESS (within 5s) and HOLD (for 5s)\r')
|
||||||
|
data: list[bytes] = []
|
||||||
|
cur = term.read(timeout=5)
|
||||||
|
while cur:
|
||||||
|
data.append(cur)
|
||||||
|
cur = term.read(timeout=.5)
|
||||||
|
if len(data) > 5:
|
||||||
|
print('RELEASE\r', end='')
|
||||||
|
try:
|
||||||
|
return extract_events([b.decode('utf-8') for b in data])
|
||||||
|
except Exception as e:
|
||||||
|
print('Error:', e, '\r')
|
||||||
|
print(data, '\r')
|
||||||
|
term.read(timeout=.1)
|
||||||
|
|
||||||
|
|
||||||
|
def save(filedata: dict[str, dict[str, str]], filename, entry, mode, events):
|
||||||
|
try:
|
||||||
|
filedata[entry][mode] = events
|
||||||
|
except KeyError:
|
||||||
|
filedata[entry] = {mode: events}
|
||||||
|
with open(filename, 'w') as f:
|
||||||
|
f.write(dumps(filedata, indent=2))
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
mode = 1
|
||||||
|
filedata = {}
|
||||||
|
print(sys.argv)
|
||||||
|
if len(sys.argv) != 3:
|
||||||
|
print('ERROR: not enough arguments')
|
||||||
|
print('Usage: python kitty_keytester.py <mode> <json-file>')
|
||||||
|
return
|
||||||
|
mode = int(sys.argv[1])
|
||||||
|
filename = sys.argv[2]
|
||||||
|
if os.path.exists(filename):
|
||||||
|
with open(filename) as f:
|
||||||
|
filedata = loads(f.read())
|
||||||
|
with cterminal_context() as term:
|
||||||
|
while True:
|
||||||
|
with term.raw_mode():
|
||||||
|
events = query(term, mode)
|
||||||
|
if events:
|
||||||
|
print(events)
|
||||||
|
entry = input('Entry: ')
|
||||||
|
if entry:
|
||||||
|
save(filedata, filename, entry, str(mode), events)
|
||||||
|
cont = input('Continue? (y)')
|
||||||
|
if cont not in ['y', '']:
|
||||||
|
break
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
main()
|
||||||
+1
-1
@@ -81,7 +81,7 @@ function checkAndPublishPackage(packageDir, repoCommit, peerDependencies, update
|
|||||||
|
|
||||||
// Update Version.ts with the new version
|
// Update Version.ts with the new version
|
||||||
if (updateVersionTs) {
|
if (updateVersionTs) {
|
||||||
const versionTsPath = path.join(packageDir, 'src/primitives/Version.ts');
|
const versionTsPath = path.join(packageDir, 'src/common/Version.ts');
|
||||||
const versionTsContent = fs.readFileSync(versionTsPath, 'utf8');
|
const versionTsContent = fs.readFileSync(versionTsPath, 'utf8');
|
||||||
const updatedVersionTs = versionTsContent.replace(
|
const updatedVersionTs = versionTsContent.replace(
|
||||||
/export const XTERM_VERSION = '[^']+';/,
|
/export const XTERM_VERSION = '[^']+';/,
|
||||||
|
|||||||
@@ -38,4 +38,4 @@ function findUnusedSymbols(
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Example usage
|
// Example usage
|
||||||
findUnusedSymbols("./src/target-browser/tsconfig.json");
|
findUnusedSymbols("./src/browser/tsconfig.json");
|
||||||
|
|||||||
@@ -65,8 +65,7 @@ export class AddonImageWindow extends BaseWindow implements IControlWindow {
|
|||||||
const dtIip = document.createElement('dt');
|
const dtIip = document.createElement('dt');
|
||||||
dtIip.textContent = 'IIP (iTerm)';
|
dtIip.textContent = 'IIP (iTerm)';
|
||||||
dlIip.appendChild(dtIip);
|
dlIip.appendChild(dtIip);
|
||||||
this._addDdWithButton(dlIip, 'image-demo3', 'palette (png File)');
|
this._addDdWithButton(dlIip, 'image-demo3', 'palette');
|
||||||
this._addDdWithButton(dlIip, 'image-demo4', 'dice (qoi MultipartFile)');
|
|
||||||
container.appendChild(dlIip);
|
container.appendChild(dlIip);
|
||||||
|
|
||||||
// Kitty demos
|
// Kitty demos
|
||||||
@@ -142,21 +141,6 @@ export class AddonImageWindow extends BaseWindow implements IControlWindow {
|
|||||||
this._terminal.write(`\x1b]1337;File=inline=1;size=${data.length}:${btoa(sdata)}\x1b\\`);
|
this._terminal.write(`\x1b]1337;File=inline=1;size=${data.length}:${btoa(sdata)}\x1b\\`);
|
||||||
});
|
});
|
||||||
|
|
||||||
const iipDemoMulti = (url: string) => () => fetch(url)
|
|
||||||
.then(resp => resp.arrayBuffer())
|
|
||||||
.then(buffer => {
|
|
||||||
const data = new Uint8Array(buffer);
|
|
||||||
let sdata = '';
|
|
||||||
for (let i = 0; i < data.length; ++i) sdata += String.fromCharCode(data[i]);
|
|
||||||
const encoded = btoa(sdata);
|
|
||||||
this._terminal.write('\r\n');
|
|
||||||
this._terminal.write(`\x1b]1337;MultipartFile=inline=1\x1b\\`);
|
|
||||||
for (let i = 0; i < encoded.length; i += 100) {
|
|
||||||
this._terminal.write(`\x1b]1337;FilePart=${encoded.slice(i, i + 100)}\x1b\\`);
|
|
||||||
}
|
|
||||||
this._terminal.write(`\x1b]1337;FileEnd\x1b\\`);
|
|
||||||
});
|
|
||||||
|
|
||||||
const kittyDemo = (url: string) => () => fetch(url)
|
const kittyDemo = (url: string) => () => fetch(url)
|
||||||
.then(resp => resp.arrayBuffer())
|
.then(resp => resp.arrayBuffer())
|
||||||
.then(buffer => {
|
.then(buffer => {
|
||||||
@@ -174,8 +158,6 @@ export class AddonImageWindow extends BaseWindow implements IControlWindow {
|
|||||||
sixelDemo('https://raw.githubusercontent.com/jerch/node-sixel/master/testfiles/test2.sixel'));
|
sixelDemo('https://raw.githubusercontent.com/jerch/node-sixel/master/testfiles/test2.sixel'));
|
||||||
document.getElementById('image-demo3')!.addEventListener('click',
|
document.getElementById('image-demo3')!.addEventListener('click',
|
||||||
iipDemo('https://raw.githubusercontent.com/jerch/node-sixel/master/palette.png'));
|
iipDemo('https://raw.githubusercontent.com/jerch/node-sixel/master/palette.png'));
|
||||||
document.getElementById('image-demo4')!.addEventListener('click',
|
|
||||||
iipDemoMulti('https://raw.githubusercontent.com/jerch/xterm-wasm-parts/master/fixtures/qoi/dice.qoi'));
|
|
||||||
document.getElementById('image-demo-kitty1')!.addEventListener('click',
|
document.getElementById('image-demo-kitty1')!.addEventListener('click',
|
||||||
kittyDemo('https://raw.githubusercontent.com/jerch/node-sixel/master/palette.png'));
|
kittyDemo('https://raw.githubusercontent.com/jerch/node-sixel/master/palette.png'));
|
||||||
|
|
||||||
@@ -194,7 +176,7 @@ export class AddonImageWindow extends BaseWindow implements IControlWindow {
|
|||||||
// }
|
// }
|
||||||
// }
|
// }
|
||||||
|
|
||||||
const pos = (this._terminal as any)._core._mouseCoordsService!.getCoords(ev, (this._terminal as any)._core.screenElement!, this._terminal.cols, this._terminal.rows);
|
const pos = (this._terminal as any)._core._mouseService!.getCoords(ev, (this._terminal as any)._core.screenElement!, this._terminal.cols, this._terminal.rows);
|
||||||
const x = pos[0] - 1;
|
const x = pos[0] - 1;
|
||||||
const y = pos[1] - 1;
|
const y = pos[1] - 1;
|
||||||
const canvas = ev.shiftKey
|
const canvas = ev.shiftKey
|
||||||
|
|||||||
@@ -252,11 +252,6 @@ export class OptionsWindow extends BaseWindow implements IControlWindow {
|
|||||||
input.value = 'auto';
|
input.value = 'auto';
|
||||||
this._handlers.updateTerminalSize();
|
this._handlers.updateTerminalSize();
|
||||||
}
|
}
|
||||||
const screenElement = document.querySelector('.xterm-screen');
|
|
||||||
if (screenElement instanceof HTMLElement) {
|
|
||||||
screenElement.style.outline = this._autoResize ? ''
|
|
||||||
: 'solid thin red';
|
|
||||||
}
|
|
||||||
} else if (o === 'theme') {
|
} else if (o === 'theme') {
|
||||||
value = this._getTheme();
|
value = this._getTheme();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,7 +2,6 @@
|
|||||||
"compilerOptions": {
|
"compilerOptions": {
|
||||||
"module": "commonjs",
|
"module": "commonjs",
|
||||||
"target": "es2021",
|
"target": "es2021",
|
||||||
"types": ["node"],
|
|
||||||
"outDir": "../out-demo/client",
|
"outDir": "../out-demo/client",
|
||||||
"rootDir": ".",
|
"rootDir": ".",
|
||||||
"sourceMap": true,
|
"sourceMap": true,
|
||||||
|
|||||||
@@ -118,7 +118,7 @@ function startServer(): void {
|
|||||||
});
|
});
|
||||||
|
|
||||||
appWs.ws('/terminals/:pid', function (ws, req) {
|
appWs.ws('/terminals/:pid', function (ws, req) {
|
||||||
const term = terminals[parseInt(Array.isArray(req.params.pid) ? req.params.pid[0] : req.params.pid)];
|
const term = terminals[parseInt(req.params.pid)];
|
||||||
console.log('Connected to terminal ' + term.pid);
|
console.log('Connected to terminal ' + term.pid);
|
||||||
temporaryDisposable[term.pid].dispose();
|
temporaryDisposable[term.pid].dispose();
|
||||||
delete temporaryDisposable[term.pid];
|
delete temporaryDisposable[term.pid];
|
||||||
|
|||||||
+1
-3
@@ -37,13 +37,12 @@ export default tseslint.config(
|
|||||||
'@stylistic/comma-spacing': ['warn', { before: false, after: true }],
|
'@stylistic/comma-spacing': ['warn', { before: false, after: true }],
|
||||||
'@stylistic/indent': ['warn', 2],
|
'@stylistic/indent': ['warn', 2],
|
||||||
'@stylistic/semi': ['warn', 'always'],
|
'@stylistic/semi': ['warn', 'always'],
|
||||||
'@stylistic/quotes': ['warn', 'single', { allowTemplateLiterals: 'always' }],
|
'@stylistic/quotes': ['warn', 'single', { allowTemplateLiterals: true }],
|
||||||
'@stylistic/member-delimiter-style': ['warn', {
|
'@stylistic/member-delimiter-style': ['warn', {
|
||||||
multiline: { delimiter: 'semi', requireLast: true },
|
multiline: { delimiter: 'semi', requireLast: true },
|
||||||
singleline: { delimiter: 'comma', requireLast: false }
|
singleline: { delimiter: 'comma', requireLast: false }
|
||||||
}],
|
}],
|
||||||
'@stylistic/type-annotation-spacing': 'warn',
|
'@stylistic/type-annotation-spacing': 'warn',
|
||||||
'@stylistic/space-unary-ops': 'warn',
|
|
||||||
|
|
||||||
'@typescript-eslint/array-type': ['warn', { default: 'array', readonly: 'generic' }],
|
'@typescript-eslint/array-type': ['warn', { default: 'array', readonly: 'generic' }],
|
||||||
'@typescript-eslint/consistent-type-assertions': 'warn',
|
'@typescript-eslint/consistent-type-assertions': 'warn',
|
||||||
@@ -86,7 +85,6 @@ export default tseslint.config(
|
|||||||
'eqeqeq': ['warn', 'always'],
|
'eqeqeq': ['warn', 'always'],
|
||||||
'jsdoc/check-alignment': 'warn',
|
'jsdoc/check-alignment': 'warn',
|
||||||
'jsdoc/check-param-names': 'warn',
|
'jsdoc/check-param-names': 'warn',
|
||||||
'jsdoc/multiline-blocks': 'warn',
|
|
||||||
'jsdoc/no-multi-asterisks': 'warn',
|
'jsdoc/no-multi-asterisks': 'warn',
|
||||||
'keyword-spacing': 'warn',
|
'keyword-spacing': 'warn',
|
||||||
'max-len': ['warn', {
|
'max-len': ['warn', {
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ export default tseslint.config(
|
|||||||
rules: {
|
rules: {
|
||||||
'@stylistic/indent': ['warn', 2],
|
'@stylistic/indent': ['warn', 2],
|
||||||
'@stylistic/semi': ['warn', 'always'],
|
'@stylistic/semi': ['warn', 'always'],
|
||||||
'@stylistic/quotes': ['warn', 'single', { allowTemplateLiterals: 'always' }],
|
'@stylistic/quotes': ['warn', 'single', { allowTemplateLiterals: true }],
|
||||||
'@stylistic/member-delimiter-style': ['warn', {
|
'@stylistic/member-delimiter-style': ['warn', {
|
||||||
multiline: { delimiter: 'semi', requireLast: true },
|
multiline: { delimiter: 'semi', requireLast: true },
|
||||||
singleline: { delimiter: 'comma', requireLast: false }
|
singleline: { delimiter: 'comma', requireLast: false }
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user