Compare commits

..
Author SHA1 Message Date
Cursor AgentandDaniel Imms 1040b799ce Disallow single-line brace-style blocks
Set allowSingleLine to false and expand multi-line braces
across the codebase via eslint --fix.

Co-authored-by: Daniel Imms <Tyriar@users.noreply.github.com>
2026-05-26 17:07:35 +00:00
Cursor AgentandDaniel Imms 1c42ae9af4 Add brace-style lint rule and fix violations
Enforce 1tbs brace style with allowSingleLine per #5900.
Auto-fix else/catch brace placement across the codebase and
move a comment in font.ts that blocked else-if formatting.

Co-authored-by: Daniel Imms <Tyriar@users.noreply.github.com>
2026-05-26 16:42:48 +00:00
468 changed files with 5341 additions and 6636 deletions
-1
View File
@@ -24,7 +24,6 @@ test-results/
# Tracing/debug files
.cursor/nal-trace/
.cursor/*.log
# Keep bundled code out of Git
dist/
+4 -19
View File
@@ -2,13 +2,10 @@
## Architecture Overview
**Core Structure**: xterm.js is a multi-target terminal emulator with these main source areas:
- `src/primitives/`: Building blocks (buffer, parser, input, data, utilities); TypeScript project `src/primitives/tsconfig.json`
- `src/runtime/`: Services, `CoreTerminal`, `InputHandler`, public API adapters; TypeScript project `src/runtime/tsconfig.json`
- `src/target-browser/`: Browser terminal with DOM rendering
- `src/target-headless/`: Node terminal without DOM
See `src/ARCHITECTURE.md` for the dependency graph and import-path conventions (`common/*`, `target-browser/*`, `target-headless/*`).
**Core Structure**: xterm.js is a multi-target terminal emulator with three main distributions:
- `src/browser/`: Full-featured browser terminal with DOM rendering
- `src/headless/`: Server-side terminal for Node.js (no DOM)
- `src/common/`: Shared core logic (parsing, buffer management, terminal state)
**Key Classes**:
- `Terminal` (browser/headless): Public API wrapper
@@ -101,15 +98,3 @@ const cell = line?.getCell(0);
## Writing unit tests
- 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.
+5 -6
View File
@@ -11,11 +11,10 @@
"removeComments": true,
"paths": {
"common/*": [
"../../../src/primitives/*",
"../../../src/runtime/*",
"../../../src/common/*"
],
"target-browser/*": [
"../../../src/target-browser/*"
"browser/*": [
"../../../src/browser/*"
],
"*": [
"./*"
@@ -32,10 +31,10 @@
],
"references": [
{
"path": "../../../src"
"path": "../../../src/common"
},
{
"path": "../../../src/target-browser"
"path": "../../../src/browser"
},
{
"path": "../../../test/playwright"
+3 -3
View File
@@ -15,8 +15,8 @@
"../../../node_modules/@types/mocha"
],
"paths": {
"target-browser/*": [
"../../../src/target-browser/*"
"browser/*": [
"../../../src/browser/*"
],
"@xterm/addon-clipboard": [
"../typings/addon-clipboard.d.ts"
@@ -29,7 +29,7 @@
],
"references": [
{
"path": "../../../src/target-browser"
"path": "../../../src/browser"
}
]
}
+5 -6
View File
@@ -11,11 +11,10 @@
"removeComments": true,
"paths": {
"common/*": [
"../../../src/primitives/*",
"../../../src/runtime/*",
"../../../src/common/*"
],
"target-browser/*": [
"../../../src/target-browser/*"
"browser/*": [
"../../../src/browser/*"
],
"*": [
"./*"
@@ -32,10 +31,10 @@
],
"references": [
{
"path": "../../../src"
"path": "../../../src/common"
},
{
"path": "../../../src/target-browser"
"path": "../../../src/browser"
},
{
"path": "../../../test/playwright"
+1 -1
View File
@@ -5,7 +5,7 @@
import type { Terminal, ITerminalAddon, IRenderDimensions } from '@xterm/xterm';
import type { FitAddon as IFitApi } from '@xterm/addon-fit';
import { ViewportConstants } from 'target-browser/shared/Constants';
import { ViewportConstants } from 'browser/shared/Constants';
interface ITerminalDimensions {
/**
+3 -3
View File
@@ -15,8 +15,8 @@
"../../../node_modules/@types/mocha"
],
"paths": {
"target-browser/*": [
"../../../src/target-browser/*"
"browser/*": [
"../../../src/browser/*"
],
"@xterm/addon-fit": [
"../typings/addon-fit.d.ts"
@@ -29,7 +29,7 @@
],
"references": [
{
"path": "../../../src/target-browser"
"path": "../../../src/browser"
}
]
}
+5 -6
View File
@@ -11,11 +11,10 @@
"removeComments": true,
"paths": {
"common/*": [
"../../../src/primitives/*",
"../../../src/runtime/*",
"../../../src/common/*"
],
"target-browser/*": [
"../../../src/target-browser/*"
"browser/*": [
"../../../src/browser/*"
],
"*": [
"./*"
@@ -32,10 +31,10 @@
],
"references": [
{
"path": "../../../src"
"path": "../../../src/common"
},
{
"path": "../../../src/target-browser"
"path": "../../../src/browser"
},
{
"path": "../../../test/playwright"
+31 -20
View File
@@ -1,7 +1,6 @@
## @xterm/addon-image
Inline image output in xterm.js. Supports SIXEL, iTerm's inline image protocol (IIP)
and partially Kitty's terminal graphics prtocol (TGP).
Inline image output in xterm.js. Supports SIXEL and iTerm's inline image protocol (IIP).
![](fixture/example.png)
@@ -44,6 +43,9 @@ terminal.loadAddon(imageAddon);
### 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:
- getWinSizePixels (CSI 14 t)
- getCellSizePixels (CSI 16 t)
@@ -71,7 +73,7 @@ terminal.loadAddon(imageAddon);
- **Cursor Positioning**
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**
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}`.
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.
- Image type hinting is not supported (always deducted from data header).
- File download is not supported.
- Filename gets parsed but not used.
- 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 pixel size is restricted by `pixelLimit` (pre- and post resizing).
- Playload size is restricted by `iipSizeLimit`.
- size parameter is not mandatory anymore.
- 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.
- Size parameter is restricted by `iipSizeLimit`.
- Cursor positioning behaves the same as for sixel (see above).
### 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
of the terminal (e.g. in the scrollback). The pattern can be deactivated by toggling `showPlaceholder`.
### Image Data Retrieval
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).
Note that the canvas gets created and data copied over for every call, thus it is not suitable for performance critical actions.
### 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:
@@ -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.
- `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.
- `sixelSizeLimit | iipSizeLimit | kittySizeLimit`
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.
- `sixelSizeLimit`
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
@@ -218,5 +211,23 @@ _How can I adjust the memory usage?_
### Status
- Sixel and IIP 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.
- Sixel support and image handling in xterm.js is considered beta quality.
- 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.
+16 -70
View File
@@ -8,7 +8,7 @@ import { IIPImageStorage } from './IIPImageStorage';
import { CELL_SIZE_DEFAULT } from './ImageStorage';
import Base64Decoder from 'xterm-wasm-parts/lib/base64/Base64Decoder.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';
// Local const enum mirror - esbuild can't inline const enums from external packages
@@ -23,7 +23,6 @@ const enum DecoderConst {
// default IIP header values
const DEFAULT_HEADER: IHeaderFields = {
type: SequenceType.INVALID,
name: 'Unnamed file',
size: 0,
width: 'auto',
@@ -40,8 +39,6 @@ export class IIPHandler implements IOscHandler, IResetHandler {
private _dec: Base64Decoder;
private _qoiDec: QoiDecoder;
private _metrics = UNSUPPORTED_TYPE;
private _isMultipart = false;
private _abortMulti = false;
constructor(
private readonly _opts: IImageAddonOptions,
@@ -55,14 +52,11 @@ export class IIPHandler implements IOscHandler, IResetHandler {
this._qoiDec = new QoiDecoder(DecoderConst.KEEP_DATA);
}
public reset(): void {
this._hp.reset();
this._dec.release();
this._qoiDec.release();
}
public reset(): void {}
public start(): void {
this._aborted = false;
this._header = DEFAULT_HEADER;
this._metrics = UNSUPPORTED_TYPE;
this._hp.reset();
}
@@ -82,27 +76,15 @@ export class IIPHandler implements IOscHandler, IResetHandler {
return;
}
if (dataPos > 0) {
const seqType = this._hp.fields.type;
if (seqType === SequenceType.FILE) {
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._header = Object.assign({}, DEFAULT_HEADER, this._hp.fields);
if (!this._header.inline || !this._header.size || this._header.size > this._opts.iipSizeLimit) {
this._aborted = true;
return;
}
this._dec.init();
if ((this._dec.put(data.subarray(dataPos, end)) as number) !== DecoderConst.OK) {
this._dec.release();
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> {
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 h = 0;
// early exit condition chain
let cond: number | boolean;
let cond: number | boolean = true;
if (cond = success) {
if (cond = !this._dec.end()) {
this._metrics = imageType(this._dec.data8);
if (cond = this._metrics.mime !== 'unsupported') {
w = this._metrics.width;
h = this._metrics.height;
if (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;
if (cond = this._dec.data8.length === this._header.size) {
this._metrics = imageType(this._dec.data8);
if (cond = this._metrics.mime !== 'unsupported') {
w = this._metrics.width;
h = this._metrics.height;
if (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;
}
}
}
}
+7 -129
View File
@@ -4,20 +4,19 @@
*/
import { assert } from 'chai';
import { HeaderParser, HeaderState, IHeaderFields, SequenceType } from './IIPHeaderParser';
import { HeaderParser, HeaderState, IHeaderFields } from './IIPHeaderParser';
const CASES: [string, IHeaderFields][] = [
['File=size=123456;name=dGVzdA==:', {type: SequenceType.FILE, 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}],
['File=size=123456;name=dGVzdA:', {name: 'test', size: 123456}],
// utf-8 encoding in name
['File=size=123456;name=w7xtbMOkdXTDnw==:', {type: SequenceType.FILE, 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}],
['File=size=123456;name=w7xtbMOkdXTDnw:', {name: 'ümläutß', size: 123456}],
// full header spec
[
'File=inline=1;width=10px;height=20%;preserveAspectRatio=1;size=123456;name=w7xtbMOkdXTDnw:',
{
type: SequenceType.FILE,
inline: 1,
width: '10px',
height: '20%',
@@ -29,7 +28,6 @@ const CASES: [string, IHeaderFields][] = [
[
'File=inline=1;width=auto;height=20;preserveAspectRatio=1;size=123456;name=w7xtbMOkdXTDnw:',
{
type: SequenceType.FILE,
inline: 1,
width: 'auto',
height: '20',
@@ -103,7 +101,7 @@ describe('IIPHeaderParser', () => {
let res = hp.parse(inp, 0, inp.length);
assert.strictEqual(res, inp.length);
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();
inp = fromBs(CASES[0][0]);
res = hp.parse(inp, 0, inp.length);
@@ -117,7 +115,7 @@ describe('IIPHeaderParser', () => {
let res = hp.parse(inp, 0, inp.length);
assert.strictEqual(res, inp.length);
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();
inp = fromBs(CASES[0][0]);
res = hp.parse(inp, 0, inp.length);
@@ -137,124 +135,4 @@ describe('IIPHeaderParser', () => {
assert.strictEqual(hp.state, HeaderState.END);
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 });
});
});
+11 -86
View File
@@ -6,27 +6,9 @@
// eslint-disable-next-line
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 {
[key: string]: number | string | Uint32Array | null | undefined;
// sequence type
type: SequenceType;
// base-64 encoded filename. Defaults to "Unnamed file".
name: string;
// File size in bytes. The file transfer will be canceled if this size is exceeded.
@@ -47,6 +29,14 @@ export interface IHeaderFields {
inline?: number;
}
export const enum HeaderState {
START = 0,
ABORT = 1,
KEY = 2,
VALUE = 3,
END = 4
}
// field value decoders
// 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];
// 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;
@@ -133,43 +111,12 @@ export class HeaderParser {
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 {
let state = this.state;
let pos = this._position;
const buffer = this._buffer;
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) {
const c = data[i];
switch (c) {
@@ -180,29 +127,8 @@ export class HeaderParser {
break;
case 61: // =
if (state === HeaderState.START) {
if (buffer[0] === 70) {
// 'File' or 'FilePart'
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();
for (let k = 0; k < FILE_MARKER.length; ++k) {
if (buffer[k] !== FILE_MARKER[k]) return this._a();
}
state = HeaderState.KEY;
pos = 0;
@@ -232,7 +158,6 @@ export class HeaderParser {
}
private _a(): number {
this.fields.type = SequenceType.INVALID;
this.state = HeaderState.ABORT;
return -1;
}
+1 -3
View File
@@ -3,7 +3,6 @@
* @license MIT
*/
import { IAddImageOpts } from 'Types';
import { ImageStorage } from './ImageStorage';
/**
@@ -13,7 +12,6 @@ import { ImageStorage } from './ImageStorage';
* - Always uses scrolling mode (cursor advances with image)
*/
export class IIPImageStorage {
private _addImageOpts: IAddImageOpts = { scrolling: true, layer: 'top', zIndex: 0, cursorPos: 'iip' };
constructor(
private readonly _storage: ImageStorage
) {}
@@ -23,6 +21,6 @@ export class IIPImageStorage {
* Always uses scrolling mode — cursor advances past the image.
*/
public addImage(img: HTMLCanvasElement | ImageBitmap): void {
this._storage.addImage(img, this._addImageOpts);
this._storage.addImage(img, true);
}
}
+1 -2
View File
@@ -30,8 +30,7 @@ const TEST_IMAGES: [string, IMetrics][] = [
['iphone_hdr_YES.jpg', { mime: 'image/jpeg', width: 3264, height: 2448 }],
['nikon-e950.jpg', { mime: 'image/jpeg', width: 800, height: 600 }],
['agfa-makernotes.jpg', { mime: 'image/jpeg', width: 8, height: 8 }],
['sony-alpha-6000.jpg', { mime: 'image/jpeg', width: 6000, height: 4000 }],
['dice.qoi', { mime: 'image/qoi', width: 800, height: 600 }],
['sony-alpha-6000.jpg', { mime: 'image/jpeg', width: 6000, height: 4000 }]
];
+6 -1
View File
@@ -137,6 +137,11 @@ export class ImageAddon implements ITerminalAddon, IImageApi {
// enable size reports
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 ?? {};
windowOps.getWinSizePixels = true;
windowOps.getCellSizePixels = true;
@@ -255,7 +260,7 @@ export class ImageAddon implements ITerminalAddon, IImageApi {
}
private _report(s: string): void {
this._terminal?._core.input(s, false);
this._terminal?._core.coreService.triggerDataEvent(s);
}
private _decset(params: (number | number[])[]): boolean {
+4 -2
View File
@@ -21,7 +21,9 @@ const enum Constants {
*/
export class ImageRenderer extends Disposable implements IDisposable {
/** @deprecated Kept for backward compat — points to top layer canvas. */
public get canvas(): HTMLCanvasElement | undefined { return this._layers.get('top')?.canvas; }
public get canvas(): HTMLCanvasElement | undefined {
return this._layers.get('top')?.canvas;
}
private _layers = new Map<ImageLayer, CanvasRenderingContext2D>();
private _placeholder: HTMLCanvasElement | undefined;
private _placeholderBitmap: ImageBitmap | undefined;
@@ -146,7 +148,7 @@ export class ImageRenderer extends Disposable implements IDisposable {
public clearLines(start: number, end: number, layer?: ImageLayer): void {
const y = start * (this.dimensions?.css.cell.height || 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') {
this._layers.get('top')?.clearRect(0, y, w, h);
}
+16 -23
View File
@@ -5,11 +5,7 @@
import { IDisposable } from '@xterm/xterm';
import { ImageRenderer } from './ImageRenderer';
import {
ITerminalExt, IExtendedAttrsImage, IImageAddonOptions, IImageSpec,
IBufferLineExt, BgFlags, Cell, Content, ICellSize, ExtFlags, Attributes,
UnderlineStyle, IAddImageOpts
} from './Types';
import { ITerminalExt, IExtendedAttrsImage, IImageAddonOptions, IImageSpec, IBufferLineExt, BgFlags, Cell, Content, ICellSize, ExtFlags, Attributes, UnderlineStyle, ImageLayer } from './Types';
// fallback default cell size
@@ -35,7 +31,9 @@ class ExtendedAttrsImage implements IExtendedAttrsImage {
}
return this._ext;
}
public set ext(value: number) { this._ext = value; }
public set ext(value: number) {
this._ext = value;
}
public get underlineStyle(): UnderlineStyle {
// Always return the URL style if it has one
@@ -239,15 +237,13 @@ export class ImageStorage implements IDisposable {
/**
* Method to add an image to the storage.
* @param img - The image to add (canvas or bitmap).
* @param opts - Options for addImage:
* - scrolling: When true, cursor advances with the image.
* When false, image is placed at ORIGIN and cursor does not move.
* - layer: Which canvas layer to render on ('top' or 'bottom').
* - zIndex: Z-index for image layering within the same layer.
* - cursorPos: 'vt340' for bottom-left, 'iip' for bottom.right.
* @param scrolling - When true, cursor advances with the image (lineFeed per row).
* When false, image is placed at (0,0) and cursor is restored (DECSET 80 / sixel origin mode).
* @param layer - Which canvas layer to render on ('top' or 'bottom').
* @param zIndex - Z-index for image layering within the same layer.
* @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
this._evictOldest(img.width * img.height);
@@ -269,7 +265,7 @@ export class ImageStorage implements IDisposable {
let offset = originX;
let tileCount = 0;
if (!opts.scrolling) {
if (!scrolling) {
buffer.x = 0;
buffer.y = 0;
offset = 0;
@@ -283,7 +279,7 @@ export class ImageStorage implements IDisposable {
this._writeToCell(line as IBufferLineExt, offset + col, imageId, row * cols + col);
tileCount++;
}
if (opts.scrolling) {
if (scrolling) {
if (row < rows - 1) this._terminal._core._inputHandler.lineFeed();
} else {
if (++buffer.y >= termRows) break;
@@ -293,12 +289,8 @@ export class ImageStorage implements IDisposable {
this._terminal._core._inputHandler._dirtyRowTracker.markDirty(buffer.y);
// cursor positioning modes
if (opts.scrolling) {
if (opts.cursorPos === 'iip') {
buffer.x = Math.min(offset + cols, termCols);
} else {
buffer.x = offset;
}
if (scrolling) {
buffer.x = offset;
} else {
buffer.x = originX;
buffer.y = originY;
@@ -341,8 +333,8 @@ export class ImageStorage implements IDisposable {
marker: endMarker || undefined,
tileCount,
bufferType: this._terminal.buffer.active.type,
layer: opts.layer,
zIndex: opts.zIndex
layer,
zIndex
};
// finally add the image
@@ -465,6 +457,7 @@ export class ImageStorage implements IDisposable {
if (!nextE || nextE.imageId !== imageId || nextE.tileId !== startTile + count) {
break;
}
e = nextE;
count++;
}
col--;
+1 -1
View File
@@ -91,7 +91,7 @@ export class SixelHandler implements IDcsHandler, IResetHandler {
const height = this._dec.height;
// partial fix for https://github.com/jerch/xterm-addon-image/issues/37
if (!width || !height) {
if (!width || ! height) {
if (height) {
this._storage.advanceCursor(height);
}
+2 -4
View File
@@ -4,7 +4,7 @@
*/
import { ImageStorage, CELL_SIZE_DEFAULT } from './ImageStorage';
import { IImageAddonOptions, ITerminalExt, IAddImageOpts } from './Types';
import { IImageAddonOptions, ITerminalExt } from './Types';
import { ImageRenderer } from './ImageRenderer';
/**
@@ -15,7 +15,6 @@ import { ImageRenderer } from './ImageRenderer';
* - advanceCursor for empty sixels carrying only height
*/
export class SixelImageStorage {
private _addImageOpts: IAddImageOpts = { scrolling: true, layer: 'top', zIndex: 0, cursorPos: 'vt340' };
constructor(
private readonly _storage: ImageStorage,
private readonly _opts: IImageAddonOptions,
@@ -28,8 +27,7 @@ export class SixelImageStorage {
* Cursor behavior depends on the sixelScrolling option (DECSET 80).
*/
public addImage(img: HTMLCanvasElement | ImageBitmap): void {
this._addImageOpts.scrolling = this._opts.sixelScrolling;
this._storage.addImage(img, this._addImageOpts);
this._storage.addImage(img, this._opts.sixelScrolling);
}
/**
+3 -11
View File
@@ -10,9 +10,9 @@ import { Attributes, BgFlags, Content, ExtFlags, UnderlineStyle } from 'common/b
import type { AttributeData } from 'common/buffer/AttributeData';
import type { IParams, IDcsHandler, IOscHandler, IApcHandler, IEscapeSequenceParser } from 'common/parser/Types';
import type { IBufferLine, IExtendedAttrs, IInputHandler } from 'common/Types';
import type { ITerminal, ReadonlyColorSet } from 'target-browser/Types';
import type { IRenderDimensions } from 'target-browser/renderer/shared/Types';
import type { ICoreBrowserService, IRenderService, IThemeService } from 'target-browser/services/Services';
import type { ITerminal, ReadonlyColorSet } from 'browser/Types';
import type { IRenderDimensions } from 'browser/renderer/shared/Types';
import type { ICoreBrowserService, IRenderService, IThemeService } from 'browser/services/Services';
export const enum Cell {
CONTENT = 0, // codepoint and wcwidth information (enum Content)
@@ -100,14 +100,6 @@ export interface ICellSize {
}
export type ImageLayer = 'top' | 'bottom';
export type CursorPos = 'vt340' | 'iip';
export interface IAddImageOpts {
scrolling: boolean;
layer: ImageLayer;
zIndex: number;
cursorPos: CursorPos;
}
export interface IImageSpec {
orig: HTMLCanvasElement | ImageBitmap | undefined;
@@ -5,7 +5,7 @@
import { IDisposable } from '@xterm/xterm';
import { ImageStorage } from '../ImageStorage';
import { ImageLayer, IAddImageOpts } from '../Types';
import { ImageLayer } from '../Types';
import { IKittyImageData } from './KittyGraphicsTypes';
// Kitty-specific image storage controller.
@@ -42,7 +42,6 @@ export class KittyImageStorage implements IDisposable {
this._images.delete(kittyId);
}
};
private _addImageOpts: IAddImageOpts = { scrolling: true, layer: 'top', zIndex: 0, cursorPos: 'iip' };
constructor(
private readonly _storage: ImageStorage
@@ -99,10 +98,7 @@ export class KittyImageStorage implements IDisposable {
if (oldStorageId !== undefined) {
this._storageIdToKittyId.delete(oldStorageId);
}
this._addImageOpts.scrolling = scrolling;
this._addImageOpts.layer = layer;
this._addImageOpts.zIndex = zIndex;
const storageId = this._storage.addImage(image, this._addImageOpts);
const storageId = this._storage.addImage(image, scrolling, layer, zIndex);
this._kittyIdToStorageId.set(kittyId, storageId);
this._storageIdToKittyId.set(storageId, kittyId);
}
+4 -7
View File
@@ -12,11 +12,8 @@
"../../../node_modules/@types/mocha"
],
"paths": {
"target-browser/*": [ "../../../src/target-browser/*" ],
"common/*": [
"../../../src/primitives/*",
"../../../src/runtime/*",
],
"browser/*": [ "../../../src/browser/*" ],
"common/*": [ "../../../src/common/*" ],
"@xterm/addon-image": [ "../typings/addon-image.d.ts" ],
"*": [ "./*" ]
}
@@ -26,7 +23,7 @@
"../../../typings/xterm.d.ts"
],
"references": [
{ "path": "../../../src/target-browser" },
{ "path": "../../../src" }
{ "path": "../../../src/browser" },
{ "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('palette.png', async () => {
await ctx.proxy.write(TESTDATA_IIP[0][0]);
+165 -57
View File
@@ -322,7 +322,9 @@ test.describe('Kitty Graphics Protocol', () => {
test('chunked transfer responds OK on final chunk when i= on first only', async () => {
await ctx.page.evaluate(() => {
(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;
});
});
const half = Math.floor(KITTY_BLACK_1X1_BASE64.length / 2);
@@ -539,28 +541,34 @@ test.describe('Kitty Graphics Protocol', () => {
test.describe('Query support (a=q)', () => {
test('responds with OK for capability query without payload', async () => {
let response = '';
await ctx.page.evaluate(() => {
(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;
});
});
await ctx.proxy.write('\x1b_Gi=31,a=q;\x1b\\');
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\\');
});
test('responds with OK for valid PNG query', async () => {
let response = '';
await ctx.page.evaluate(() => {
(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;
});
});
await ctx.proxy.write(`\x1b_Gi=42,a=q,f=100;${KITTY_BLACK_1X1_BASE64}\x1b\\`);
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\\');
});
@@ -576,35 +584,43 @@ test.describe('Kitty Graphics Protocol', () => {
});
test('responds with error for invalid base64', async () => {
let response = '';
await ctx.page.evaluate(() => {
(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;
});
});
await ctx.proxy.write('\x1b_Gi=60,a=q,f=100;!!!invalid!!!\x1b\\');
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);
});
test('responds with error for RGB data without dimensions', async () => {
let response = '';
await ctx.page.evaluate(() => {
(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;
});
});
await ctx.proxy.write('\x1b_Gi=70,a=q,f=24;AAAA\x1b\\');
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\\');
});
test('suppresses OK response when q=1', async () => {
await ctx.page.evaluate(() => {
(window as any).kittyGotResponse = false;
(window as any).term.onData(() => { (window as any).kittyGotResponse = true; });
(window as any).term.onData(() => {
(window as any).kittyGotResponse = true;
});
});
await ctx.proxy.write(`\x1b_Gi=80,a=q,q=1,f=100;${KITTY_BLACK_1X1_BASE64}\x1b\\`);
@@ -616,7 +632,9 @@ test.describe('Kitty Graphics Protocol', () => {
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; });
(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\\`);
@@ -628,7 +646,9 @@ test.describe('Kitty Graphics Protocol', () => {
test('suppresses error response when q=2', async () => {
await ctx.page.evaluate(() => {
(window as any).kittyGotResponse = false;
(window as any).term.onData(() => { (window as any).kittyGotResponse = true; });
(window as any).term.onData(() => {
(window as any).kittyGotResponse = true;
});
});
await ctx.proxy.write('\x1b_Gi=90,a=q,q=2,f=100;!!!invalid!!!\x1b\\');
@@ -638,30 +658,36 @@ test.describe('Kitty Graphics Protocol', () => {
});
test('responds with EINVAL when both i and I keys are specified', async () => {
let response = '';
await ctx.page.evaluate(() => {
(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;
});
});
// Per spec: "Specifying both i and I keys in any command is an error"
await ctx.proxy.write(`\x1b_Gi=100,I=200,a=q,f=100;${KITTY_BLACK_1X1_BASE64}\x1b\\`);
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\\');
});
test('responds with EINVAL for i+I conflict even without payload', async () => {
let response = '';
await ctx.page.evaluate(() => {
(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;
});
});
// Delete command with both i and I (no payload case)
await ctx.proxy.write('\x1b_Gi=101,I=201,a=d\x1b\\');
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\\');
});
});
@@ -670,7 +696,9 @@ test.describe('Kitty Graphics Protocol', () => {
test('a=t sends EINVAL on decode error when id is specified', async () => {
await ctx.page.evaluate(() => {
(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;
});
});
await ctx.proxy.write('\x1b_Gi=110,a=t,f=100;!!!invalid!!!\x1b\\');
@@ -683,7 +711,9 @@ test.describe('Kitty Graphics Protocol', () => {
test('a=t sends no response on decode error without id', async () => {
await ctx.page.evaluate(() => {
(window as any).kittyGotResponse = false;
(window as any).term.onData(() => { (window as any).kittyGotResponse = true; });
(window as any).term.onData(() => {
(window as any).kittyGotResponse = true;
});
});
await ctx.proxy.write('\x1b_Ga=t,f=100;!!!invalid!!!\x1b\\');
@@ -695,7 +725,9 @@ test.describe('Kitty Graphics Protocol', () => {
test('a=T sends EINVAL on decode error when id is specified', async () => {
await ctx.page.evaluate(() => {
(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;
});
});
await ctx.proxy.write('\x1b_Gi=120,a=T,f=100;!!!invalid!!!\x1b\\');
@@ -708,7 +740,9 @@ test.describe('Kitty Graphics Protocol', () => {
test('a=T sends no response on decode error without id', async () => {
await ctx.page.evaluate(() => {
(window as any).kittyGotResponse = false;
(window as any).term.onData(() => { (window as any).kittyGotResponse = true; });
(window as any).term.onData(() => {
(window as any).kittyGotResponse = true;
});
});
await ctx.proxy.write('\x1b_Ga=T,f=100;!!!invalid!!!\x1b\\');
@@ -720,7 +754,9 @@ test.describe('Kitty Graphics Protocol', () => {
test('a=T sends EINVAL when raw pixel render fails (missing dimensions)', async () => {
await ctx.page.evaluate(() => {
(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;
});
});
await ctx.proxy.write(`\x1b_Gi=130,a=T,f=24;${RAW_RGB_1X1_BLACK}\x1b\\`);
@@ -733,7 +769,9 @@ test.describe('Kitty Graphics Protocol', () => {
test('a=T sends OK on successful render with id', async () => {
await ctx.page.evaluate(() => {
(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;
});
});
await ctx.proxy.write(`\x1b_Gi=140,a=T,f=100;${KITTY_BLACK_1X1_BASE64}\x1b\\`);
@@ -746,7 +784,9 @@ test.describe('Kitty Graphics Protocol', () => {
test('a=t sends OK on successful transmit with id', async () => {
await ctx.page.evaluate(() => {
(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;
});
});
await ctx.proxy.write(`\x1b_Gi=150,a=t,f=100;${KITTY_BLACK_1X1_BASE64}\x1b\\`);
@@ -759,7 +799,9 @@ test.describe('Kitty Graphics Protocol', () => {
test('a=t EINVAL suppressed by q=2', async () => {
await ctx.page.evaluate(() => {
(window as any).kittyGotResponse = false;
(window as any).term.onData(() => { (window as any).kittyGotResponse = true; });
(window as any).term.onData(() => {
(window as any).kittyGotResponse = true;
});
});
await ctx.proxy.write('\x1b_Gi=160,a=t,q=2,f=100;!!!invalid!!!\x1b\\');
@@ -771,7 +813,9 @@ test.describe('Kitty Graphics Protocol', () => {
test('a=T EINVAL suppressed by q=2', async () => {
await ctx.page.evaluate(() => {
(window as any).kittyGotResponse = false;
(window as any).term.onData(() => { (window as any).kittyGotResponse = true; });
(window as any).term.onData(() => {
(window as any).kittyGotResponse = true;
});
});
await ctx.proxy.write('\x1b_Gi=170,a=T,q=2,f=100;!!!invalid!!!\x1b\\');
@@ -783,7 +827,9 @@ test.describe('Kitty Graphics Protocol', () => {
test('a=t OK suppressed by q=1', async () => {
await ctx.page.evaluate(() => {
(window as any).kittyGotResponse = false;
(window as any).term.onData(() => { (window as any).kittyGotResponse = true; });
(window as any).term.onData(() => {
(window as any).kittyGotResponse = true;
});
});
await ctx.proxy.write(`\x1b_Gi=180,a=t,q=1,f=100;${KITTY_BLACK_1X1_BASE64}\x1b\\`);
@@ -795,7 +841,9 @@ test.describe('Kitty Graphics Protocol', () => {
test('a=T OK suppressed by q=1', async () => {
await ctx.page.evaluate(() => {
(window as any).kittyGotResponse = false;
(window as any).term.onData(() => { (window as any).kittyGotResponse = true; });
(window as any).term.onData(() => {
(window as any).kittyGotResponse = true;
});
});
await ctx.proxy.write(`\x1b_Gi=190,a=T,q=1,f=100;${KITTY_BLACK_1X1_BASE64}\x1b\\`);
@@ -807,7 +855,9 @@ test.describe('Kitty Graphics Protocol', () => {
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; });
(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\\`);
@@ -819,7 +869,9 @@ test.describe('Kitty Graphics Protocol', () => {
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; });
(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\\`);
@@ -833,7 +885,9 @@ test.describe('Kitty Graphics Protocol', () => {
test('query rejects t=f (file transmission)', async () => {
await ctx.page.evaluate(() => {
(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;
});
});
await ctx.proxy.write(`\x1b_Gi=200,a=q,t=f,f=100;${KITTY_BLACK_1X1_BASE64}\x1b\\`);
@@ -846,7 +900,9 @@ test.describe('Kitty Graphics Protocol', () => {
test('query rejects t=s (shared memory)', async () => {
await ctx.page.evaluate(() => {
(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;
});
});
await ctx.proxy.write(`\x1b_Gi=201,a=q,t=s,f=100;${KITTY_BLACK_1X1_BASE64}\x1b\\`);
@@ -859,7 +915,9 @@ test.describe('Kitty Graphics Protocol', () => {
test('query rejects t=t (temp file)', async () => {
await ctx.page.evaluate(() => {
(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;
});
});
await ctx.proxy.write(`\x1b_Gi=202,a=q,t=t,f=100;${KITTY_BLACK_1X1_BASE64}\x1b\\`);
@@ -872,7 +930,9 @@ test.describe('Kitty Graphics Protocol', () => {
test('query accepts t=d (direct transmission)', async () => {
await ctx.page.evaluate(() => {
(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;
});
});
await ctx.proxy.write(`\x1b_Gi=203,a=q,t=d,f=100;${KITTY_BLACK_1X1_BASE64}\x1b\\`);
@@ -885,7 +945,9 @@ test.describe('Kitty Graphics Protocol', () => {
test('query without t key defaults to direct (OK)', async () => {
await ctx.page.evaluate(() => {
(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;
});
});
await ctx.proxy.write(`\x1b_Gi=204,a=q,f=100;${KITTY_BLACK_1X1_BASE64}\x1b\\`);
@@ -898,7 +960,9 @@ test.describe('Kitty Graphics Protocol', () => {
test('transmit rejects t=f with id (EINVAL response)', async () => {
await ctx.page.evaluate(() => {
(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;
});
});
await ctx.proxy.write(`\x1b_Gi=300,a=t,t=f,f=100;${KITTY_BLACK_1X1_BASE64}\x1b\\`);
@@ -911,7 +975,9 @@ test.describe('Kitty Graphics Protocol', () => {
test('transmit rejects t=s with id (EINVAL response)', async () => {
await ctx.page.evaluate(() => {
(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;
});
});
await ctx.proxy.write(`\x1b_Gi=301,a=t,t=s,f=100;${KITTY_BLACK_1X1_BASE64}\x1b\\`);
@@ -924,7 +990,9 @@ test.describe('Kitty Graphics Protocol', () => {
test('transmit rejects t=t with id (EINVAL response)', async () => {
await ctx.page.evaluate(() => {
(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;
});
});
await ctx.proxy.write(`\x1b_Gi=302,a=t,t=t,f=100;${KITTY_BLACK_1X1_BASE64}\x1b\\`);
@@ -937,7 +1005,9 @@ test.describe('Kitty Graphics Protocol', () => {
test('transmit rejects t=f without id (no response)', async () => {
await ctx.page.evaluate(() => {
(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;
});
});
await ctx.proxy.write(`\x1b_Ga=t,t=f,f=100;${KITTY_BLACK_1X1_BASE64}\x1b\\`);
@@ -950,7 +1020,9 @@ test.describe('Kitty Graphics Protocol', () => {
test('transmit+display rejects t=f with id (EINVAL response)', async () => {
await ctx.page.evaluate(() => {
(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;
});
});
await ctx.proxy.write(`\x1b_Gi=310,a=T,t=f,f=100;${KITTY_BLACK_1X1_BASE64}\x1b\\`);
@@ -963,7 +1035,9 @@ test.describe('Kitty Graphics Protocol', () => {
test('transmit+display rejects t=s with id (EINVAL response)', async () => {
await ctx.page.evaluate(() => {
(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;
});
});
await ctx.proxy.write(`\x1b_Gi=311,a=T,t=s,f=100;${KITTY_BLACK_1X1_BASE64}\x1b\\`);
@@ -976,7 +1050,9 @@ test.describe('Kitty Graphics Protocol', () => {
test('transmit+display rejects t=t with id (EINVAL response)', async () => {
await ctx.page.evaluate(() => {
(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;
});
});
await ctx.proxy.write(`\x1b_Gi=312,a=T,t=t,f=100;${KITTY_BLACK_1X1_BASE64}\x1b\\`);
@@ -989,7 +1065,9 @@ test.describe('Kitty Graphics Protocol', () => {
test('transmit+display rejects t=f without id (no response)', async () => {
await ctx.page.evaluate(() => {
(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;
});
});
await ctx.proxy.write(`\x1b_Ga=T,t=f,f=100;${KITTY_BLACK_1X1_BASE64}\x1b\\`);
@@ -1020,7 +1098,9 @@ test.describe('Kitty Graphics Protocol', () => {
await ctx.page.evaluate(() => {
(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;
});
});
await ctx.proxy.write(`\x1b_Ga=p,i=211\x1b\\`);
@@ -1033,7 +1113,9 @@ test.describe('Kitty Graphics Protocol', () => {
test('responds ENOENT for non-existent image id', async () => {
await ctx.page.evaluate(() => {
(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;
});
});
await ctx.proxy.write(`\x1b_Ga=p,i=9999\x1b\\`);
@@ -1046,7 +1128,9 @@ test.describe('Kitty Graphics Protocol', () => {
test('without id sends no response', async () => {
await ctx.page.evaluate(() => {
(window as any).kittyGotResponse = false;
(window as any).term.onData(() => { (window as any).kittyGotResponse = true; });
(window as any).term.onData(() => {
(window as any).kittyGotResponse = true;
});
});
await ctx.proxy.write(`\x1b_Ga=p\x1b\\`);
@@ -1166,7 +1250,9 @@ test.describe('Kitty Graphics Protocol', () => {
await ctx.page.evaluate(() => {
(window as any).kittyGotResponse = false;
(window as any).term.onData(() => { (window as any).kittyGotResponse = true; });
(window as any).term.onData(() => {
(window as any).kittyGotResponse = true;
});
});
await ctx.proxy.write(`\x1b_Ga=p,i=220,q=1\x1b\\`);
@@ -1182,7 +1268,9 @@ test.describe('Kitty Graphics Protocol', () => {
await ctx.page.evaluate(() => {
(window as any).kittyGotResponse = false;
(window as any).term.onData(() => { (window as any).kittyGotResponse = true; });
(window as any).term.onData(() => {
(window as any).kittyGotResponse = true;
});
});
await ctx.proxy.write(`\x1b_Ga=p,i=221,q=2\x1b\\`);
@@ -1195,7 +1283,9 @@ test.describe('Kitty Graphics Protocol', () => {
test('ENOENT error suppressed by q=2', async () => {
await ctx.page.evaluate(() => {
(window as any).kittyGotResponse = false;
(window as any).term.onData(() => { (window as any).kittyGotResponse = true; });
(window as any).term.onData(() => {
(window as any).kittyGotResponse = true;
});
});
await ctx.proxy.write(`\x1b_Ga=p,i=9998,q=2\x1b\\`);
@@ -1207,7 +1297,9 @@ test.describe('Kitty Graphics Protocol', () => {
test('ENOENT still reported when q=1 (only suppresses OK)', async () => {
await ctx.page.evaluate(() => {
(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;
});
});
await ctx.proxy.write(`\x1b_Ga=p,i=9997,q=1\x1b\\`);
@@ -1262,7 +1354,9 @@ test.describe('Kitty Graphics Protocol', () => {
await ctx.page.evaluate(() => {
(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;
});
});
await ctx.proxy.write(`\x1b_Ga=p,i=224,p=42\x1b\\`);
@@ -1797,7 +1891,9 @@ test.describe('Kitty Graphics Protocol', () => {
test('responds with OK for valid 200x100 PNG query', async () => {
await ctx.page.evaluate(() => {
(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;
});
});
await ctx.proxy.write(`\x1b_Gi=600,a=q,f=100;${KITTY_MULTICOLOR_200X100_BASE64}\x1b\\`);
@@ -1946,7 +2042,9 @@ test.describe('Kitty Graphics Protocol', () => {
test('query returns EINVAL without dimensions', async () => {
await ctx.page.evaluate(() => {
(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;
});
});
await ctx.proxy.write(`\x1b_Gi=200,a=q,f=24;${RAW_RGB_1X1_BLACK}\x1b\\`);
await timeout(100);
@@ -1957,7 +2055,9 @@ test.describe('Kitty Graphics Protocol', () => {
test('query returns EINVAL for insufficient pixel data', async () => {
await ctx.page.evaluate(() => {
(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;
});
});
await ctx.proxy.write(`\x1b_Gi=201,a=q,f=24,s=2,v=2;${RAW_RGB_1X1_BLACK}\x1b\\`);
await timeout(100);
@@ -1968,7 +2068,9 @@ test.describe('Kitty Graphics Protocol', () => {
test('query returns OK for valid RGB data with correct dimensions', async () => {
await ctx.page.evaluate(() => {
(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;
});
});
await ctx.proxy.write(`\x1b_Gi=202,a=q,f=24,s=1,v=1;${RAW_RGB_1X1_RED}\x1b\\`);
await timeout(100);
@@ -2080,7 +2182,9 @@ test.describe('Kitty Graphics Protocol', () => {
test('query returns EINVAL without dimensions', async () => {
await ctx.page.evaluate(() => {
(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;
});
});
await ctx.proxy.write(`\x1b_Gi=300,a=q,f=32;${RAW_RGBA_1X1_RED}\x1b\\`);
await timeout(100);
@@ -2091,7 +2195,9 @@ test.describe('Kitty Graphics Protocol', () => {
test('query returns EINVAL for insufficient pixel data', async () => {
await ctx.page.evaluate(() => {
(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;
});
});
await ctx.proxy.write(`\x1b_Gi=301,a=q,f=32,s=2,v=2;${RAW_RGBA_1X1_RED}\x1b\\`);
await timeout(100);
@@ -2102,7 +2208,9 @@ test.describe('Kitty Graphics Protocol', () => {
test('query returns OK for valid RGBA data with correct dimensions', async () => {
await ctx.page.evaluate(() => {
(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;
});
});
await ctx.proxy.write(`\x1b_Gi=302,a=q,f=32,s=1,v=1;${RAW_RGBA_1X1_RED}\x1b\\`);
await timeout(100);
+5 -6
View File
@@ -11,11 +11,10 @@
"removeComments": true,
"paths": {
"common/*": [
"../../../src/primitives/*",
"../../../src/runtime/*",
"../../../src/common/*"
],
"target-browser/*": [
"../../../src/target-browser/*"
"browser/*": [
"../../../src/browser/*"
],
"*": [
"./*"
@@ -32,10 +31,10 @@
],
"references": [
{
"path": "../../../src"
"path": "../../../src/common"
},
{
"path": "../../../src/target-browser"
"path": "../../../src/browser"
},
{
"path": "../../../test/playwright"
+2 -5
View File
@@ -25,11 +25,8 @@ const addon = {
modules: ['./node_modules'],
extensions: [ '.js' ],
alias: {
common: [
path.resolve('../../out/runtime'),
path.resolve('../../out/primitives'),
],
browser: path.resolve('../../out/target-browser'),
common: path.resolve('../../out/common'),
browser: path.resolve('../../out/browser'),
vs: path.resolve('../../out/vs')
}
},
+9 -5
View File
@@ -20,7 +20,7 @@
"package": "webpack",
"pretest": "npm run build",
"test": "nyc mocha out/**/*.test.js",
"prepublishOnly": "npm run package"
"prepublish": "npm run package"
},
"keywords": [
"font",
@@ -31,11 +31,15 @@
],
"license": "MIT",
"dependencies": {
"lru-cache": "^11.3.6",
"opentype.js": "^2.0.0"
"lru-cache": "^6.0.0",
"opentype.js": "^0.8.0"
},
"devDependencies": {
"@types/opentype.js": "^1.3.9",
"font-finder": "^1.1.0"
"@types/lru-cache": "^5.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"
}
}
+2 -3
View File
@@ -65,9 +65,8 @@ export default async function load(fontFamily: string, cacheSize: number): Promi
console.error(err.name, err.message);
}
}
}
// Latest proposal https://bugs.chromium.org/p/chromium/issues/detail?id=1312603
else if (typeof window !== 'undefined' && 'queryLocalFonts' in window) {
} else if (typeof window !== 'undefined' && 'queryLocalFonts' in window) {
// Latest proposal https://bugs.chromium.org/p/chromium/issues/detail?id=1312603
const fonts: Record<string, IFontMetadata[]> = {};
try {
const fontsIterator = await (window as any).queryLocalFonts();
@@ -1,5 +1,5 @@
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 mergeTrees from './merge';
@@ -23,8 +23,8 @@ class FontImpl implements IFont {
if (options.cacheSize > 0) {
this._cache = new LRUCache({
maxSize: options.cacheSize,
sizeCalculation: ((val: ILigatureData | [number, number][], key: string) => key.length) as any
max: options.cacheSize,
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
ranges[i - 1][1] = Math.max(newRangeEnd, range[1]);
ranges.splice(i, 1);
inRange = false;
return ranges;
}
// Case 7: New range extends from previous range past the
@@ -46,7 +46,9 @@ describe('LigaturesAddon', () => {
});
beforeEach(() => {
onRefresh = Object.assign((..._args: any[]) => { onRefresh.called = true; onRefresh.callCount++; }, { called: false, callCount: 0 });
onRefresh = Object.assign((..._args: any[]) => {
onRefresh.called = true; onRefresh.callCount++;
}, { called: false, callCount: 0 });
term = new MockTerminal(onRefresh);
ligatureSupport.enableLigatures(term as any);
});
@@ -127,7 +129,9 @@ class MockTerminal {
public deregisterCharacterJoiner(id: number): void {
this.joiner = undefined;
}
public get options(): { [name: string]: string | number } { return this._options; }
public get options(): { [name: string]: string | number } {
return this._options;
}
public set options(options: { [name: string]: string | number }) {
for (const key in this._options) {
this._options[key] = options[key];
+5 -6
View File
@@ -16,11 +16,10 @@
],
"paths": {
"common/*": [
"../../../src/primitives/*",
"../../../src/runtime/*",
"../../../src/common/*"
],
"target-browser/*": [
"../../../src/target-browser/*"
"browser/*": [
"../../../src/browser/*"
],
"@xterm/addon-progress": [
"../typings/addon-progress.d.ts"
@@ -33,10 +32,10 @@
],
"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;
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.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> {
await ctx.page.evaluate(`
window.progressStack = [];
+5 -6
View File
@@ -11,11 +11,10 @@
"removeComments": true,
"paths": {
"common/*": [
"../../../src/primitives/*",
"../../../src/runtime/*",
"../../../src/common/*"
],
"target-browser/*": [
"../../../src/target-browser/*"
"browser/*": [
"../../../src/browser/*"
],
"*": [
"./*"
@@ -32,10 +31,10 @@
],
"references": [
{
"path": "../../../src"
"path": "../../../src/common"
},
{
"path": "../../../src/target-browser"
"path": "../../../src/browser"
},
{
"path": "../../../test/playwright"
+2 -5
View File
@@ -25,11 +25,8 @@ module.exports = {
modules: ['./node_modules'],
extensions: [ '.js' ],
alias: {
common: [
path.resolve('../../out/runtime'),
path.resolve('../../out/primitives'),
],
browser: path.resolve('../../out/target-browser'),
common: path.resolve('../../out/common'),
browser: path.resolve('../../out/browser'),
vs: path.resolve('../../out/vs')
}
},
+6 -2
View File
@@ -64,7 +64,9 @@ export class DecorationManager extends Disposable {
public createActiveDecoration(result: ISearchResult, options: ISearchDecorationOptions): IMultiHighlight | undefined {
const decorations = this._createResultDecorations(result, options, true);
if (decorations) {
return { decorations, match: result, dispose() { dispose(decorations); } };
return { decorations, match: result, dispose() {
dispose(decorations);
} };
}
return undefined;
}
@@ -85,7 +87,9 @@ export class DecorationManager extends Disposable {
*/
private _storeDecoration(decoration: IDecoration, match: ISearchResult): void {
this._highlightedLines.add(decoration.marker.line);
this._highlightDecorations.push({ decoration, match, dispose() { decoration.dispose(); } });
this._highlightDecorations.push({ decoration, match, dispose() {
decoration.dispose();
} });
}
/**
+1 -1
View File
@@ -5,7 +5,7 @@
import { assert } from 'chai';
import { SearchEngine } from './SearchEngine';
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 { DisposableStore } from 'common/Lifecycle';
+3 -3
View File
@@ -174,7 +174,7 @@ export class SearchEngine {
this._terminal.clearSelection();
let startRow = this._terminal.buffer.active.baseY + this._terminal.rows - 1;
const startCol = this._terminal.cols;
let startCol = this._terminal.cols;
const isReverseSearch = true;
this._lineCache.initLinesCache();
@@ -186,14 +186,14 @@ export class SearchEngine {
let result: ISearchResult | undefined;
if (prevSelectedPos) {
searchPosition.startRow = startRow = prevSelectedPos.start.y;
searchPosition.startCol = prevSelectedPos.start.x;
searchPosition.startCol = startCol = prevSelectedPos.start.x;
if (cachedSearchTerm !== term) {
// Try to expand selection to right first.
result = this._findInLine(term, searchPosition, searchOptions, false);
if (!result) {
// If selection was not able to be expanded to the right, then try reverse search
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 { SearchLineCache, LineCacheEntry } from './SearchLineCache';
import { Terminal } from 'target-browser/public/Terminal';
import { Terminal } from 'browser/public/Terminal';
import { timeout } from 'common/Async';
function writeP(terminal: Terminal, data: string): Promise<void> {
@@ -25,7 +25,9 @@ export class SearchResultTracker extends Disposable {
private _selectedDecoration: ISelectedDecoration | undefined;
private readonly _onDidChangeResults = this._register(new Emitter<ISearchResultChangeEvent>());
public get onDidChangeResults(): IEvent<ISearchResultChangeEvent> { return this._onDidChangeResults.event; }
public get onDidChangeResults(): IEvent<ISearchResultChangeEvent> {
return this._onDidChangeResults.event;
}
/**
* Gets the current search results.
+5 -6
View File
@@ -16,11 +16,10 @@
],
"paths": {
"common/*": [
"../../../src/primitives/*",
"../../../src/runtime/*",
"../../../src/common/*"
],
"target-browser/*": [
"../../../src/target-browser/*"
"browser/*": [
"../../../src/browser/*"
],
"@xterm/addon-search" : [
"../typings/addon-search.d.ts"
@@ -33,10 +32,10 @@
],
"references": [
{
"path": "../../../src"
"path": "../../../src/common"
},
{
"path": "../../../src/target-browser"
"path": "../../../src/browser"
}
]
}
+5 -6
View File
@@ -11,11 +11,10 @@
"removeComments": true,
"paths": {
"common/*": [
"../../../src/primitives/*",
"../../../src/runtime/*",
"../../../src/common/*"
],
"target-browser/*": [
"../../../src/target-browser/*"
"browser/*": [
"../../../src/browser/*"
],
"*": [
"./*"
@@ -32,10 +31,10 @@
],
"references": [
{
"path": "../../../src"
"path": "../../../src/common"
},
{
"path": "../../../src/target-browser"
"path": "../../../src/browser"
},
{
"path": "../../../test/playwright"
+1 -4
View File
@@ -25,10 +25,7 @@ module.exports = {
modules: ['./node_modules'],
extensions: [ '.js' ],
alias: {
common: [
path.resolve('../../out/runtime'),
path.resolve('../../out/primitives'),
],
common: path.resolve('../../out/common'),
vs: path.resolve('../../out/vs')
}
},
@@ -7,7 +7,7 @@ import { perfContext, before, ThroughputRuntimeCase } from 'xterm-benchmark';
import { spawn } from 'node-pty';
import { Utf8ToUtf32, stringFromCodePoint } from 'common/input/TextDecoder';
import { Terminal } from 'target-browser/public/Terminal';
import { Terminal } from 'browser/public/Terminal';
import { SerializeAddon } from 'SerializeAddon';
class TestTerminal extends Terminal {
@@ -7,16 +7,13 @@
"rootDir": "..",
"outDir": "../out-benchmark",
"types": ["../../../node_modules/@types/node"],
"moduleResolution": "nodenext",
"moduleResolution": "node",
"strict": false,
"target": "es2021",
"module": "nodenext",
"module": "commonjs",
"paths": {
"common/*": [
"../../../src/primitives/*",
"../../../src/runtime/*",
],
"target-browser/*": ["../../../src/target-browser/*"],
"common/*": ["../../../src/common/*"],
"browser/*": ["../../../src/browser/*"],
"SerializeAddon": ["../src/SerializeAddon"],
"@xterm/addon-serialize": [
"../typings/addon-serialize.d.ts"
@@ -26,7 +23,7 @@
"include": ["../**/*", "../../../typings/xterm.d.ts"],
"exclude": ["../../../**/*test.ts", "../../**/*api.ts"],
"references": [
{ "path": "../../../src" },
{ "path": "../../../src/target-browser" }
{ "path": "../../../src/common" },
{ "path": "../../../src/browser" }
]
}
@@ -6,10 +6,10 @@
import jsdom = require('jsdom');
import { assert } from 'chai';
import { SerializeAddon } from './SerializeAddon';
import { Terminal } from 'target-browser/public/Terminal';
import { SelectionModel } from 'target-browser/selection/SelectionModel';
import { Terminal } from 'browser/public/Terminal';
import { SelectionModel } from 'browser/selection/SelectionModel';
import { IBufferService } from 'common/services/Services';
import { ThemeService } from 'target-browser/services/ThemeService';
import { ThemeService } from 'browser/services/ThemeService';
function sgr(...seq: string[]): string {
return `\x1b[${seq.join(';')}m`;
@@ -29,12 +29,20 @@ class TestSelectionService {
this._model = new SelectionModel(bufferService);
}
public get model(): SelectionModel { return this._model; }
public get model(): SelectionModel {
return this._model;
}
public get hasSelection(): boolean { return this._hasSelection; }
public get hasSelection(): boolean {
return this._hasSelection;
}
public get selectionStart(): [number, number] | undefined { return this._model.finalSelectionStart; }
public get selectionEnd(): [number, number] | undefined { return this._model.finalSelectionEnd; }
public get selectionStart(): [number, number] | undefined {
return this._model.finalSelectionStart;
}
public get selectionEnd(): [number, number] | undefined {
return this._model.finalSelectionEnd;
}
public setSelection(col: number, row: number, length: number): void {
this._model.selectionStart = [col, row];
+64 -27
View File
@@ -8,7 +8,7 @@
import type { IBuffer, IBufferCell, IBufferRange, ITerminalAddon, Terminal } from '@xterm/xterm';
import type { IHTMLSerializeOptions, SerializeAddon as ISerializeApi, ISerializeOptions, ISerializeRange } from '@xterm/addon-serialize';
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';
function constrain(value: number, low: number, high: number): number {
@@ -70,7 +70,9 @@ abstract class BaseSerializeHandler {
protected _rowEnd(row: number, isLastRow: boolean): void { }
protected _beforeSerialize(rows: number, startRow: number, endRow: number): void { }
protected _afterSerialize(): void { }
protected _serializeString(excludeFinalCursorPosition?: boolean): string { return ''; }
protected _serializeString(excludeFinalCursorPosition?: boolean): string {
return '';
}
}
function equalFg(cell1: IBufferCell | IAttributeData, cell2: IBufferCell): boolean {
@@ -291,25 +293,39 @@ class StringSerializeHandler extends BaseSerializeHandler {
} else {
if (fgChanged) {
const color = cell.getFgColor();
if (cell.isFgRGB()) { sgrSeq.push(38, 2, (color >>> 16) & 0xFF, (color >>> 8) & 0xFF, color & 0xFF); }
else if (cell.isFgPalette()) {
if (color >= 16) { sgrSeq.push(38, 5, color); }
else { sgrSeq.push(color & 8 ? 90 + (color & 7) : 30 + (color & 7)); }
if (cell.isFgRGB()) {
sgrSeq.push(38, 2, (color >>> 16) & 0xFF, (color >>> 8) & 0xFF, color & 0xFF);
} else if (cell.isFgPalette()) {
if (color >= 16) {
sgrSeq.push(38, 5, color);
} else {
sgrSeq.push(color & 8 ? 90 + (color & 7) : 30 + (color & 7));
}
} else {
sgrSeq.push(39);
}
else { sgrSeq.push(39); }
}
if (bgChanged) {
const color = cell.getBgColor();
if (cell.isBgRGB()) { sgrSeq.push(48, 2, (color >>> 16) & 0xFF, (color >>> 8) & 0xFF, color & 0xFF); }
else if (cell.isBgPalette()) {
if (color >= 16) { sgrSeq.push(48, 5, color); }
else { sgrSeq.push(color & 8 ? 100 + (color & 7) : 40 + (color & 7)); }
if (cell.isBgRGB()) {
sgrSeq.push(48, 2, (color >>> 16) & 0xFF, (color >>> 8) & 0xFF, color & 0xFF);
} else if (cell.isBgPalette()) {
if (color >= 16) {
sgrSeq.push(48, 5, color);
} else {
sgrSeq.push(color & 8 ? 100 + (color & 7) : 40 + (color & 7));
}
} else {
sgrSeq.push(49);
}
else { sgrSeq.push(49); }
}
if (flagsChanged) {
if (cell.isInverse() !== oldCell.isInverse()) { sgrSeq.push(cell.isInverse() ? 7 : 27); }
if (cell.isBold() !== oldCell.isBold()) { sgrSeq.push(cell.isBold() ? 1 : 22); }
if (cell.isInverse() !== oldCell.isInverse()) {
sgrSeq.push(cell.isInverse() ? 7 : 27);
}
if (cell.isBold() !== oldCell.isBold()) {
sgrSeq.push(cell.isBold() ? 1 : 22);
}
if (!equalUnderline(cell, oldCell)) {
const style = cell.getUnderlineStyle();
if (style === UnderlineStyle.NONE) {
@@ -332,12 +348,24 @@ class StringSerializeHandler extends BaseSerializeHandler {
} else if (cell.isUnderline() !== oldCell.isUnderline()) {
sgrSeq.push(cell.isUnderline() ? 4 : 24);
}
if (cell.isOverline() !== oldCell.isOverline()) { sgrSeq.push(cell.isOverline() ? 53 : 55); }
if (cell.isBlink() !== oldCell.isBlink()) { sgrSeq.push(cell.isBlink() ? 5 : 25); }
if (cell.isInvisible() !== oldCell.isInvisible()) { sgrSeq.push(cell.isInvisible() ? 8 : 28); }
if (cell.isItalic() !== oldCell.isItalic()) { sgrSeq.push(cell.isItalic() ? 3 : 23); }
if (cell.isDim() !== oldCell.isDim()) { sgrSeq.push(cell.isDim() ? 2 : 22); }
if (cell.isStrikethrough() !== oldCell.isStrikethrough()) { sgrSeq.push(cell.isStrikethrough() ? 9 : 29); }
if (cell.isOverline() !== oldCell.isOverline()) {
sgrSeq.push(cell.isOverline() ? 53 : 55);
}
if (cell.isBlink() !== oldCell.isBlink()) {
sgrSeq.push(cell.isBlink() ? 5 : 25);
}
if (cell.isInvisible() !== oldCell.isInvisible()) {
sgrSeq.push(cell.isInvisible() ? 8 : 28);
}
if (cell.isItalic() !== oldCell.isItalic()) {
sgrSeq.push(cell.isItalic() ? 3 : 23);
}
if (cell.isDim() !== oldCell.isDim()) {
sgrSeq.push(cell.isDim() ? 2 : 22);
}
if (cell.isStrikethrough() !== oldCell.isStrikethrough()) {
sgrSeq.push(cell.isStrikethrough() ? 9 : 29);
}
}
}
}
@@ -646,8 +674,7 @@ export class HTMLSerializeHandler extends BaseSerializeHandler {
// For xterm headless: fallback to ansi colors
if ((_terminal as any)._core._themeService) {
this._ansiColors = (_terminal as any)._core._themeService.colors.ansi;
}
else {
} else {
this._ansiColors = DEFAULT_ANSI_COLORS;
}
}
@@ -752,8 +779,12 @@ export class HTMLSerializeHandler extends BaseSerializeHandler {
content.push('background-color: ' + bgHexColor + ';');
}
if (cell.isInverse()) { content.push('color: #000000; background-color: #BFBFBF;'); }
if (cell.isBold()) { content.push('font-weight: bold;'); }
if (cell.isInverse()) {
content.push('color: #000000; background-color: #BFBFBF;');
}
if (cell.isBold()) {
content.push('font-weight: bold;');
}
// Handle text-decoration (underline, overline, strikethrough, blink)
const decorations: string[] = [];
@@ -781,9 +812,15 @@ export class HTMLSerializeHandler extends BaseSerializeHandler {
}
}
if (cell.isInvisible()) { content.push('visibility: hidden;'); }
if (cell.isItalic()) { content.push('font-style: italic;'); }
if (cell.isDim()) { content.push('opacity: 0.5;'); }
if (cell.isInvisible()) {
content.push('visibility: hidden;');
}
if (cell.isItalic()) {
content.push('font-style: italic;');
}
if (cell.isDim()) {
content.push('opacity: 0.5;');
}
return content;
}
+5 -6
View File
@@ -12,11 +12,10 @@
"removeComments": true,
"paths": {
"common/*": [
"../../../src/primitives/*",
"../../../src/runtime/*",
"../../../src/common/*"
],
"target-browser/*": [
"../../../src/target-browser/*"
"browser/*": [
"../../../src/browser/*"
],
"@xterm/addon-serialize": [
"../typings/addon-serialize.d.ts"
@@ -34,10 +33,10 @@
],
"references": [
{
"path": "../../../src"
"path": "../../../src/common"
},
{
"path": "../../../src/target-browser"
"path": "../../../src/browser"
}
]
}
+5 -6
View File
@@ -11,11 +11,10 @@
"removeComments": true,
"paths": {
"common/*": [
"../../../src/primitives/*",
"../../../src/runtime/*",
"../../../src/common/*"
],
"target-browser/*": [
"../../../src/target-browser/*"
"browser/*": [
"../../../src/browser/*"
]
},
"strict": true,
@@ -29,10 +28,10 @@
],
"references": [
{
"path": "../../../src"
"path": "../../../src/common"
},
{
"path": "../../../src/target-browser"
"path": "../../../src/browser"
},
{
"path": "../../../test/playwright"
+2 -5
View File
@@ -25,11 +25,8 @@ module.exports = {
modules: ['./node_modules'],
extensions: [ '.js' ],
alias: {
common: [
path.resolve('../../out/runtime'),
path.resolve('../../out/primitives'),
],
browser: path.resolve('../../out/target-browser')
common: path.resolve('../../out/common'),
browser: path.resolve('../../out/browser')
}
},
output: {
@@ -7,7 +7,7 @@ import { perfContext, before, ThroughputRuntimeCase } from 'xterm-benchmark';
import { spawn } from 'node-pty';
import { Utf8ToUtf32, stringFromCodePoint } from 'common/input/TextDecoder';
import { CoreBrowserTerminal } from 'target-browser/CoreBrowserTerminal';
import { CoreBrowserTerminal } from 'browser/CoreBrowserTerminal';
import { UnicodeGraphemeProvider } from 'UnicodeGraphemeProvider';
@@ -4,16 +4,13 @@
"rootDir": "..",
"outDir": "../out-benchmark",
"types": ["../../../node_modules/@types/node"],
"moduleResolution": "nodenext",
"moduleResolution": "node",
"strict": false,
"target": "es2015",
"module": "nodenext",
"module": "commonjs",
"paths": {
"common/*": [
"../../../src/primitives/*",
"../../../src/runtime/*",
],
"target-browser/*": ["../../../src/target-browser/*"],
"common/*": ["../../../src/common/*"],
"browser/*": ["../../../src/browser/*"],
"UnicodeGraphemeProvider": ["../src/UnicodeGraphemeProvider"],
"@xterm/addon-unicode-graphemes": [
"../typings/addon-unicode-graphemes.d.ts"
@@ -23,7 +20,7 @@
"include": ["../**/*", "../../../typings/xterm.d.ts"],
"exclude": ["../../../**/*test.ts", "../../**/*api.ts"],
"references": [
{ "path": "../../../src" },
{ "path": "../../../src/target-browser" }
{ "path": "../../../src/common" },
{ "path": "../../../src/browser" }
]
}
@@ -4,8 +4,8 @@
*/
import { IUnicodeVersionProvider } from '@xterm/xterm';
import { createPropertyValue, extractCharKind, extractWidth } from 'common/input/UnicodeProperties';
import { UnicodeCharProperties, UnicodeCharWidth } from 'common/input/UnicodeTypes';
import { UnicodeCharProperties, UnicodeCharWidth } from 'common/services/Services';
import { UnicodeService } from 'common/services/UnicodeService';
import * as UC from './third-party/UnicodeProperties';
export class UnicodeGraphemeProvider implements IUnicodeVersionProvider {
@@ -19,7 +19,7 @@ export class UnicodeGraphemeProvider implements IUnicodeVersionProvider {
}
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 {
// Optimize the simple ASCII case, under the condition that
@@ -39,9 +39,9 @@ export class UnicodeGraphemeProvider implements IUnicodeVersionProvider {
w = 1;
}
if (preceding !== 0) {
const oldWidth = extractWidth(preceding);
const oldWidth = UnicodeService.extractWidth(preceding);
if (this.handleGraphemes) {
charInfo = UC.shouldJoin(extractCharKind(preceding), charInfo);
charInfo = UC.shouldJoin(UnicodeService.extractCharKind(preceding), charInfo);
} else {
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 {
@@ -13,8 +13,7 @@
"strict": true,
"paths": {
"common/*": [
"../../../src/primitives/*",
"../../../src/runtime/*",
"../../../src/common/*"
],
"@xterm/addon-unicode-graphemes": [
"../typings/addon-unicode-graphemes.d.ts"
@@ -30,7 +29,7 @@
],
"references": [
{
"path": "../../../src"
"path": "../../../src/common"
}
]
}
@@ -11,11 +11,10 @@
"removeComments": true,
"paths": {
"common/*": [
"../../../src/primitives/*",
"../../../src/runtime/*",
"../../../src/common/*"
],
"target-browser/*": [
"../../../src/target-browser/*"
"browser/*": [
"../../../src/browser/*"
]
},
"strict": true,
@@ -29,10 +28,10 @@
],
"references": [
{
"path": "../../../src"
"path": "../../../src/common"
},
{
"path": "../../../src/target-browser"
"path": "../../../src/browser"
},
{
"path": "../../../test/playwright"
@@ -25,10 +25,7 @@ module.exports = {
modules: ['./node_modules'],
extensions: [ '.js' ],
alias: {
common: [
path.resolve('../../out/runtime'),
path.resolve('../../out/primitives'),
],
common: path.resolve('../../out/common'),
vs: path.resolve('../../out/vs')
}
},
+4 -4
View File
@@ -4,8 +4,8 @@
*/
import { IUnicodeVersionProvider } from '@xterm/xterm';
import { createPropertyValue, extractWidth } from 'common/input/UnicodeProperties';
import { UnicodeCharProperties, UnicodeCharWidth } from 'common/input/UnicodeTypes';
import { UnicodeCharProperties, UnicodeCharWidth } from 'common/services/Services';
import { UnicodeService } from 'common/services/UnicodeService';
const BMP_COMBINING = [
[0x0300, 0x036F], [0x0483, 0x0489], [0x0591, 0x05BD],
@@ -223,13 +223,13 @@ export class UnicodeV11 implements IUnicodeVersionProvider {
let width = this.wcwidth(codepoint);
let shouldJoin = width === 0 && preceding !== 0;
if (shouldJoin) {
const oldWidth = extractWidth(preceding);
const oldWidth = UnicodeService.extractWidth(preceding);
if (oldWidth === 0) {
shouldJoin = false;
} else if (oldWidth > width) {
width = oldWidth;
}
}
return createPropertyValue(0, width, shouldJoin);
return UnicodeService.createPropertyValue(0, width, shouldJoin);
}
}
+2 -3
View File
@@ -13,8 +13,7 @@
"strict": true,
"paths": {
"common/*": [
"../../../src/primitives/*",
"../../../src/runtime/*",
"../../../src/common/*"
],
"@xterm/addon-unicode11": [
"../typings/addon-unicode11.d.ts"
@@ -33,7 +32,7 @@
],
"references": [
{
"path": "../../../src"
"path": "../../../src/common"
}
]
}
+5 -6
View File
@@ -11,11 +11,10 @@
"removeComments": true,
"paths": {
"common/*": [
"../../../src/primitives/*",
"../../../src/runtime/*",
"../../../src/common/*"
],
"target-browser/*": [
"../../../src/target-browser/*"
"browser/*": [
"../../../src/browser/*"
],
"*": [
"./*"
@@ -32,10 +31,10 @@
],
"references": [
{
"path": "../../../src"
"path": "../../../src/common"
},
{
"path": "../../../src/target-browser"
"path": "../../../src/browser"
},
{
"path": "../../../test/playwright"
+1 -4
View File
@@ -25,10 +25,7 @@ module.exports = {
modules: ['./node_modules'],
extensions: [ '.js' ],
alias: {
common: [
path.resolve('../../out/runtime'),
path.resolve('../../out/primitives'),
],
common: path.resolve('../../out/common'),
vs: path.resolve('../../out/vs')
}
},
@@ -78,22 +78,7 @@ test.describe('WebFontsAddon', () => {
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);
`);
let fonts = await getDocumentFonts();
// 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' }]);
}
deepStrictEqual(await getDocumentFonts(), [{ family: 'Kongtext', status: 'unloaded' }]);
// broken case: webfont in ctor without addon usage
await ctx.page.evaluate(`
@@ -101,20 +86,15 @@ test.describe('WebFontsAddon', () => {
window.helperTerm.open(term.element);
`);
fonts = await getDocumentFonts();
// safari might load the font, firefox & chrome dont
// safari loads the font, firefox & chrome dont
if (browser.browserType().name() === 'webkit') {
try {
deepStrictEqual(fonts, [{ family: 'Kongtext', status: 'loading' }]);
deepStrictEqual(await getDocumentFonts(), [{ family: 'Kongtext', status: 'loading' }]);
} catch (e) {
try {
deepStrictEqual(fonts, [{ family: 'Kongtext', status: 'loaded' }]);
} catch (e) {
deepStrictEqual(fonts, [{ family: 'Kongtext', status: 'unloaded' }]);
}
deepStrictEqual(await getDocumentFonts(), [{ family: 'Kongtext', status: 'loaded' }]);
}
} else {
deepStrictEqual(fonts, [{ family: 'Kongtext', status: 'unloaded' }]);
deepStrictEqual(await getDocumentFonts(), [{ family: 'Kongtext', status: 'unloaded' }]);
}
// good case: addon fixes layout for webfont in ctor
+6 -6
View File
@@ -5,17 +5,17 @@
"lib": [
"es2021",
],
// "downlevelIteration": true,
"rootDir": ".",
"outDir": "../out-test",
"sourceMap": true,
"removeComments": true,
"paths": {
"common/*": [
"../../../src/primitives/*",
"../../../src/runtime/*",
"../../../src/common/*"
],
"target-browser/*": [
"../../../src/target-browser/*"
"browser/*": [
"../../../src/browser/*"
],
"*": [
"./*"
@@ -32,10 +32,10 @@
],
"references": [
{
"path": "../../../src"
"path": "../../../src/common"
},
{
"path": "../../../src/target-browser"
"path": "../../../src/browser"
},
{
"path": "../../../test/playwright"
@@ -113,8 +113,8 @@ export class LinkComputer {
let line: IBufferLine | undefined;
let topIdx = lineIndex;
let bottomIdx = lineIndex;
let length;
let content;
let length = 0;
let content = '';
const lines: string[] = [];
if ((line = terminal.buffer.active.getLine(lineIndex))) {
+6 -6
View File
@@ -5,17 +5,17 @@
"lib": [
"es2021",
],
// "downlevelIteration": true,
"rootDir": ".",
"outDir": "../out-test",
"sourceMap": true,
"removeComments": true,
"paths": {
"common/*": [
"../../../src/primitives/*",
"../../../src/runtime/*",
"../../../src/common/*"
],
"target-browser/*": [
"../../../src/target-browser/*"
"browser/*": [
"../../../src/browser/*"
],
"*": [
"./*"
@@ -32,10 +32,10 @@
],
"references": [
{
"path": "../../../src"
"path": "../../../src/common"
},
{
"path": "../../../src/target-browser"
"path": "../../../src/browser"
},
{
"path": "../../../test/playwright"
+4 -4
View File
@@ -1,12 +1,12 @@
import { ISelectionRenderModel } from 'target-browser/renderer/shared/Types';
import { ICoreBrowserService, IThemeService } from 'target-browser/services/Services';
import { ReadonlyColorSet } from 'target-browser/Types';
import { ISelectionRenderModel } from 'browser/renderer/shared/Types';
import { ICoreBrowserService, IThemeService } from 'browser/services/Services';
import { ReadonlyColorSet } from 'browser/Types';
import { Attributes, BgFlags, ExtFlags, FgFlags, NULL_CELL_CODE, UnderlineStyle } from 'common/buffer/Constants';
import { IDecorationService, IOptionsService } from 'common/services/Services';
import { ICellData } from 'common/Types';
import { Terminal } from '@xterm/xterm';
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';
// Work variables to avoid garbage collection
+1 -1
View File
@@ -5,7 +5,7 @@
import { TextureAtlas } from './TextureAtlas';
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 { generateConfig, configEquals } from './CharAtlasUtils';
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));
});
});
});
+1 -4
View File
@@ -6,7 +6,7 @@
import { ICharAtlasConfig } from './Types';
import { Attributes } from 'common/buffer/Constants';
import { ITerminalOptions } from '@xterm/xterm';
import { IColorSet, ReadonlyColorSet } from 'target-browser/Types';
import { IColorSet, ReadonlyColorSet } from 'browser/Types';
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 {
@@ -59,7 +59,6 @@ export function configEquals(a: ICharAtlasConfig, b: ICharAtlasConfig): boolean
}
}
return a.devicePixelRatio === b.devicePixelRatio &&
a.deviceMaxTextureSize === b.deviceMaxTextureSize &&
a.customGlyphs === b.customGlyphs &&
a.lineHeight === b.lineHeight &&
a.letterSpacing === b.letterSpacing &&
@@ -68,8 +67,6 @@ export function configEquals(a: ICharAtlasConfig, b: ICharAtlasConfig): boolean
a.fontWeight === b.fontWeight &&
a.fontWeightBold === b.fontWeightBold &&
a.allowTransparency === b.allowTransparency &&
a.deviceCellWidth === b.deviceCellWidth &&
a.deviceCellHeight === b.deviceCellHeight &&
a.deviceCharWidth === b.deviceCharWidth &&
a.deviceCharHeight === b.deviceCharHeight &&
a.drawBoldTextInBrightColors === b.drawBoldTextInBrightColors &&
@@ -3,8 +3,8 @@
* @license MIT
*/
import { RendererConstants } from 'target-browser/renderer/shared/Constants';
import { ICoreBrowserService } from 'target-browser/services/Services';
import { RendererConstants } from 'browser/renderer/shared/Constants';
import { ICoreBrowserService } from 'browser/services/Services';
const enum Constants {
/**
@@ -40,7 +40,9 @@ export class CursorBlinkStateManager {
}
}
public get isPaused(): boolean { return !(this._blinkStartTimeout || this._blinkInterval); }
public get isPaused(): boolean {
return !(this._blinkStartTimeout || this._blinkInterval);
}
public dispose(): void {
if (this._blinkInterval) {
+7 -9
View File
@@ -3,14 +3,14 @@
* @license MIT
*/
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 { Disposable, toDisposable } from 'common/Lifecycle';
import { Terminal } from '@xterm/xterm';
import { IRenderModel, IWebGL2RenderingContext, IWebGLVertexArrayObject, type IRasterizedGlyph, type ITextureAtlas } from './Types';
import { createProgram, GLTexture, PROJECTION_MATRIX } from './WebglUtils';
import type { ILogService, IOptionsService } from 'common/services/Services';
import { allowRescaling, throwIfFalsy } from 'target-browser/renderer/shared/RendererUtils';
import type { IOptionsService } from 'common/services/Services';
import { allowRescaling, throwIfFalsy } from 'browser/renderer/shared/RendererUtils';
interface IVertices {
attributes: Float32Array;
@@ -114,8 +114,7 @@ export class GlyphRenderer extends Disposable {
private readonly _terminal: Terminal,
private readonly _gl: IWebGL2RenderingContext,
private _dimensions: IRenderDimensions,
private readonly _optionsService: IOptionsService,
private readonly _logService: ILogService
private readonly _optionsService: IOptionsService
) {
super();
@@ -128,7 +127,7 @@ export class GlyphRenderer extends Disposable {
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)));
// Uniform locations
@@ -137,9 +136,8 @@ export class GlyphRenderer extends Disposable {
this._textureLocation = throwIfFalsy(gl.getUniformLocation(this._program, 'u_texture'));
// Create and set the vertex array object
const vertexArrayObject = this._vertexArrayObject = gl.createVertexArray();
this._register(toDisposable(() => gl.deleteVertexArray(vertexArrayObject)));
gl.bindVertexArray(vertexArrayObject);
this._vertexArrayObject = gl.createVertexArray();
gl.bindVertexArray(this._vertexArrayObject);
// Setup a_unitquad, this defines the 4 vertices of a rectangle
const unitQuadVertices = new Float32Array([0, 0, 1, 0, 0, 1, 1, 1]);
+8 -11
View File
@@ -3,9 +3,9 @@
* @license MIT
*/
import { IRenderDimensions } from 'target-browser/renderer/shared/Types';
import { IThemeService } from 'target-browser/services/Services';
import { ReadonlyColorSet } from 'target-browser/Types';
import { IRenderDimensions } from 'browser/renderer/shared/Types';
import { IThemeService } from 'browser/services/Services';
import { ReadonlyColorSet } from 'browser/Types';
import { Attributes, FgFlags } from 'common/buffer/Constants';
import { Disposable, toDisposable } from 'common/Lifecycle';
import { IColor } from 'common/Types';
@@ -13,8 +13,7 @@ import { Terminal } from '@xterm/xterm';
import { RenderModelConstants } from './RenderModel';
import { IRenderModel, IWebGL2RenderingContext, IWebGLVertexArrayObject } from './Types';
import { createProgram, expandFloat32Array, PROJECTION_MATRIX } from './WebglUtils';
import { throwIfFalsy } from 'target-browser/renderer/shared/RendererUtils';
import type { ILogService } from 'common/services/Services';
import { throwIfFalsy } from 'browser/renderer/shared/RendererUtils';
const enum VertexAttribLocations {
POSITION = 0,
@@ -90,23 +89,21 @@ export class RectangleRenderer extends Disposable {
private _terminal: Terminal,
private _gl: IWebGL2RenderingContext,
private _dimensions: IRenderDimensions,
private readonly _themeService: IThemeService,
private readonly _logService: ILogService
private readonly _themeService: IThemeService
) {
super();
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)));
// Uniform locations
this._projectionLocation = throwIfFalsy(gl.getUniformLocation(this._program, 'u_projection'));
// Create and set the vertex array object
const vertexArrayObject = this._vertexArrayObject = gl.createVertexArray();
this._register(toDisposable(() => gl.deleteVertexArray(vertexArrayObject)));
gl.bindVertexArray(vertexArrayObject);
this._vertexArrayObject = gl.createVertexArray();
gl.bindVertexArray(this._vertexArrayObject);
// Setup a_unitquad, this defines the 4 vertices of a rectangle
const unitQuadVertices = new Float32Array([0, 0, 1, 0, 0, 1, 1, 1]);
+2 -2
View File
@@ -4,8 +4,8 @@
*/
import { ICursorRenderModel, IRenderModel } from './Types';
import { ISelectionRenderModel } from 'target-browser/renderer/shared/Types';
import { createSelectionRenderModel } from 'target-browser/renderer/shared/SelectionRenderModel';
import { ISelectionRenderModel } from 'browser/renderer/shared/Types';
import { createSelectionRenderModel } from 'browser/renderer/shared/SelectionRenderModel';
export const enum RenderModelConstants {
INDICIES_PER_CELL = 4,
+12 -7
View File
@@ -3,10 +3,10 @@
* @license MIT
*/
import { IColorContrastCache } from 'target-browser/Types';
import { IColorContrastCache } from 'browser/Types';
import { DIM_OPACITY, TEXT_BASELINE } from './Constants';
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 { NULL_COLOR, channels, color, rgba } from 'common/Color';
import { FourKeyMap } from 'common/MultiKeyMap';
@@ -62,7 +62,9 @@ export class TextureAtlas implements ITextureAtlas {
// The texture that the atlas is drawn to
private _pages: AtlasPage[] = [];
public get pages(): { canvas: HTMLCanvasElement, version: number }[] { return this._pages; }
public get pages(): { canvas: HTMLCanvasElement, version: number }[] {
return this._pages;
}
// The set of atlas pages that can be written to
private _activePages: AtlasPage[] = [];
@@ -109,7 +111,6 @@ export class TextureAtlas implements ITextureAtlas {
page.canvas.remove();
}
this._onAddTextureAtlasCanvas.dispose();
this._onRemoveTextureAtlasCanvas.dispose();
}
public warmUp(): void {
@@ -530,7 +531,7 @@ export class TextureAtlas implements ITextureAtlas {
let customGlyph = false;
if (this._config.customGlyphs !== false) {
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
@@ -1040,10 +1041,14 @@ class AtlasPage {
public readonly ctx: CanvasRenderingContext2D;
private _usedPixels: number = 0;
public get percentageUsed(): number { return this._usedPixels / (this.canvas.width * this.canvas.height); }
public get percentageUsed(): number {
return this._usedPixels / (this.canvas.width * this.canvas.height);
}
private readonly _glyphs: IRasterizedGlyph[] = [];
public get glyphs(): ReadonlyArray<IRasterizedGlyph> { return this._glyphs; }
public get glyphs(): ReadonlyArray<IRasterizedGlyph> {
return this._glyphs;
}
public addGlyph(glyph: IRasterizedGlyph): void {
this._glyphs.push(glyph);
this._usedPixels += glyph.size.x * glyph.size.y;
+2 -3
View File
@@ -4,8 +4,8 @@
*/
import { FontWeight } from '@xterm/xterm';
import { IColorSet } from 'target-browser/Types';
import { ISelectionRenderModel } from 'target-browser/renderer/shared/Types';
import { IColorSet } from 'browser/Types';
import { ISelectionRenderModel } from 'browser/renderer/shared/Types';
import { CursorInactiveStyle, CursorStyle, type IDisposable } from 'common/Types';
import type { IEvent } from 'common/Event';
@@ -28,7 +28,6 @@ export interface ICursorRenderModel {
export interface IWebGL2RenderingContext extends WebGLRenderingContext {
vertexAttribDivisor(index: number, divisor: number): void;
createVertexArray(): IWebGLVertexArrayObject;
deleteVertexArray(vao: IWebGLVertexArrayObject): void;
bindVertexArray(vao: IWebGLVertexArrayObject): void;
drawElementsInstanced(mode: number, count: number, type: number, offset: number, instanceCount: number): void;
}
+3 -5
View File
@@ -5,11 +5,11 @@
import type { ITerminalAddon, Terminal } from '@xterm/xterm';
import type { IWebglAddonOptions, WebglAddon as IWebglApi } from '@xterm/addon-webgl';
import { ICharacterJoinerService, ICharSizeService, ICoreBrowserService, IRenderService, IThemeService } from 'target-browser/services/Services';
import { ITerminal } from 'target-browser/Types';
import { ICharacterJoinerService, ICharSizeService, ICoreBrowserService, IRenderService, IThemeService } from 'browser/services/Services';
import { ITerminal } from 'browser/Types';
import { Disposable, toDisposable } from 'common/Lifecycle';
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 { WebglRenderer } from './WebglRenderer';
import { Emitter, EventUtils } from 'common/Event';
@@ -65,7 +65,6 @@ export class WebglAddon extends Disposable implements ITerminalAddon, IWebglApi
const charSizeService: ICharSizeService = unsafeCore._charSizeService;
const coreBrowserService: ICoreBrowserService = unsafeCore._coreBrowserService;
const decorationService: IDecorationService = unsafeCore._decorationService;
const logService: ILogService = unsafeCore._logService;
const themeService: IThemeService = unsafeCore._themeService;
this._renderer = this._register(new WebglRenderer(
@@ -75,7 +74,6 @@ export class WebglAddon extends Disposable implements ITerminalAddon, IWebglApi
coreBrowserService,
coreService,
decorationService,
logService,
optionsService,
themeService,
this._customGlyphs,
+14 -17
View File
@@ -3,19 +3,19 @@
* @license MIT
*/
import { ITerminal } from 'target-browser/Types';
import { ITerminal } from 'browser/Types';
import { CellColorResolver } from './CellColorResolver';
import { acquireTextureAtlas, removeTerminalFromCache } from './CharAtlasCache';
import { CursorBlinkStateManager } from './CursorBlinkStateManager';
import { observeDevicePixelDimensions } from './DevicePixelObserver';
import { IRenderDimensions, IRenderer, IRequestRedrawEvent } from 'target-browser/renderer/shared/Types';
import { ICharSizeService, ICharacterJoinerService, ICoreBrowserService, IThemeService } from 'target-browser/services/Services';
import { IRenderDimensions, IRenderer, IRequestRedrawEvent } from 'browser/renderer/shared/Types';
import { ICharSizeService, ICharacterJoinerService, ICoreBrowserService, IThemeService } from 'browser/services/Services';
import { CharData, IBufferLine, ICellData } from 'common/Types';
import { AttributeData } from 'common/buffer/AttributeData';
import { CellData } from 'common/buffer/CellData';
import { Attributes, Content, FgFlags, NULL_CELL_CHAR, NULL_CELL_CODE } from 'common/buffer/Constants';
import { TextBlinkStateManager } from 'target-browser/renderer/shared/TextBlinkStateManager';
import { ICoreService, IDecorationService, ILogService, IOptionsService } from 'common/services/Services';
import { TextBlinkStateManager } from 'browser/renderer/shared/TextBlinkStateManager';
import { ICoreService, IDecorationService, IOptionsService } from 'common/services/Services';
import { Terminal } from '@xterm/xterm';
import { GlyphRenderer } from './GlyphRenderer';
import { RectangleRenderer } from './RectangleRenderer';
@@ -24,9 +24,9 @@ import { IWebGL2RenderingContext, type ITextureAtlas } from './Types';
import { LinkRenderLayer } from './renderLayer/LinkRenderLayer';
import { IRenderLayer } from './renderLayer/Types';
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 { createRenderDimensions } from 'target-browser/renderer/shared/RendererUtils';
import { createRenderDimensions } from 'browser/renderer/shared/RendererUtils';
const enum Constants {
MERGE_RETRY_LIMIT = 32
@@ -77,7 +77,6 @@ export class WebglRenderer extends Disposable implements IRenderer {
private readonly _coreBrowserService: ICoreBrowserService,
private readonly _coreService: ICoreService,
private readonly _decorationService: IDecorationService,
private readonly _logService: ILogService,
private readonly _optionsService: IOptionsService,
private readonly _themeService: IThemeService,
private readonly _customGlyphs: boolean = true,
@@ -123,19 +122,19 @@ export class WebglRenderer extends Disposable implements IRenderer {
this._deviceMaxTextureSize = this._gl.getParameter(this._gl.MAX_TEXTURE_SIZE);
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.
e.preventDefault();
// Wait a few seconds to see if the 'webglcontextrestored' event is fired.
// If not, dispatch the onContextLoss notification to observers.
this._contextRestorationTimeout = setTimeout(() => {
this._contextRestorationTimeout = undefined;
this._logService.warn('webgl context not restored; firing onContextLoss');
console.warn('webgl context not restored; firing onContextLoss');
this._onContextLoss.fire(e);
}, 3000 /* ms */);
}));
this._register(addDisposableListener(this._canvas, 'webglcontextrestored', (e) => {
this._logService.warn('webglcontextrestored event received');
console.warn('webglcontextrestored event received');
clearTimeout(this._contextRestorationTimeout);
this._contextRestorationTimeout = undefined;
// The texture atlas and glyph renderer must be fully reinitialized
@@ -159,8 +158,6 @@ export class WebglRenderer extends Disposable implements IRenderer {
this._isAttached = this._core.screenElement!.isConnected;
this._register(toDisposable(() => {
clearTimeout(this._contextRestorationTimeout);
this._contextRestorationTimeout = undefined;
for (const l of this._renderLayers) {
l.dispose();
}
@@ -277,8 +274,8 @@ export class WebglRenderer extends Disposable implements IRenderer {
* Initializes members dependent on WebGL context state.
*/
private _initializeWebGLState(): [RectangleRenderer, GlyphRenderer] {
this._rectangleRenderer.value = new RectangleRenderer(this._terminal, this._gl, this.dimensions, this._themeService, this._logService);
this._glyphRenderer.value = new GlyphRenderer(this._terminal, this._gl, this.dimensions, this._optionsService, 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);
// Update dimensions and acquire char atlas
this.handleCharSizeChanged();
@@ -427,8 +424,8 @@ export class WebglRenderer extends Disposable implements IRenderer {
let line: IBufferLine;
let joinedRanges: [number, number][];
let isJoined: boolean;
let skipJoinedCheckUntilX: number;
let isValidJoinRange: boolean;
let skipJoinedCheckUntilX: number = 0;
let isValidJoinRange: boolean = true;
let lastCharX: number;
let range: [number, number];
let isCursorRow: boolean;
+7 -8
View File
@@ -3,8 +3,7 @@
* @license MIT
*/
import { throwIfFalsy } from 'target-browser/renderer/shared/RendererUtils';
import type { ILogService } from 'common/services/Services';
import { throwIfFalsy } from 'browser/renderer/shared/RendererUtils';
/**
* 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
]);
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());
gl.attachShader(program, throwIfFalsy(createShader(gl, gl.VERTEX_SHADER, vertexSource, logService)));
gl.attachShader(program, throwIfFalsy(createShader(gl, gl.FRAGMENT_SHADER, fragmentSource, logService)));
gl.attachShader(program, throwIfFalsy(createShader(gl, gl.VERTEX_SHADER, vertexSource)));
gl.attachShader(program, throwIfFalsy(createShader(gl, gl.FRAGMENT_SHADER, fragmentSource)));
gl.linkProgram(program);
const success = gl.getProgramParameter(program, gl.LINK_STATUS);
if (success) {
return program;
}
logService.error(gl.getProgramInfoLog(program));
console.error(gl.getProgramInfoLog(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));
gl.shaderSource(shader, source);
gl.compileShader(shader);
@@ -40,7 +39,7 @@ export function createShader(gl: WebGLRenderingContext, type: number, source: st
return shader;
}
logService.error(gl.getShaderInfoLog(shader));
console.error(gl.getShaderInfoLog(shader));
gl.deleteShader(shader);
}
@@ -3,8 +3,7 @@
* @license MIT
*/
import { throwIfFalsy } from 'target-browser/renderer/shared/RendererUtils';
import type { ILogService } from 'common/services/Services';
import { throwIfFalsy } from 'browser/renderer/shared/RendererUtils';
import { customGlyphDefinitions } from './CustomGlyphDefinitions';
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,
fontSize: number,
devicePixelRatio: number,
logService: ILogService,
backgroundColor?: string,
variantOffset: number = 0
): boolean {
@@ -32,7 +30,7 @@ export function tryDrawCustomGlyph(
// Normalize to array for uniform handling
const parts = Array.isArray(unifiedCharDefinition) ? unifiedCharDefinition : [unifiedCharDefinition];
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;
}
@@ -51,7 +49,6 @@ function drawDefinitionPart(
deviceCharHeight: number,
fontSize: number,
devicePixelRatio: number,
logService: ILogService,
backgroundColor?: string,
variantOffset: number = 0
): void {
@@ -82,7 +79,7 @@ function drawDefinitionPart(
drawPatternChar(ctx, part.data, drawXOffset, drawYOffset, drawWidth, drawHeight, variantOffset);
break;
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;
case CustomGlyphDefinitionType.PATH:
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);
break;
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;
case CustomGlyphDefinitionType.BRAILLE:
drawBrailleCharacter(ctx, part.data, drawXOffset, drawYOffset, drawWidth, drawHeight);
@@ -513,7 +510,6 @@ function drawPathFunctionCharacter(
deviceCellWidth: number,
deviceCellHeight: number,
devicePixelRatio: number,
logService: ILogService,
strokeWidth?: number
): void {
ctx.save();
@@ -540,7 +536,7 @@ function drawPathFunctionCharacter(
}
const f = svgToCanvasInstructionMap[type];
if (!f) {
logService.error(`Could not find drawing instructions for "${type}"`);
console.error(`Could not find drawing instructions for "${type}"`);
continue;
}
const args: string[] = instruction.substring(1).split(',');
@@ -602,8 +598,7 @@ function drawVectorShape(
deviceCellWidth: number,
deviceCellHeight: number,
fontSize: number,
devicePixelRatio: number,
logService: ILogService
devicePixelRatio: number
): void {
// Clip the cell to make sure drawing doesn't occur beyond bounds
const clipRegion = new Path2D();
@@ -624,7 +619,7 @@ function drawVectorShape(
}
const f = svgToCanvasInstructionMap[type];
if (!f) {
logService.error(`Could not find drawing instructions for "${type}"`);
console.error(`Could not find drawing instructions for "${type}"`);
continue;
}
const args: string[] = instruction.substring(1).split(',');
@@ -3,16 +3,16 @@
* @license MIT
*/
import { ReadonlyColorSet } from 'target-browser/Types';
import { ReadonlyColorSet } from 'browser/Types';
import { acquireTextureAtlas } from '../CharAtlasCache';
import { IRenderDimensions } from 'target-browser/renderer/shared/Types';
import { ICoreBrowserService, IThemeService } from 'target-browser/services/Services';
import { IRenderDimensions } from 'browser/renderer/shared/Types';
import { ICoreBrowserService, IThemeService } from 'browser/services/Services';
import { Disposable, toDisposable } from 'common/Lifecycle';
import { CellData } from 'common/buffer/CellData';
import { IOptionsService } from 'common/services/Services';
import { Terminal } from '@xterm/xterm';
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 type { ITextureAtlas } from '../Types';
@@ -4,10 +4,10 @@
*/
import { is256Color } from '../CharAtlasUtils';
import { INVERTED_DEFAULT_COLOR } from 'target-browser/renderer/shared/Constants';
import { IRenderDimensions } from 'target-browser/renderer/shared/Types';
import { ICoreBrowserService, IThemeService } from 'target-browser/services/Services';
import { ILinkifier2, ILinkifierEvent } from 'target-browser/Types';
import { INVERTED_DEFAULT_COLOR } from 'browser/renderer/shared/Constants';
import { IRenderDimensions } from 'browser/renderer/shared/Types';
import { ICoreBrowserService, IThemeService } from 'browser/services/Services';
import { ILinkifier2, ILinkifierEvent } from 'browser/Types';
import { IOptionsService } from 'common/services/Services';
import { Terminal } from '@xterm/xterm';
import { BaseRenderLayer } from './BaseRenderLayer';
+1 -1
View File
@@ -4,7 +4,7 @@
*/
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 {
/**
+6 -6
View File
@@ -12,11 +12,10 @@
"removeComments": true,
"paths": {
"common/*": [
"../../../src/primitives/*",
"../../../src/runtime/*",
"../../../src/common/*"
],
"target-browser/*": [
"../../../src/target-browser/*"
"browser/*": [
"../../../src/browser/*"
],
"@xterm/addon-webgl": [
"../typings/addon-webgl.d.ts"
@@ -26,6 +25,7 @@
]
},
"strict": true,
"downlevelIteration": true,
"experimentalDecorators": true,
"types": [
"../../../node_modules/@types/mocha"
@@ -37,10 +37,10 @@
],
"references": [
{
"path": "../../../src"
"path": "../../../src/common"
},
{
"path": "../../../src/target-browser"
"path": "../../../src/browser"
}
]
}
+5 -6
View File
@@ -11,11 +11,10 @@
"removeComments": true,
"paths": {
"common/*": [
"../../../src/primitives/*",
"../../../src/runtime/*",
"../../../src/common/*"
],
"target-browser/*": [
"../../../src/target-browser/*"
"browser/*": [
"../../../src/browser/*"
],
"*": [
"./*"
@@ -33,10 +32,10 @@
],
"references": [
{
"path": "../../../src"
"path": "../../../src/common"
},
{
"path": "../../../src/target-browser"
"path": "../../../src/browser"
},
{
"path": "../../../test/playwright"
+2 -5
View File
@@ -25,11 +25,8 @@ module.exports = {
modules: ['./node_modules'],
extensions: [ '.js' ],
alias: {
common: [
path.resolve('../../out/runtime'),
path.resolve('../../out/primitives'),
],
browser: path.resolve('../../out/target-browser'),
common: path.resolve('../../out/common'),
browser: path.resolve('../../out/browser'),
vs: path.resolve('../../out/vs')
}
},
+13 -37
View File
@@ -92,9 +92,6 @@ let outConfig = {
}
let skipOut = false;
/** @type {esbuild.BuildOptions[] | undefined} */
let outBuilds;
/** @type {esbuild.BuildOptions} */
let outTestConfig = {
format: 'cjs',
@@ -174,7 +171,7 @@ if (config.addon) {
} else if (config.isHeadless) {
bundleConfig = {
...bundleConfig,
entryPoints: [`src/target-headless/public/Terminal.ts`],
entryPoints: [`src/headless/public/Terminal.ts`],
outfile: `headless/lib-headless/xterm-headless.mjs`
};
outConfig = {
@@ -186,35 +183,18 @@ if (config.addon) {
} else {
bundleConfig = {
...bundleConfig,
entryPoints: [`src/target-browser/public/Terminal.ts`],
entryPoints: [`src/browser/public/Terminal.ts`],
outfile: `lib/xterm.mjs`
};
outBuilds = [
{
...outConfig,
entryPoints: ['src/primitives/**/*.ts'],
outbase: 'src/primitives',
outdir: 'out-esbuild/common',
},
{
...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',
},
];
outConfig = {
...outConfig,
entryPoints: [
'src/browser/**/*.ts',
'src/common/**/*.ts',
'src/headless/**/*.ts'
],
outdir: 'out-esbuild/'
};
outTestConfig = {
...outConfig,
entryPoints: ['test/**/*.ts'],
@@ -225,9 +205,7 @@ if (config.addon) {
if (config.isWatch) {
context(bundleConfig).then(e => e.watch());
if (!skipOut) {
for (const buildConfig of outBuilds ?? [outConfig]) {
context(buildConfig).then(e => e.watch());
}
context(outConfig).then(e => e.watch());
}
if (!skipOutTest) {
context(outTestConfig).then(e => e.watch());
@@ -235,9 +213,7 @@ if (config.isWatch) {
} else {
await build(bundleConfig);
if (!skipOut) {
for (const buildConfig of outBuilds ?? [outConfig]) {
await build(buildConfig);
}
await build(outConfig);
}
if (!skipOutTest) {
await build(outTestConfig);
+1 -1
View File
@@ -81,7 +81,7 @@ function checkAndPublishPackage(packageDir, repoCommit, peerDependencies, update
// Update Version.ts with the new version
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 updatedVersionTs = versionTsContent.replace(
/export const XTERM_VERSION = '[^']+';/,
+1 -1
View File
@@ -38,4 +38,4 @@ function findUnusedSymbols(
}
// Example usage
findUnusedSymbols("./src/target-browser/tsconfig.json");
findUnusedSymbols("./src/browser/tsconfig.json");
+1 -2
View File
@@ -521,8 +521,7 @@ function initAddons(term: Terminal): void {
} else if (name === 'webLinks') {
controlBar.setTabVisible('addon-web-links', true);
}
}
catch {
} catch {
addon.instance = undefined;
checkbox.checked = false;
checkbox.disabled = true;
@@ -65,8 +65,7 @@ export class AddonImageWindow extends BaseWindow implements IControlWindow {
const dtIip = document.createElement('dt');
dtIip.textContent = 'IIP (iTerm)';
dlIip.appendChild(dtIip);
this._addDdWithButton(dlIip, 'image-demo3', 'palette (png File)');
this._addDdWithButton(dlIip, 'image-demo4', 'dice (qoi MultipartFile)');
this._addDdWithButton(dlIip, 'image-demo3', 'palette');
container.appendChild(dlIip);
// 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\\`);
});
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)
.then(resp => resp.arrayBuffer())
.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'));
document.getElementById('image-demo3')!.addEventListener('click',
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',
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 y = pos[1] - 1;
const canvas = ev.shiftKey
@@ -30,7 +30,9 @@ export class AddonWebFontsWindow extends BaseWindow implements IControlWindow {
this._terminal.options.lineHeight = 1.3;
this._addons.fit.instance?.fit();
setTimeout(() => this._terminal.write('\x1b[?12h\x1b]12;#776CF9\x07\x1b[38;2;119;108;249;48;2;21;8;150m\x1b[2J\x1b[2;5H**** COMMODORE 64 BASIC V2 ****\r\n\r\n 64K RAM SYSTEM 38911 BASIC BYTES FREE\r\n\r\nREADY.\r\nLOAD '), 1000);
setTimeout(() => { this._terminal.write('🤣\x1b[m\x1b[99;1H'); this._terminal.input('\r'); }, 5000);
setTimeout(() => {
this._terminal.write('🤣\x1b[m\x1b[99;1H'); this._terminal.input('\r');
}, 5000);
});
ddKongtext.appendChild(btnKongtext);
dl.appendChild(ddKongtext);
+3 -1
View File
@@ -8,7 +8,9 @@ import type { IControlWindow } from '../controlBar';
import type { Terminal } from '@xterm/xterm';
export abstract class BaseWindow implements IControlWindow {
protected get _terminal(): Terminal { return this._terminalPrivate; }
protected get _terminal(): Terminal {
return this._terminalPrivate;
}
constructor(
private _terminalPrivate: Terminal,
-1
View File
@@ -2,7 +2,6 @@
"compilerOptions": {
"module": "commonjs",
"target": "es2021",
"types": ["node"],
"outDir": "../out-demo/client",
"rootDir": ".",
"sourceMap": true,
+1 -1
View File
@@ -118,7 +118,7 @@ function startServer(): void {
});
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);
temporaryDisposable[term.pid].dispose();
delete temporaryDisposable[term.pid];
+2 -3
View File
@@ -37,13 +37,12 @@ export default tseslint.config(
'@stylistic/comma-spacing': ['warn', { before: false, after: true }],
'@stylistic/indent': ['warn', 2],
'@stylistic/semi': ['warn', 'always'],
'@stylistic/quotes': ['warn', 'single', { allowTemplateLiterals: 'always' }],
'@stylistic/quotes': ['warn', 'single', { allowTemplateLiterals: true }],
'@stylistic/member-delimiter-style': ['warn', {
multiline: { delimiter: 'semi', requireLast: true },
singleline: { delimiter: 'comma', requireLast: false }
}],
'@stylistic/type-annotation-spacing': 'warn',
'@stylistic/space-unary-ops': 'warn',
'@typescript-eslint/array-type': ['warn', { default: 'array', readonly: 'generic' }],
'@typescript-eslint/consistent-type-assertions': 'warn',
@@ -86,7 +85,6 @@ export default tseslint.config(
'eqeqeq': ['warn', 'always'],
'jsdoc/check-alignment': 'warn',
'jsdoc/check-param-names': 'warn',
'jsdoc/multiline-blocks': 'warn',
'jsdoc/no-multi-asterisks': 'warn',
'keyword-spacing': 'warn',
'max-len': ['warn', {
@@ -130,6 +128,7 @@ export default tseslint.config(
'no-fallthrough': 'off',
'prefer-spread': 'off',
'object-curly-spacing': ['warn', 'always'],
'brace-style': ['warn', '1tbs', { allowSingleLine: false }],
'prefer-const': 'warn',
'spaced-comment': ['warn', 'always', { markers: ['/'], exceptions: ['-'] }]
}
+1 -1
View File
@@ -17,7 +17,7 @@ export default tseslint.config(
rules: {
'@stylistic/indent': ['warn', 2],
'@stylistic/semi': ['warn', 'always'],
'@stylistic/quotes': ['warn', 'single', { allowTemplateLiterals: 'always' }],
'@stylistic/quotes': ['warn', 'single', { allowTemplateLiterals: true }],
'@stylistic/member-delimiter-style': ['warn', {
multiline: { delimiter: 'semi', requireLast: true },
singleline: { delimiter: 'comma', requireLast: false }

Some files were not shown because too many files have changed in this diff Show More