Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
511ce3f5d3 | ||
|
|
cf68f0b514 | ||
|
|
bd8510e3ab | ||
|
|
f6c1b21258 | ||
|
|
b7d45bfefa | ||
|
|
701c0affd5 |
@@ -6,19 +6,14 @@ on:
|
||||
pull_request:
|
||||
branches: [ "master" ]
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 10
|
||||
steps:
|
||||
- uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5
|
||||
with:
|
||||
persist-credentials: false
|
||||
- uses: actions/checkout@v5
|
||||
- name: Use Node.js 22.x
|
||||
uses: actions/setup-node@a0853c24544627f65ddf259abe73b1d18a591444 # v5
|
||||
uses: actions/setup-node@v5
|
||||
with:
|
||||
node-version: 22.x
|
||||
cache: 'npm'
|
||||
@@ -74,7 +69,7 @@ jobs:
|
||||
./addons/addon-webgl/out/* \
|
||||
./addons/addon-webgl/out-*st/*
|
||||
- name: Upload artifacts
|
||||
uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6
|
||||
uses: actions/upload-artifact@v6
|
||||
with:
|
||||
name: build-artifacts
|
||||
path: compressed-build.zip
|
||||
@@ -84,11 +79,9 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 10
|
||||
steps:
|
||||
- uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5
|
||||
with:
|
||||
persist-credentials: false
|
||||
- uses: actions/checkout@v5
|
||||
- name: Use Node.js 22.x
|
||||
uses: actions/setup-node@a0853c24544627f65ddf259abe73b1d18a591444 # v5
|
||||
uses: actions/setup-node@v5
|
||||
with:
|
||||
node-version: 22.x
|
||||
cache: 'npm'
|
||||
@@ -107,18 +100,16 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 10
|
||||
steps:
|
||||
- uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5
|
||||
with:
|
||||
persist-credentials: false
|
||||
- uses: actions/checkout@v5
|
||||
- name: Use Node.js 22.x
|
||||
uses: actions/setup-node@a0853c24544627f65ddf259abe73b1d18a591444 # v5
|
||||
uses: actions/setup-node@v5
|
||||
with:
|
||||
node-version: 22.x
|
||||
cache: 'npm'
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
npm ci
|
||||
- uses: actions/download-artifact@37930b1c2abaa49bbe596cd826c3c89aef350131 # v7
|
||||
- uses: actions/download-artifact@v7
|
||||
with:
|
||||
name: build-artifacts
|
||||
- name: Unzip artifacts
|
||||
@@ -146,18 +137,16 @@ jobs:
|
||||
runs-on: [ubuntu, macos, windows]
|
||||
runs-on: ${{ matrix.runs-on }}-latest
|
||||
steps:
|
||||
- uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5
|
||||
with:
|
||||
persist-credentials: false
|
||||
- uses: actions/checkout@v5
|
||||
- name: Use Node.js ${{ matrix.node-version }}.x
|
||||
uses: actions/setup-node@a0853c24544627f65ddf259abe73b1d18a591444 # v5
|
||||
uses: actions/setup-node@v5
|
||||
with:
|
||||
node-version: ${{ matrix.node-version }}.x
|
||||
cache: 'npm'
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
npm ci
|
||||
- uses: actions/download-artifact@37930b1c2abaa49bbe596cd826c3c89aef350131 # v7
|
||||
- uses: actions/download-artifact@v7
|
||||
with:
|
||||
name: build-artifacts
|
||||
- name: Unzip artifacts
|
||||
@@ -182,11 +171,9 @@ jobs:
|
||||
browser: [chromium, firefox, webkit]
|
||||
runs-on: ${{ matrix.runs-on }}
|
||||
steps:
|
||||
- uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5
|
||||
with:
|
||||
persist-credentials: false
|
||||
- uses: actions/checkout@v5
|
||||
- name: Use Node.js ${{ matrix.node-version }}.x
|
||||
uses: actions/setup-node@a0853c24544627f65ddf259abe73b1d18a591444 # v5
|
||||
uses: actions/setup-node@v5
|
||||
with:
|
||||
node-version: ${{ matrix.node-version }}.x
|
||||
cache: 'npm'
|
||||
@@ -195,7 +182,7 @@ jobs:
|
||||
npm ci
|
||||
- name: Install playwright
|
||||
run: npx playwright install --with-deps ${{ matrix.browser }}
|
||||
- uses: actions/download-artifact@37930b1c2abaa49bbe596cd826c3c89aef350131 # v7
|
||||
- uses: actions/download-artifact@v7
|
||||
with:
|
||||
name: build-artifacts
|
||||
- name: Unzip artifacts
|
||||
@@ -245,11 +232,9 @@ jobs:
|
||||
matrix:
|
||||
node-version: [22]
|
||||
steps:
|
||||
- uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5
|
||||
with:
|
||||
persist-credentials: false
|
||||
- uses: actions/checkout@v5
|
||||
- name: Use Node.js ${{ matrix.node-version }}.x
|
||||
uses: actions/setup-node@a0853c24544627f65ddf259abe73b1d18a591444 # v5
|
||||
uses: actions/setup-node@v5
|
||||
with:
|
||||
node-version: ${{ matrix.node-version }}.x
|
||||
cache: 'npm'
|
||||
@@ -258,7 +243,7 @@ jobs:
|
||||
npm ci
|
||||
- name: Install playwright
|
||||
run: npx playwright install
|
||||
- uses: actions/download-artifact@37930b1c2abaa49bbe596cd826c3c89aef350131 # v7
|
||||
- uses: actions/download-artifact@v7
|
||||
with:
|
||||
name: build-artifacts
|
||||
- name: Unzip artifacts
|
||||
|
||||
@@ -8,11 +8,6 @@ on:
|
||||
schedule:
|
||||
- cron: '41 17 * * 0'
|
||||
|
||||
permissions:
|
||||
actions: read
|
||||
contents: read
|
||||
security-events: write
|
||||
|
||||
jobs:
|
||||
analyze:
|
||||
name: Analyze
|
||||
@@ -29,14 +24,12 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5
|
||||
with:
|
||||
persist-credentials: false
|
||||
uses: actions/checkout@v5
|
||||
- name: Initialize CodeQL
|
||||
uses: github/codeql-action/init@8aad20d150bbac5944a9f9d289da16a4b0d87c1e # v4
|
||||
uses: github/codeql-action/init@v4
|
||||
with:
|
||||
languages: ${{ matrix.language }}
|
||||
- name: Autobuild
|
||||
uses: github/codeql-action/autobuild@8aad20d150bbac5944a9f9d289da16a4b0d87c1e # v4
|
||||
uses: github/codeql-action/autobuild@v4
|
||||
- name: Perform CodeQL Analysis
|
||||
uses: github/codeql-action/analyze@8aad20d150bbac5944a9f9d289da16a4b0d87c1e # v4
|
||||
uses: github/codeql-action/analyze@v4
|
||||
|
||||
@@ -9,9 +9,6 @@ on:
|
||||
paths:
|
||||
- .github/workflows/copilot-setup-steps.yml
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
copilot-setup-steps:
|
||||
runs-on: ubuntu-latest
|
||||
@@ -21,11 +18,9 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5
|
||||
with:
|
||||
persist-credentials: false
|
||||
uses: actions/checkout@v5
|
||||
- name: Use Node.js 22.x
|
||||
uses: actions/setup-node@a0853c24544627f65ddf259abe73b1d18a591444 # v5
|
||||
uses: actions/setup-node@v5
|
||||
with:
|
||||
node-version: 22.x
|
||||
cache: 'npm'
|
||||
|
||||
@@ -6,18 +6,13 @@ on:
|
||||
# without running tests to save time to publish
|
||||
branches: [ "master" ]
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
release:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5
|
||||
with:
|
||||
persist-credentials: false
|
||||
- uses: actions/checkout@v5
|
||||
- name: Use Node.js 22.x
|
||||
uses: actions/setup-node@a0853c24544627f65ddf259abe73b1d18a591444 # v5
|
||||
uses: actions/setup-node@v5
|
||||
with:
|
||||
node-version: 22.x
|
||||
cache: 'npm'
|
||||
|
||||
@@ -254,13 +254,6 @@ Xterm.js is used in many world-class applications to provide great terminal expe
|
||||
- [**WooTTY**](https://github.com/icoretech/wootty): Flawless browser terminal for real operators.
|
||||
- [**Orca**](https://github.com/stablyai/orca): Agentic development environment for 100x builders.
|
||||
- [**4bit**](https://ciembor.github.io/4bit): Terminal Color Scheme Designer.
|
||||
- [**Cate**](https://github.com/0-AI-UG/cate): Open source desktop IDE on an infinite zoomable canvas. Editor, browser, and terminal panels float in spatial space; the terminals are xterm.js with the WebGL addon, backed by node-pty.
|
||||
- [**OmnyShell PWA**](https://github.com/OmnyGrid/omnyshell_web): Web client for [**OmnyShell**](https://github.com/OmnyGrid/omnyshell), featuring remote terminals, a built-in IDE, AI-assisted workflows, and detachable/resumable sessions for seamless browser-based access to nodes.
|
||||
- [**kmux**](https://github.com/kkd927/kmux): A keyboard-centric terminal workspace for running parallel AI coding agents side by side on macOS and Linux.
|
||||
- [**LynxHub**](https://github.com/KindaBrazy/LynxHub): Cross-platform, extensible terminal and browser for AI management.
|
||||
- [**Codeman**](https://github.com/Ark0N/Codeman): Self-hosted mission control for AI coding agents, streaming live tmux sessions to browser and phone.
|
||||
- [**JSVIsion**](https://github.com/blendsdk/jsvision): A Turbo Vision-Style TUI SDK for TypeScript. We use xterm.js in our docs to show live JSVision examples running in a web terminal.
|
||||
- [**BitFun**](https://github.com/GCWing/BitFun): Open-source desktop AI agent whose integrated terminals and read-only terminal output views use xterm.js with the Fit, Web Links, and WebGL addons.
|
||||
- [And much more...](https://github.com/xtermjs/xterm.js/network/dependents?package_id=UGFja2FnZS0xNjYzMjc4OQ%3D%3D)
|
||||
|
||||
Do you use xterm.js in your application as well? Please [open a Pull Request](https://github.com/sourcelair/xterm.js/pulls) to include it here. We would love to have it on our list. Please add any new contributions to the end of the list.
|
||||
|
||||
@@ -98,7 +98,7 @@ 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, QOI, WEBP and AVIF
|
||||
- Supported formats: PNG, JPEG, GIF and QOI
|
||||
- No animation support.
|
||||
- Image type hinting is not supported (always deducted from data header).
|
||||
- File download is not supported.
|
||||
@@ -112,13 +112,6 @@ terminal.loadAddon(imageAddon);
|
||||
- ReportCellSize sequence is supported.
|
||||
- Hi-res display support is currently limited (image gets blurred).
|
||||
|
||||
- **Note on IIP image format support**
|
||||
For all image formats except QOI the addon only does the dimension extraction and leaves
|
||||
the image decoding to the underlying browser engine. Note that some formats are implemented
|
||||
and enabled with different constraints by browser vendors, especially newer formats
|
||||
(e.g. under Firefox you might need to change the setting _image.avif.compliance_strictness_ to 0
|
||||
to get broader AVIF support).
|
||||
|
||||
- **Kitty Graphics Support (TGP)**
|
||||
Set by default, change it with `{kittySupport: true}`.
|
||||
|
||||
|
||||
|
Before Width: | Height: | Size: 30 KiB |
|
Before Width: | Height: | Size: 18 KiB |
|
Before Width: | Height: | Size: 80 KiB |
|
Before Width: | Height: | Size: 59 KiB |
|
Before Width: | Height: | Size: 14 KiB |
|
Before Width: | Height: | Size: 27 KiB |
|
Before Width: | Height: | Size: 198 KiB |
|
Before Width: | Height: | Size: 52 KiB |
|
Before Width: | Height: | Size: 149 KiB |
|
Before Width: | Height: | Size: 173 KiB |
|
Before Width: | Height: | Size: 18 KiB |
|
Before Width: | Height: | Size: 33 KiB |
|
Before Width: | Height: | Size: 81 KiB |
|
Before Width: | Height: | Size: 57 KiB |
|
Before Width: | Height: | Size: 97 KiB |
|
Before Width: | Height: | Size: 90 KiB |
|
Before Width: | Height: | Size: 253 KiB |
|
Before Width: | Height: | Size: 84 KiB |
@@ -39,6 +39,7 @@ export class IIPHandler implements IOscHandler, IResetHandler {
|
||||
private _header: IHeaderFields = DEFAULT_HEADER;
|
||||
private _dec: Base64Decoder;
|
||||
private _qoiDec: QoiDecoder;
|
||||
private _metrics = UNSUPPORTED_TYPE;
|
||||
private _isMultipart = false;
|
||||
private _abortMulti = false;
|
||||
|
||||
@@ -62,6 +63,7 @@ export class IIPHandler implements IOscHandler, IResetHandler {
|
||||
|
||||
public start(): void {
|
||||
this._aborted = false;
|
||||
this._metrics = UNSUPPORTED_TYPE;
|
||||
this._hp.reset();
|
||||
}
|
||||
|
||||
@@ -118,14 +120,14 @@ export class IIPHandler implements IOscHandler, IResetHandler {
|
||||
|
||||
if (seqType === SequenceType.REPORTCELLSIZE) {
|
||||
// OSC 1337 ; ReportCellSize=[height];[width];[scale] ST
|
||||
let w = CELL_SIZE_DEFAULT.width;
|
||||
let h = CELL_SIZE_DEFAULT.height;
|
||||
let width = CELL_SIZE_DEFAULT.width;
|
||||
let height = CELL_SIZE_DEFAULT.height;
|
||||
if (this._renderer.dimensions) {
|
||||
w = this._renderer.dimensions.css.canvas.width / this._coreTerminal.cols;
|
||||
h = this._renderer.dimensions.css.canvas.height / this._coreTerminal.rows;
|
||||
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=${h.toFixed(3)};${w.toFixed(3)};${scale.toFixed(3)}\x1b\\`;
|
||||
const report = `\x1b]1337;ReportCellSize=${height.toFixed(3)};${width.toFixed(3)};${scale.toFixed(3)}\x1b\\`;
|
||||
this._coreTerminal.input(report, false);
|
||||
return true;
|
||||
}
|
||||
@@ -152,24 +154,17 @@ export class IIPHandler implements IOscHandler, IResetHandler {
|
||||
|
||||
// early exit condition chain
|
||||
let cond: number | boolean;
|
||||
let metrics = UNSUPPORTED_TYPE;
|
||||
if (cond = success) {
|
||||
if (cond = !this._dec.end()) {
|
||||
metrics = imageType(this._dec.data8);
|
||||
if (cond = metrics.mime !== 'unsupported') {
|
||||
w = metrics.width;
|
||||
h = metrics.height;
|
||||
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;
|
||||
} else {
|
||||
console.warn(`IIP: image dimension issue ${metrics.width}x${metrics.height}`);
|
||||
}
|
||||
} else {
|
||||
console.warn('IIP: unsupported image type');
|
||||
}
|
||||
} else {
|
||||
console.warn('IIP: error during BASE64 decoding');
|
||||
}
|
||||
}
|
||||
if (!cond) {
|
||||
@@ -178,7 +173,7 @@ export class IIPHandler implements IOscHandler, IResetHandler {
|
||||
}
|
||||
|
||||
let blob: Blob | ImageData;
|
||||
if (metrics.mime === 'image/qoi') {
|
||||
if (this._metrics.mime === 'image/qoi') {
|
||||
const data = this._qoiDec.decode(this._dec.data8);
|
||||
blob = new ImageData(
|
||||
new Uint8ClampedArray(data.buffer, data.byteOffset, data.byteLength),
|
||||
@@ -195,17 +190,13 @@ export class IIPHandler implements IOscHandler, IResetHandler {
|
||||
return true;
|
||||
}
|
||||
} else {
|
||||
blob = new Blob([this._dec.data8], { type: metrics.mime });
|
||||
blob = new Blob([this._dec.data8], { type: this._metrics.mime });
|
||||
}
|
||||
this._dec.release();
|
||||
return createImageBitmap(blob, { resizeWidth: w, resizeHeight: h })
|
||||
.then(bm => {
|
||||
this._storage.addImage(bm);
|
||||
return true;
|
||||
})
|
||||
.catch(e => {
|
||||
console.warn(`IIP: decoding error ${metrics.mime} ${metrics.width}x${metrics.height}`, e);
|
||||
return true;
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -32,28 +32,6 @@ const TEST_IMAGES: [string, IMetrics][] = [
|
||||
['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 }],
|
||||
// VP8
|
||||
['1.webp', { mime: 'image/webp', width: 550, height: 368 }],
|
||||
['2.webp', { mime: 'image/webp', width: 550, height: 404 }],
|
||||
['3.webp', { mime: 'image/webp', width: 1280, height: 720 }],
|
||||
['4.webp', { mime: 'image/webp', width: 1024, height: 772 }],
|
||||
['5.webp', { mime: 'image/webp', width: 1024, height: 752 }],
|
||||
// VP8X
|
||||
['1_webp_a.webp', { mime: 'image/webp', width: 400, height: 301 }],
|
||||
['2_webp_a.webp', { mime: 'image/webp', width: 386, height: 395 }],
|
||||
['3_webp_a.webp', { mime: 'image/webp', width: 800, height: 600 }],
|
||||
['4_webp_a.webp', { mime: 'image/webp', width: 421, height: 163 }],
|
||||
['5_webp_a.webp', { mime: 'image/webp', width: 300, height: 300 }],
|
||||
// VP8L
|
||||
['1_webp_ll.webp', { mime: 'image/webp', width: 400, height: 301 }],
|
||||
['2_webp_ll.webp', { mime: 'image/webp', width: 386, height: 395 }],
|
||||
['3_webp_ll.webp', { mime: 'image/webp', width: 800, height: 600 }],
|
||||
['4_webp_ll.webp', { mime: 'image/webp', width: 421, height: 163 }],
|
||||
['5_webp_ll.webp', { mime: 'image/webp', width: 300, height: 300 }],
|
||||
// some AVIF test images
|
||||
['fox.profile2.12bpc.yuv444.odd-width.odd-height.avif', { mime: 'image/avif', width: 1203, height: 799 }],
|
||||
['hato.profile0.8bpc.yuv420.avif', { mime: 'image/avif', width: 3082, height: 2048 }],
|
||||
['kimono.crop.avif', { mime: 'image/avif', width: 722, height: 1024 }],
|
||||
];
|
||||
|
||||
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
*/
|
||||
|
||||
|
||||
export type ImageType = 'image/png' | 'image/jpeg' | 'image/gif' | 'image/qoi' | 'image/webp' | 'image/avif' | 'unsupported' | '';
|
||||
export type ImageType = 'image/png' | 'image/jpeg' | 'image/gif' | 'image/qoi' | 'unsupported' | '';
|
||||
|
||||
export interface IMetrics {
|
||||
mime: ImageType;
|
||||
@@ -19,16 +19,16 @@ export const UNSUPPORTED_TYPE: IMetrics = {
|
||||
};
|
||||
|
||||
export function imageType(d: Uint8Array): IMetrics {
|
||||
if (d.length < 32) {
|
||||
if (d.length < 24) {
|
||||
return UNSUPPORTED_TYPE;
|
||||
}
|
||||
const d32 = new Uint32Array(d.buffer, d.byteOffset, 8);
|
||||
const d32 = new Uint32Array(d.buffer, d.byteOffset, 6);
|
||||
// PNG: 89 50 4E 47 0D 0A 1A 0A (8 first bytes == magic number for PNG)
|
||||
// + first chunk must be IHDR
|
||||
if (d32[0] === 0x474E5089 && d32[1] === 0x0A1A0A0D && d32[3] === 0x52444849) {
|
||||
return {
|
||||
mime: 'image/png',
|
||||
width: d[16] << 24 | d[17] << 16 | d[18] << 8 | d[19],
|
||||
width: d[16] << 24 | d[17] << 16 | d[18] << 8 | d[19],
|
||||
height: d[20] << 24 | d[21] << 16 | d[22] << 8 | d[23]
|
||||
};
|
||||
}
|
||||
@@ -41,7 +41,7 @@ export function imageType(d: Uint8Array): IMetrics {
|
||||
if (d32[0] === 0x38464947 && (d[4] === 0x37 || d[4] === 0x39) && d[5] === 0x61) {
|
||||
return {
|
||||
mime: 'image/gif',
|
||||
width: d[7] << 8 | d[6],
|
||||
width: d[7] << 8 | d[6],
|
||||
height: d[9] << 8 | d[8]
|
||||
};
|
||||
}
|
||||
@@ -49,71 +49,13 @@ export function imageType(d: Uint8Array): IMetrics {
|
||||
if (d32[0] === 0x66696F71) {
|
||||
return {
|
||||
mime: 'image/qoi',
|
||||
width: d[4] << 24 | d[5] << 16 | d[6] << 8 | d[7],
|
||||
width: d[4] << 24 | d[5] << 16 | d[6] << 8 | d[7],
|
||||
height: d[8] << 24 | d[9] << 16 | d[10] << 8 | d[11]
|
||||
};
|
||||
}
|
||||
// WEBP: RIFF | xxxx | WEBP | VP8x
|
||||
if (d32[0] === 0x46464952 && d32[2] === 0x50424557 && (d32[3] & 0xFFFFFF) === 0x385056) {
|
||||
switch (d[15]) {
|
||||
case 0x58: // Extended WebP VP8X --> "X"
|
||||
return {
|
||||
mime: 'image/webp',
|
||||
width: (d[24] | d[25] << 8 | d[26] << 16) + 1,
|
||||
height: (d[27] | d[28] << 8 | d[29] << 16) + 1
|
||||
};
|
||||
case 0x4C: // Lossless WebP VP8L --> "L"
|
||||
if (d[20] !== 0x2f) return UNSUPPORTED_TYPE;
|
||||
const dim = d[21] | d[22] << 8 | d[23] << 16 | d[24] << 24;
|
||||
return {
|
||||
mime: 'image/webp',
|
||||
width: (dim & 0x3FFF) + 1,
|
||||
height: (dim >>> 14 & 0x3FFF) + 1
|
||||
};
|
||||
case 0x20: // Lossy WebP VP8 --> " "
|
||||
if (d[23] !== 0x9d || d[24] !== 0x01 || d[25] !== 0x2a) return UNSUPPORTED_TYPE;
|
||||
return {
|
||||
mime: 'image/webp',
|
||||
width: (d[26] | d[27] << 8) & 0x3FFF,
|
||||
height: (d[28] | d[29] << 8) & 0x3FFF
|
||||
};
|
||||
}
|
||||
return UNSUPPORTED_TYPE;
|
||||
}
|
||||
// AVIF: Box size | ftyp | avif/avis
|
||||
if (d32[1] === 0x70797466 && (d32[2] === 0x66697661 || d32[2] === 0x73697661)) {
|
||||
let pos = -1;
|
||||
// search for ispe box within first 1024 bytes
|
||||
const limit = Math.min(d.length - 16, 1024);
|
||||
for (let i = 8; i < limit; i++) {
|
||||
// scan for ispe
|
||||
if (d[i] === 0x69 && d[i + 1] === 0x73 && d[i + 2] === 0x70 && d[i + 3] === 0x65) {
|
||||
pos = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (pos !== -1) {
|
||||
// dimensions are in BE at +8 (width) at +12 (height)
|
||||
const width =
|
||||
d[pos + 8] << 24 |
|
||||
d[pos + 9] << 16 |
|
||||
d[pos + 10] << 8 |
|
||||
d[pos + 11];
|
||||
const height =
|
||||
d[pos + 12] << 24 |
|
||||
d[pos + 13] << 16 |
|
||||
d[pos + 14] << 8 |
|
||||
d[pos + 15];
|
||||
if (width > 0 && height > 0) {
|
||||
return { mime: 'image/avif', width, height };
|
||||
}
|
||||
}
|
||||
return UNSUPPORTED_TYPE;
|
||||
}
|
||||
return UNSUPPORTED_TYPE;
|
||||
}
|
||||
|
||||
|
||||
function jpgSize(d: Uint8Array): [number, number] {
|
||||
const len = d.length;
|
||||
let i = 4;
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
|
||||
import type { ITerminalAddon, IDisposable } from '@xterm/xterm';
|
||||
import type { ImageAddon as IImageApi } from '@xterm/addon-image';
|
||||
import { Emitter, type IEvent } from 'common/Event';
|
||||
import { Emitter, type IEvent } from 'common/primitives/Event';
|
||||
import { IIPHandler } from './IIPHandler';
|
||||
import { ImageRenderer } from './ImageRenderer';
|
||||
import { ImageStorage, CELL_SIZE_DEFAULT } from './ImageStorage';
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
import { toRGBA8888 } from 'sixel/lib/Colors';
|
||||
import { IDisposable } from '@xterm/xterm';
|
||||
import { ICellSize, ImageLayer, ITerminalExt, IImageSpec, IRenderDimensions, IRenderService } from './Types';
|
||||
import { Disposable, MutableDisposable, toDisposable } from 'common/Lifecycle';
|
||||
import { Disposable, MutableDisposable, toDisposable } from 'common/primitives/Lifecycle';
|
||||
|
||||
const enum Constants {
|
||||
PLACEHOLDER_LENGTH = 4096,
|
||||
|
||||
@@ -5,11 +5,12 @@
|
||||
|
||||
import { IDisposable } from '@xterm/xterm';
|
||||
import { ImageRenderer } from './ImageRenderer';
|
||||
import type {
|
||||
ITerminalExt, IImageAddonOptions, IImageSpec, ICellSize, IAddImageOpts
|
||||
import {
|
||||
ITerminalExt, IExtendedAttrsImage, IImageAddonOptions, IImageSpec,
|
||||
IBufferLineExt, BgFlags, Cell, Content, ICellSize, ExtFlags, Attributes,
|
||||
UnderlineStyle, IAddImageOpts
|
||||
} from './Types';
|
||||
import type { IBufferLine } from 'common/buffer/Types';
|
||||
import { CellData } from 'common/buffer/CellData';
|
||||
|
||||
|
||||
// fallback default cell size
|
||||
export const CELL_SIZE_DEFAULT: ICellSize = {
|
||||
@@ -17,12 +18,93 @@ export const CELL_SIZE_DEFAULT: ICellSize = {
|
||||
height: 14
|
||||
};
|
||||
|
||||
class ImageTileInfo {
|
||||
/**
|
||||
* Extend extended attribute to also hold image tile information.
|
||||
*
|
||||
* Object definition is copied from base repo to fully mimick its behavior.
|
||||
* Image data is added as additional public properties `imageId` and `tileId`.
|
||||
*/
|
||||
class ExtendedAttrsImage implements IExtendedAttrsImage {
|
||||
private _ext: number = 0;
|
||||
public get ext(): number {
|
||||
if (this._urlId) {
|
||||
return (
|
||||
(this._ext & ~ExtFlags.UNDERLINE_STYLE) |
|
||||
(this.underlineStyle << 26)
|
||||
);
|
||||
}
|
||||
return this._ext;
|
||||
}
|
||||
public set ext(value: number) { this._ext = value; }
|
||||
|
||||
public get underlineStyle(): UnderlineStyle {
|
||||
// Always return the URL style if it has one
|
||||
if (this._urlId) {
|
||||
return UnderlineStyle.DASHED;
|
||||
}
|
||||
return (this._ext & ExtFlags.UNDERLINE_STYLE) >> 26;
|
||||
}
|
||||
public set underlineStyle(value: UnderlineStyle) {
|
||||
this._ext &= ~ExtFlags.UNDERLINE_STYLE;
|
||||
this._ext |= (value << 26) & ExtFlags.UNDERLINE_STYLE;
|
||||
}
|
||||
|
||||
public get underlineColor(): number {
|
||||
return this._ext & (Attributes.CM_MASK | Attributes.RGB_MASK);
|
||||
}
|
||||
public set underlineColor(value: number) {
|
||||
this._ext &= ~(Attributes.CM_MASK | Attributes.RGB_MASK);
|
||||
this._ext |= value & (Attributes.CM_MASK | Attributes.RGB_MASK);
|
||||
}
|
||||
|
||||
public get underlineVariantOffset(): number {
|
||||
const val = (this._ext & ExtFlags.VARIANT_OFFSET) >> 29;
|
||||
if (val < 0) {
|
||||
return val ^ 0xFFFFFFF8;
|
||||
}
|
||||
return val;
|
||||
}
|
||||
public set underlineVariantOffset(value: number) {
|
||||
this._ext &= ~ExtFlags.VARIANT_OFFSET;
|
||||
this._ext |= (value << 29) & ExtFlags.VARIANT_OFFSET;
|
||||
}
|
||||
|
||||
private _urlId: number = 0;
|
||||
public get urlId(): number {
|
||||
return this._urlId;
|
||||
}
|
||||
public set urlId(value: number) {
|
||||
this._urlId = value;
|
||||
}
|
||||
|
||||
constructor(
|
||||
ext: number = 0,
|
||||
urlId: number = 0,
|
||||
public imageId = -1,
|
||||
public tileId = -1) {
|
||||
public tileId = -1
|
||||
) {
|
||||
this._ext = ext;
|
||||
this._urlId = urlId;
|
||||
}
|
||||
|
||||
public clone(): IExtendedAttrsImage {
|
||||
/**
|
||||
* Technically we dont need a clone variant of ExtendedAttrsImage,
|
||||
* as we never clone a cell holding image data.
|
||||
* Note: Clone is only meant to be used by the InputHandler for
|
||||
* sticky attributes, which is never the case for image data.
|
||||
* We still provide a proper clone method to reflect the full ext attr
|
||||
* state in case there are future use cases for clone.
|
||||
*/
|
||||
return new ExtendedAttrsImage(this._ext, this._urlId, this.imageId, this.tileId);
|
||||
}
|
||||
|
||||
public isEmpty(): boolean {
|
||||
return this.underlineStyle === UnderlineStyle.NONE && this._urlId === 0 && this.imageId === -1;
|
||||
}
|
||||
}
|
||||
const EMPTY_ATTRS = new ExtendedAttrsImage();
|
||||
|
||||
|
||||
/**
|
||||
* ImageStorage - extension of CoreTerminal:
|
||||
@@ -44,7 +126,6 @@ export class ImageStorage implements IDisposable {
|
||||
private _needsFullClear = false;
|
||||
// hard limit of stored pixels (fallback limit of 10 MB)
|
||||
private _pixelLimit: number = 2500000;
|
||||
private _workCell: CellData = new CellData();
|
||||
|
||||
private _viewportMetrics: { cols: number, rows: number };
|
||||
public onImageAdded: (() => void) | undefined;
|
||||
@@ -196,10 +277,10 @@ export class ImageStorage implements IDisposable {
|
||||
|
||||
this._terminal._core._inputHandler._dirtyRowTracker.markDirty(buffer.y);
|
||||
for (let row = 0; row < rows; ++row) {
|
||||
const line = buffer.lines.get(buffer.y + buffer.ybase)!;
|
||||
const line = buffer.lines.get(buffer.y + buffer.ybase);
|
||||
for (let col = 0; col < cols; ++col) {
|
||||
if (offset + col >= termCols) break;
|
||||
this._writeToCell(line, offset + col, imageId, row * cols + col);
|
||||
this._writeToCell(line as IBufferLineExt, offset + col, imageId, row * cols + col);
|
||||
tileCount++;
|
||||
}
|
||||
if (opts.scrolling) {
|
||||
@@ -346,44 +427,55 @@ export class ImageStorage implements IDisposable {
|
||||
const placeholderCalls: { col: number, row: number, count: number }[] = [];
|
||||
|
||||
// walk all cells in viewport and collect tiles found
|
||||
// Note: We check _extendedAttrs directly (not just HAS_EXTENDED flag)
|
||||
// because text writes clear the BG flag but leave image tile data intact.
|
||||
// This lets top-layer images survive text overwrites (kitty C=1 behavior).
|
||||
for (let row = start; row <= end; ++row) {
|
||||
const line = buffer.lines.get(row + buffer.ydisp);
|
||||
const line = buffer.lines.get(row + buffer.ydisp) as IBufferLineExt;
|
||||
if (!line) return;
|
||||
for (let col = 0; col < cols; ++col) {
|
||||
const e = line.getExtended(col)?.payload;
|
||||
if (e instanceof ImageTileInfo) {
|
||||
const imageId = e.imageId;
|
||||
if (imageId === undefined || imageId === -1) {
|
||||
let e: IExtendedAttrsImage;
|
||||
if (line.getBg(col) & BgFlags.HAS_EXTENDED) {
|
||||
e = line._extendedAttrs[col] ?? EMPTY_ATTRS;
|
||||
} else {
|
||||
const maybeImg = line._extendedAttrs[col] as IExtendedAttrsImage | undefined;
|
||||
if (!maybeImg || maybeImg.imageId === undefined || maybeImg.imageId === -1) {
|
||||
continue;
|
||||
}
|
||||
const imgSpec = this._images.get(imageId);
|
||||
if (e.tileId !== -1) {
|
||||
const startTile = e.tileId;
|
||||
const startCol = col;
|
||||
let count = 1;
|
||||
/**
|
||||
* merge tiles to the right into a single draw call, if:
|
||||
* - not at end of line
|
||||
* - cell has same image id
|
||||
* - cell has consecutive tile id
|
||||
*/
|
||||
while (++col < cols) {
|
||||
const nextE = line.getExtended(col)?.payload;
|
||||
if (!(nextE instanceof ImageTileInfo) || nextE.imageId !== imageId || nextE.tileId !== startTile + count) {
|
||||
break;
|
||||
}
|
||||
count++;
|
||||
e = maybeImg;
|
||||
}
|
||||
const imageId = e.imageId;
|
||||
if (imageId === undefined || imageId === -1) {
|
||||
continue;
|
||||
}
|
||||
const imgSpec = this._images.get(imageId);
|
||||
if (e.tileId !== -1) {
|
||||
const startTile = e.tileId;
|
||||
const startCol = col;
|
||||
let count = 1;
|
||||
/**
|
||||
* merge tiles to the right into a single draw call, if:
|
||||
* - not at end of line
|
||||
* - cell has same image id
|
||||
* - cell has consecutive tile id
|
||||
* Also check _extendedAttrs directly for cells where text cleared HAS_EXTENDED.
|
||||
*/
|
||||
while (++col < cols) {
|
||||
const nextE = line._extendedAttrs[col] as IExtendedAttrsImage | undefined;
|
||||
if (!nextE || nextE.imageId !== imageId || nextE.tileId !== startTile + count) {
|
||||
break;
|
||||
}
|
||||
col--;
|
||||
if (imgSpec) {
|
||||
if (imgSpec.actual) {
|
||||
drawCalls.push({ imgSpec, tileId: startTile, col: startCol, row, count });
|
||||
}
|
||||
} else if (this._opts.showPlaceholder) {
|
||||
placeholderCalls.push({ col: startCol, row, count });
|
||||
}
|
||||
this._fullyCleared = false;
|
||||
count++;
|
||||
}
|
||||
col--;
|
||||
if (imgSpec) {
|
||||
if (imgSpec.actual) {
|
||||
drawCalls.push({ imgSpec, tileId: startTile, col: startCol, row, count });
|
||||
}
|
||||
} else if (this._opts.showPlaceholder) {
|
||||
placeholderCalls.push({ col: startCol, row, count });
|
||||
}
|
||||
this._fullyCleared = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -421,9 +513,9 @@ export class ImageStorage implements IDisposable {
|
||||
const rows = buffer.lines.length;
|
||||
const oldCol = this._viewportMetrics.cols - 1;
|
||||
for (let row = 0; row < rows; ++row) {
|
||||
const line = buffer.lines.get(row)!;
|
||||
const e = line.getExtended(oldCol)?.payload;
|
||||
if (e instanceof ImageTileInfo) {
|
||||
const line = buffer.lines.get(row) as IBufferLineExt;
|
||||
if (line.getBg(oldCol) & BgFlags.HAS_EXTENDED) {
|
||||
const e: IExtendedAttrsImage = line._extendedAttrs[oldCol] ?? EMPTY_ATTRS;
|
||||
const imageId = e.imageId;
|
||||
if (imageId === undefined || imageId === -1) {
|
||||
continue;
|
||||
@@ -440,7 +532,7 @@ export class ImageStorage implements IDisposable {
|
||||
// expand only if right side is empty (nothing got wrapped from below)
|
||||
let hasData = false;
|
||||
for (let rightCol = oldCol + 1; rightCol > metrics.cols; ++rightCol) {
|
||||
if (line.hasContent(rightCol)) {
|
||||
if (line._data[rightCol * Cell.SIZE + Cell.CONTENT] & Content.HAS_CONTENT_MASK) {
|
||||
hasData = true;
|
||||
break;
|
||||
}
|
||||
@@ -452,7 +544,7 @@ export class ImageStorage implements IDisposable {
|
||||
const end = Math.min(metrics.cols, tilesPerRow - (e.tileId % tilesPerRow) + oldCol);
|
||||
let lastTile = e.tileId;
|
||||
for (let expandCol = oldCol + 1; expandCol < end; ++expandCol) {
|
||||
this._writeToCell(line, expandCol, imageId, ++lastTile);
|
||||
this._writeToCell(line as IBufferLineExt, expandCol, imageId, ++lastTile);
|
||||
imgSpec.tileCount++;
|
||||
}
|
||||
}
|
||||
@@ -466,10 +558,10 @@ export class ImageStorage implements IDisposable {
|
||||
*/
|
||||
public getImageAtBufferCell(x: number, y: number): HTMLCanvasElement | undefined {
|
||||
const buffer = this._terminal._core.buffer;
|
||||
const line = buffer.lines.get(y);
|
||||
if (line) {
|
||||
const e = line.getExtended(x)?.payload;
|
||||
if (e instanceof ImageTileInfo && e.imageId && e.imageId !== -1) {
|
||||
const line = buffer.lines.get(y) as IBufferLineExt;
|
||||
if (line && line.getBg(x) & BgFlags.HAS_EXTENDED) {
|
||||
const e: IExtendedAttrsImage = line._extendedAttrs[x] ?? EMPTY_ATTRS;
|
||||
if (e.imageId && e.imageId !== -1) {
|
||||
const orig = this._images.get(e.imageId)?.orig;
|
||||
if (window.ImageBitmap && orig instanceof ImageBitmap) {
|
||||
const canvas = ImageRenderer.createCanvas(window.document, orig.width, orig.height);
|
||||
@@ -486,10 +578,10 @@ export class ImageStorage implements IDisposable {
|
||||
*/
|
||||
public extractTileAtBufferCell(x: number, y: number): HTMLCanvasElement | undefined {
|
||||
const buffer = this._terminal._core.buffer;
|
||||
const line = buffer.lines.get(y);
|
||||
if (line) {
|
||||
const e = line.getExtended(x)?.payload;
|
||||
if (e instanceof ImageTileInfo && e.imageId && e.imageId !== -1 && e.tileId !== -1) {
|
||||
const line = buffer.lines.get(y) as IBufferLineExt;
|
||||
if (line && line.getBg(x) & BgFlags.HAS_EXTENDED) {
|
||||
const e: IExtendedAttrsImage = line._extendedAttrs[x] ?? EMPTY_ATTRS;
|
||||
if (e.imageId && e.imageId !== -1 && e.tileId !== -1) {
|
||||
const spec = this._images.get(e.imageId);
|
||||
if (spec) {
|
||||
return this._renderer.extractTile(spec, e.tileId);
|
||||
@@ -517,31 +609,31 @@ export class ImageStorage implements IDisposable {
|
||||
return used - current;
|
||||
}
|
||||
|
||||
private _writeToCell(line: IBufferLine, x: number, imageId: number, tileId: number): void {
|
||||
const workCell = this._workCell;
|
||||
line.loadCell(x, workCell);
|
||||
if (this._workCell.hasExtendedAttrs()) {
|
||||
const old = workCell.extended.payload;
|
||||
if (old instanceof ImageTileInfo) {
|
||||
// found an old ExtendedAttrsImage, since we know that
|
||||
// they are always isolated instances (single cell usage),
|
||||
// we can re-use it and just update their id entries
|
||||
const oldSpec = this._images.get(old.imageId);
|
||||
if (oldSpec) {
|
||||
// early eviction for in-viewport overwrites
|
||||
oldSpec.tileCount--;
|
||||
private _writeToCell(line: IBufferLineExt, x: number, imageId: number, tileId: number): void {
|
||||
if (line._data[x * Cell.SIZE + Cell.BG] & BgFlags.HAS_EXTENDED) {
|
||||
const old = line._extendedAttrs[x];
|
||||
if (old) {
|
||||
if (old.imageId !== undefined) {
|
||||
// found an old ExtendedAttrsImage, since we know that
|
||||
// they are always isolated instances (single cell usage),
|
||||
// we can re-use it and just update their id entries
|
||||
const oldSpec = this._images.get(old.imageId);
|
||||
if (oldSpec) {
|
||||
// early eviction for in-viewport overwrites
|
||||
oldSpec.tileCount--;
|
||||
}
|
||||
old.imageId = imageId;
|
||||
old.tileId = tileId;
|
||||
return;
|
||||
}
|
||||
old.imageId = imageId;
|
||||
old.tileId = tileId;
|
||||
// found a plain ExtendedAttrs instance, clone it to new entry
|
||||
line._extendedAttrs[x] = new ExtendedAttrsImage(old.ext, old.urlId, imageId, tileId);
|
||||
return;
|
||||
}
|
||||
}
|
||||
// fall-through: always create new ExtendedAttrs entry
|
||||
const extattr = workCell.extended.clone();
|
||||
extattr.payload = new ImageTileInfo(imageId, tileId);
|
||||
workCell.extended = extattr;
|
||||
workCell.updateExtended();
|
||||
line.setCell(x, workCell);
|
||||
// fall-through: always create new ExtendedAttrsImage entry
|
||||
line._data[x * Cell.SIZE + Cell.BG] |= BgFlags.HAS_EXTENDED;
|
||||
line._extendedAttrs[x] = new ExtendedAttrsImage(0, 0, imageId, tileId);
|
||||
}
|
||||
|
||||
private _evictOnAlternate(): void {
|
||||
@@ -554,16 +646,18 @@ export class ImageStorage implements IDisposable {
|
||||
// re-count tiles on whole buffer
|
||||
const buffer = this._terminal._core.buffer;
|
||||
for (let y = 0; y < this._terminal.rows; ++y) {
|
||||
const line = buffer.lines.get(y);
|
||||
const line = buffer.lines.get(y) as IBufferLineExt;
|
||||
if (!line) {
|
||||
continue;
|
||||
}
|
||||
for (let x = 0; x < this._terminal.cols; ++x) {
|
||||
const payload = line.getExtended(x)?.payload;
|
||||
if (payload instanceof ImageTileInfo) {
|
||||
const spec = this._images.get(payload.imageId);
|
||||
if (spec) {
|
||||
spec.tileCount++;
|
||||
if (line._data[x * Cell.SIZE + Cell.BG] & BgFlags.HAS_EXTENDED) {
|
||||
const imgId = line._extendedAttrs[x]?.imageId;
|
||||
if (imgId) {
|
||||
const spec = this._images.get(imgId);
|
||||
if (spec) {
|
||||
spec.tileCount++;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,10 +6,11 @@
|
||||
import { IDisposable, IMarker, Terminal } from '@xterm/xterm';
|
||||
|
||||
// private imports from base repo we build against
|
||||
import { Attributes, BgFlags, Content, ExtFlags, UnderlineStyle } from 'common/buffer/Constants';
|
||||
import type { AttributeData } from 'common/buffer/AttributeData';
|
||||
import type { IParams, IDcsHandler, IOscHandler, IApcHandler, IEscapeSequenceParser } from 'common/parser/Types';
|
||||
import { Attributes, BgFlags, Content, ExtFlags, UnderlineStyle } from 'common/primitives/buffer/Constants';
|
||||
import type { AttributeData } from 'common/primitives/buffer/AttributeData';
|
||||
import type { IParams, IDcsHandler, IOscHandler, IApcHandler, IEscapeSequenceParser } from 'common/primitives/parser/Types';
|
||||
import type { IInputHandler } from 'common/Types';
|
||||
import type { IBufferLine, IExtendedAttrs } from 'common/primitives/buffer/Types';
|
||||
import type { ITerminal, ReadonlyColorSet } from 'browser/Types';
|
||||
import type { IRenderDimensions } from 'browser/renderer/shared/Types';
|
||||
import type { ICoreBrowserService, IRenderService, IThemeService } from 'browser/services/Services';
|
||||
@@ -47,7 +48,6 @@ export interface IResetHandler {
|
||||
reset(): void;
|
||||
}
|
||||
|
||||
/* eslint-disable */
|
||||
/**
|
||||
* Stub into private interfaces.
|
||||
* This should be kept in line with common libs.
|
||||
@@ -55,6 +55,19 @@ export interface IResetHandler {
|
||||
* have a somewhat reliable testing against code changes in the core repo.
|
||||
*/
|
||||
|
||||
// overloaded IExtendedAttrs to hold image refs
|
||||
export interface IExtendedAttrsImage extends IExtendedAttrs {
|
||||
imageId: number;
|
||||
tileId: number;
|
||||
clone(): IExtendedAttrsImage;
|
||||
}
|
||||
|
||||
/* eslint-disable */
|
||||
export interface IBufferLineExt extends IBufferLine {
|
||||
_extendedAttrs: {[index: number]: IExtendedAttrsImage | undefined};
|
||||
_data: Uint32Array;
|
||||
}
|
||||
|
||||
interface IInputHandlerExt extends IInputHandler {
|
||||
_parser: IEscapeSequenceParser;
|
||||
_curAttrData: AttributeData;
|
||||
@@ -65,7 +78,6 @@ interface IInputHandlerExt extends IInputHandler {
|
||||
};
|
||||
onRequestReset(handler: () => void): IDisposable;
|
||||
}
|
||||
/* eslint-enable */
|
||||
|
||||
export interface ICoreTerminalExt extends ITerminal {
|
||||
_themeService: IThemeService | undefined;
|
||||
@@ -77,6 +89,8 @@ export interface ICoreTerminalExt extends ITerminal {
|
||||
export interface ITerminalExt extends Terminal {
|
||||
_core: ICoreTerminalExt;
|
||||
}
|
||||
/* eslint-enable */
|
||||
|
||||
|
||||
/**
|
||||
* Some storage definitions.
|
||||
|
||||
@@ -435,15 +435,6 @@ test.describe('ImageAddon', () => {
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
test.describe('text overwrite removes tiles', () => {
|
||||
test('sixel', async () => {
|
||||
await assertTextClearsTiles(SIXEL_SEQ_0);
|
||||
});
|
||||
test('iip', async () => {
|
||||
await assertTextClearsTiles(TESTDATA_IIP[0][0]);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
/**
|
||||
@@ -481,20 +472,3 @@ async function getOrigSize(id: number): Promise<[number, number]> {
|
||||
async function getImageAtBufferCell(x: number, y: number): Promise<string | undefined> {
|
||||
return ctx.page.evaluate<any>(`window.imageAddon.getImageAtBufferCell(${x}, ${y})?.toDataURL('image/png')`);
|
||||
}
|
||||
|
||||
async function hasTileAtBufferCell(x: number, y: number): Promise<boolean> {
|
||||
return ctx.page.evaluate(`!!window.imageAddon.extractTileAtBufferCell(${x}, ${y})`);
|
||||
}
|
||||
|
||||
async function assertTextClearsTiles(imageSeq: string): Promise<void> {
|
||||
await ctx.proxy.write('\x1b[H' + imageSeq);
|
||||
await pollFor(ctx.page, '!!window.imageAddon.extractTileAtBufferCell(5, 1)', true);
|
||||
ok(await hasTileAtBufferCell(0, 1));
|
||||
await ctx.proxy.write('\x1b[2;6H#######');
|
||||
for (let x = 5; x < 12; x++) {
|
||||
strictEqual(await hasTileAtBufferCell(x, 1), false);
|
||||
}
|
||||
ok(await hasTileAtBufferCell(0, 1));
|
||||
ok(await hasTileAtBufferCell(13, 1));
|
||||
ok(((await ctx.page.evaluate('window.term.buffer.active.getLine(1).translateToString(true)')) as string).includes('#######'));
|
||||
}
|
||||
|
||||
@@ -2213,21 +2213,6 @@ test.describe('Kitty Graphics Protocol', () => {
|
||||
await pollFor(ctx.page, 'window._imageAddedCount', 2);
|
||||
});
|
||||
});
|
||||
|
||||
test.describe('text overwrite removes tiles', () => {
|
||||
test('a=T placement', async () => {
|
||||
await ctx.proxy.write(`\x1b[H\x1b_Ga=T,f=100;${KITTY_MULTICOLOR_200X100_BASE64}\x1b\\`);
|
||||
await pollFor(ctx.page, '!!window.imageAddon.extractTileAtBufferCell(5, 1)', true);
|
||||
ok(await hasTileAtBufferCell(0, 1));
|
||||
await ctx.proxy.write('\x1b[2;6H#######');
|
||||
for (let x = 5; x < 12; x++) {
|
||||
strictEqual(await hasTileAtBufferCell(x, 1), false);
|
||||
}
|
||||
ok(await hasTileAtBufferCell(0, 1));
|
||||
ok(await hasTileAtBufferCell(13, 1));
|
||||
ok(((await ctx.page.evaluate('window.term.buffer.active.getLine(1).translateToString(true)')) as string).includes('#######'));
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
/**
|
||||
@@ -2277,7 +2262,3 @@ async function getPixels(col: number, row: number, x: number, y: number, w: numb
|
||||
return Array.from(ctx2d.getImageData(x, y, w, h).data);
|
||||
}, [col, row, x, y, w, h]);
|
||||
}
|
||||
|
||||
async function hasTileAtBufferCell(x: number, y: number): Promise<boolean> {
|
||||
return ctx.page.evaluate(`!!window.imageAddon.extractTileAtBufferCell(${x}, ${y})`);
|
||||
}
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
|
||||
import type { Terminal, ITerminalAddon, IDisposable } from '@xterm/xterm';
|
||||
import type { ProgressAddon as IProgressApi, IProgressState } from '@xterm/addon-progress';
|
||||
import type { Emitter, IEvent } from 'common/Event';
|
||||
import type { Emitter, IEvent } from 'common/primitives/Event';
|
||||
|
||||
|
||||
const enum ProgressType {
|
||||
|
||||
@@ -10,7 +10,7 @@ import { SearchLineCache } from './SearchLineCache';
|
||||
import { Terminal } from 'browser/public/Terminal';
|
||||
import type { ISearchDecorationOptions } from '@xterm/addon-search';
|
||||
import type { IDecorationOptions } from '@xterm/xterm';
|
||||
import { DisposableStore } from 'common/Lifecycle';
|
||||
import { DisposableStore } from 'common/primitives/Lifecycle';
|
||||
|
||||
function writeP(terminal: Terminal, data: string): Promise<void> {
|
||||
return new Promise(r => terminal.write(data, r));
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
|
||||
import type { Terminal, IDisposable, IDecoration } from '@xterm/xterm';
|
||||
import type { ISearchDecorationOptions } from '@xterm/addon-search';
|
||||
import { dispose, Disposable, toDisposable } from 'common/Lifecycle';
|
||||
import { dispose, Disposable, toDisposable } from 'common/primitives/Lifecycle';
|
||||
import type { ISearchResult } from './SearchEngine';
|
||||
|
||||
/**
|
||||
|
||||
@@ -5,9 +5,9 @@
|
||||
|
||||
import type { Terminal, IDisposable, ITerminalAddon } from '@xterm/xterm';
|
||||
import type { SearchAddon as ISearchApi, ISearchOptions, ISearchAddonOptions, ISearchResultChangeEvent, ISearchDecorationOptions } from '@xterm/addon-search';
|
||||
import { Emitter, type IEvent } from 'common/Event';
|
||||
import { Disposable, MutableDisposable, toDisposable } from 'common/Lifecycle';
|
||||
import { disposableTimeout } from 'common/Async';
|
||||
import { Emitter, type IEvent } from 'common/primitives/Event';
|
||||
import { Disposable, MutableDisposable, toDisposable } from 'common/primitives/Lifecycle';
|
||||
import { disposableTimeout } from 'common/primitives/Async';
|
||||
import { SearchLineCache } from './SearchLineCache';
|
||||
import { SearchState } from './SearchState';
|
||||
import { SearchEngine, type ISearchResult } from './SearchEngine';
|
||||
|
||||
@@ -7,7 +7,7 @@ import { SearchEngine } from './SearchEngine';
|
||||
import { SearchLineCache } from './SearchLineCache';
|
||||
import { Terminal } from 'browser/public/Terminal';
|
||||
import type { ISearchOptions } from '@xterm/addon-search';
|
||||
import { DisposableStore } from 'common/Lifecycle';
|
||||
import { DisposableStore } from 'common/primitives/Lifecycle';
|
||||
|
||||
function writeP(terminal: Terminal, data: string): Promise<void> {
|
||||
return new Promise(r => terminal.write(data, r));
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
import { assert } from 'chai';
|
||||
import { SearchLineCache, LineCacheEntry } from './SearchLineCache';
|
||||
import { Terminal } from 'browser/public/Terminal';
|
||||
import { timeout } from 'common/Async';
|
||||
import { timeout } from 'common/primitives/Async';
|
||||
|
||||
function writeP(terminal: Terminal, data: string): Promise<void> {
|
||||
return new Promise(r => terminal.write(data, r));
|
||||
|
||||
@@ -4,8 +4,8 @@
|
||||
*/
|
||||
|
||||
import type { Terminal } from '@xterm/xterm';
|
||||
import { combinedDisposable, Disposable, MutableDisposable, toDisposable } from 'common/Lifecycle';
|
||||
import { disposableTimeout } from 'common/Async';
|
||||
import { combinedDisposable, Disposable, MutableDisposable, toDisposable } from 'common/primitives/Lifecycle';
|
||||
import { disposableTimeout } from 'common/primitives/Async';
|
||||
|
||||
export type LineCacheEntry = [
|
||||
/**
|
||||
|
||||
@@ -5,8 +5,8 @@
|
||||
|
||||
import type { ISearchResultChangeEvent } from '@xterm/addon-search';
|
||||
import type { IDisposable } from '@xterm/xterm';
|
||||
import { Emitter, type IEvent } from 'common/Event';
|
||||
import { Disposable } from 'common/Lifecycle';
|
||||
import { Emitter, type IEvent } from 'common/primitives/Event';
|
||||
import { Disposable } from 'common/primitives/Lifecycle';
|
||||
import type { ISearchResult } from './SearchEngine';
|
||||
|
||||
/**
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
import { perfContext, before, ThroughputRuntimeCase } from 'xterm-benchmark';
|
||||
|
||||
import { spawn } from 'node-pty';
|
||||
import { Utf8ToUtf32, stringFromCodePoint } from 'common/input/TextDecoder';
|
||||
import { Utf8ToUtf32, stringFromCodePoint } from 'common/primitives/input/TextDecoder';
|
||||
import { Terminal } from 'browser/public/Terminal';
|
||||
import { SerializeAddon } from 'SerializeAddon';
|
||||
|
||||
|
||||
@@ -7,10 +7,10 @@
|
||||
|
||||
import type { IBuffer, IBufferCell, IBufferRange, ITerminalAddon, Terminal } from '@xterm/xterm';
|
||||
import type { IHTMLSerializeOptions, SerializeAddon as ISerializeApi, ISerializeOptions, ISerializeRange } from '@xterm/addon-serialize';
|
||||
import { IColor } from 'common/Types';
|
||||
import { IAttributeData } from 'common/buffer/Types';
|
||||
import { IColor } from 'common/primitives/Types';
|
||||
import { IAttributeData } from 'common/primitives/buffer/Types';
|
||||
import { DEFAULT_ANSI_COLORS } from 'browser/Types';
|
||||
import { UnderlineStyle } from 'common/buffer/Constants';
|
||||
import { UnderlineStyle } from 'common/primitives/buffer/Constants';
|
||||
|
||||
function constrain(value: number, low: number, high: number): number {
|
||||
return Math.max(low, Math.min(value, high));
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
import { perfContext, before, ThroughputRuntimeCase } from 'xterm-benchmark';
|
||||
|
||||
import { spawn } from 'node-pty';
|
||||
import { Utf8ToUtf32, stringFromCodePoint } from 'common/input/TextDecoder';
|
||||
import { Utf8ToUtf32, stringFromCodePoint } from 'common/primitives/input/TextDecoder';
|
||||
import { CoreBrowserTerminal } from 'browser/CoreBrowserTerminal';
|
||||
import { UnicodeGraphemeProvider } from 'UnicodeGraphemeProvider';
|
||||
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
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 { Attributes, BgFlags, ExtFlags, FgFlags, NULL_CELL_CODE, UnderlineStyle } from 'common/primitives/buffer/Constants';
|
||||
import { IDecorationService, IOptionsService } from 'common/services/Services';
|
||||
import { ICellData } from 'common/buffer/Types';
|
||||
import { ICellData } from 'common/primitives/buffer/Types';
|
||||
import { Terminal } from '@xterm/xterm';
|
||||
import { rgba } from 'common/Color';
|
||||
import { rgba } from 'common/primitives/Color';
|
||||
import { treatGlyphAsBackgroundColor } from 'browser/renderer/shared/RendererUtils';
|
||||
import { blockPatternCodepoints } from './customGlyphs/CustomGlyphDefinitions';
|
||||
|
||||
|
||||
@@ -6,8 +6,8 @@
|
||||
import { assert } from 'chai';
|
||||
import { configEquals } from './CharAtlasUtils';
|
||||
import { ICharAtlasConfig } from './Types';
|
||||
import { NULL_COLOR } from 'common/Color';
|
||||
import { IColor } from 'common/Types';
|
||||
import { NULL_COLOR } from 'common/primitives/Color';
|
||||
import { IColor } from 'common/primitives/Types';
|
||||
|
||||
function createTestConfig(overrides: Partial<ICharAtlasConfig> = {}): ICharAtlasConfig {
|
||||
const color: IColor = { css: '#ffffff', rgba: 0xffffffff };
|
||||
|
||||
@@ -4,10 +4,10 @@
|
||||
*/
|
||||
|
||||
import { ICharAtlasConfig } from './Types';
|
||||
import { Attributes } from 'common/buffer/Constants';
|
||||
import { Attributes } from 'common/primitives/buffer/Constants';
|
||||
import { ITerminalOptions } from '@xterm/xterm';
|
||||
import { IColorSet, ReadonlyColorSet } from 'browser/Types';
|
||||
import { NULL_COLOR } from 'common/Color';
|
||||
import { NULL_COLOR } from 'common/primitives/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 {
|
||||
// null out some fields that don't matter
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
* @license MIT
|
||||
*/
|
||||
|
||||
import { isFirefox, isLegacyEdge } from 'common/Platform';
|
||||
import { isFirefox, isLegacyEdge } from 'common/primitives/Platform';
|
||||
|
||||
export const DIM_OPACITY = 0.5;
|
||||
// The text baseline is set conditionally by browser. Using 'ideographic' for Firefox or Legacy Edge
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
* @license MIT
|
||||
*/
|
||||
|
||||
import { toDisposable, IDisposable } from 'common/Lifecycle';
|
||||
import { toDisposable, IDisposable } from 'common/primitives/Lifecycle';
|
||||
|
||||
export function observeDevicePixelDimensions(element: HTMLElement, parentWindow: Window & typeof globalThis, callback: (deviceWidth: number, deviceHeight: number) => void): IDisposable {
|
||||
// Observe any resizes to the element and extract the actual pixel size of the element if the
|
||||
|
||||
@@ -4,8 +4,8 @@
|
||||
*/
|
||||
import { TextureAtlas } from './TextureAtlas';
|
||||
import { IRenderDimensions } from 'browser/renderer/shared/Types';
|
||||
import { NULL_CELL_CODE } from 'common/buffer/Constants';
|
||||
import { Disposable, toDisposable } from 'common/Lifecycle';
|
||||
import { NULL_CELL_CODE } from 'common/primitives/buffer/Constants';
|
||||
import { Disposable, toDisposable } from 'common/primitives/Lifecycle';
|
||||
import { Terminal } from '@xterm/xterm';
|
||||
import { IRenderModel, IWebGL2RenderingContext, IWebGLVertexArrayObject, type IRasterizedGlyph, type ITextureAtlas } from './Types';
|
||||
import { createProgram, GLTexture, PROJECTION_MATRIX } from './WebglUtils';
|
||||
@@ -100,8 +100,6 @@ export class GlyphRenderer extends Disposable {
|
||||
private readonly _attributesBuffer: WebGLBuffer;
|
||||
|
||||
private _atlas: ITextureAtlas | undefined;
|
||||
private _lastSeenPageLayoutVersion: number = -1;
|
||||
private _pageOverflowWarned: boolean = false;
|
||||
private _activeBuffer: number = 0;
|
||||
private readonly _vertices: IVertices = {
|
||||
count: 0,
|
||||
@@ -215,19 +213,8 @@ export class GlyphRenderer extends Disposable {
|
||||
this.handleResize();
|
||||
}
|
||||
|
||||
/**
|
||||
* Call when a frame is being drawn. Returns whether the full model must be rebuilt before
|
||||
* rendering this frame because the atlas page layout changed since this renderer last drew.
|
||||
*/
|
||||
public beginFrame(): boolean {
|
||||
if (!this._atlas) {
|
||||
return true;
|
||||
}
|
||||
if (this._atlas.pageLayoutVersion !== this._lastSeenPageLayoutVersion) {
|
||||
this._lastSeenPageLayoutVersion = this._atlas.pageLayoutVersion;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
return this._atlas ? this._atlas.beginFrame() : true;
|
||||
}
|
||||
|
||||
public updateCell(x: number, y: number, code: number, bg: number, fg: number, ext: number, chars: string, width: number, lastBg: number): void {
|
||||
@@ -376,13 +363,7 @@ export class GlyphRenderer extends Disposable {
|
||||
// Bind the atlas page texture if they have changed. AtlasPage.version is globally
|
||||
// monotonic, so a page object swap at the same index (which happens after a page merge)
|
||||
// is detected by the same comparison.
|
||||
// Clamp defensively in case the atlas unexpectedly exceeds texture capacity.
|
||||
const pageCount = Math.min(this._atlas.pages.length, this._atlasTextures.length);
|
||||
if (this._atlas.pages.length > this._atlasTextures.length && !this._pageOverflowWarned) {
|
||||
this._pageOverflowWarned = true;
|
||||
this._logService.warn(`Atlas page count (${this._atlas.pages.length}) exceeds the renderer's texture capacity (${this._atlasTextures.length}), some glyphs will not render correctly`);
|
||||
}
|
||||
for (let i = 0; i < pageCount; i++) {
|
||||
for (let i = 0; i < this._atlas.pages.length; i++) {
|
||||
if (this._atlas.pages[i].version !== this._atlasTextures[i].version) {
|
||||
this._bindAtlasPageTexture(gl, this._atlas, i);
|
||||
}
|
||||
@@ -394,7 +375,6 @@ export class GlyphRenderer extends Disposable {
|
||||
|
||||
public setAtlas(atlas: ITextureAtlas): void {
|
||||
this._atlas = atlas;
|
||||
this._lastSeenPageLayoutVersion = -1;
|
||||
this.invalidateAtlasTextures();
|
||||
}
|
||||
|
||||
|
||||
@@ -6,9 +6,9 @@
|
||||
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';
|
||||
import { Attributes, FgFlags } from 'common/primitives/buffer/Constants';
|
||||
import { Disposable, toDisposable } from 'common/primitives/Lifecycle';
|
||||
import { IColor } from 'common/primitives/Types';
|
||||
import { Terminal } from '@xterm/xterm';
|
||||
import { RenderModelConstants } from './RenderModel';
|
||||
import { IRenderModel, IWebGL2RenderingContext, IWebGLVertexArrayObject } from './Types';
|
||||
|
||||
@@ -8,14 +8,14 @@ import { DIM_OPACITY, TEXT_BASELINE } from './Constants';
|
||||
import { tryDrawCustomGlyph } from './customGlyphs/CustomGlyphRasterizer';
|
||||
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';
|
||||
import { IdleTaskQueue } from 'common/TaskQueue';
|
||||
import { IColor } from 'common/Types';
|
||||
import { AttributeData } from 'common/buffer/AttributeData';
|
||||
import { Attributes, DEFAULT_COLOR, DEFAULT_EXT, UnderlineStyle } from 'common/buffer/Constants';
|
||||
import { NULL_COLOR, channels, color, rgba } from 'common/primitives/Color';
|
||||
import { FourKeyMap } from 'common/primitives/MultiKeyMap';
|
||||
import { IdleTaskQueue } from 'common/primitives/TaskQueue';
|
||||
import { IColor } from 'common/primitives/Types';
|
||||
import { AttributeData } from 'common/primitives/buffer/AttributeData';
|
||||
import { Attributes, DEFAULT_COLOR, DEFAULT_EXT, UnderlineStyle } from 'common/primitives/buffer/Constants';
|
||||
import { ILogService, IUnicodeService } from 'common/services/Services';
|
||||
import { Emitter } from 'common/Event';
|
||||
import { Emitter } from 'common/primitives/Event';
|
||||
|
||||
/**
|
||||
* A shared object which is used to draw nothing for a particular cell.
|
||||
@@ -132,8 +132,12 @@ export class TextureAtlas implements ITextureAtlas {
|
||||
}
|
||||
}
|
||||
|
||||
private _pageLayoutVersion = 0;
|
||||
public get pageLayoutVersion(): number { return this._pageLayoutVersion; }
|
||||
private _requestClearModel = false;
|
||||
public beginFrame(): boolean {
|
||||
const result = this._requestClearModel;
|
||||
this._requestClearModel = false;
|
||||
return result;
|
||||
}
|
||||
|
||||
public clearTexture(): void {
|
||||
if (this._pages[0].currentRow.x === 0 && this._pages[0].currentRow.y === 0) {
|
||||
@@ -145,11 +149,6 @@ export class TextureAtlas implements ITextureAtlas {
|
||||
this._cacheMap.clear();
|
||||
this._cacheMapCombined.clear();
|
||||
this._didWarmUp = false;
|
||||
|
||||
// Invalidate renderer models so all texture pages are refreshed. The atlas may be shared, in
|
||||
// which case the clearing renderer has cleared only its own model and every other owner still
|
||||
// holds texture coords into the rows just wiped.
|
||||
this._pageLayoutVersion++;
|
||||
}
|
||||
|
||||
private _createNewPage(): AtlasPage {
|
||||
@@ -185,8 +184,6 @@ export class TextureAtlas implements ITextureAtlas {
|
||||
// Only proceed with merge if we have exactly 4 same-sized pages. If not, we cannot
|
||||
// effectively reduce page count and merging would cause issues.
|
||||
if (mergingPages.length < 4 || mergingPages.some(p => p.canvas.width !== mergingPages[0].canvas.width)) {
|
||||
// Evict instead of adding a page beyond the renderer's texture capacity.
|
||||
this._evictAllPages();
|
||||
const newPage = new AtlasPage(this._document, this._textureSize);
|
||||
this._pages.push(newPage);
|
||||
this._activePages.push(newPage);
|
||||
@@ -209,8 +206,8 @@ export class TextureAtlas implements ITextureAtlas {
|
||||
// Add the new merged page to the end
|
||||
this.pages.push(mergedPage);
|
||||
|
||||
// Invalidate renderer models so all texture pages are refreshed.
|
||||
this._pageLayoutVersion++;
|
||||
// Request the model to be cleared to refresh all texture pages.
|
||||
this._requestClearModel = true;
|
||||
this._onAddTextureAtlasCanvas.fire(mergedPage.canvas);
|
||||
}
|
||||
|
||||
@@ -261,23 +258,6 @@ export class TextureAtlas implements ITextureAtlas {
|
||||
}
|
||||
}
|
||||
|
||||
private _evictAllPages(): void {
|
||||
const startTime = performance.now();
|
||||
const pageCount = this._pages.length;
|
||||
for (const page of this._pages) {
|
||||
this._onRemoveTextureAtlasCanvas.fire(page.canvas);
|
||||
page.canvas.remove();
|
||||
}
|
||||
this._pages.length = 0;
|
||||
this._activePages.length = 0;
|
||||
this._overflowSizePage = undefined;
|
||||
this._cacheMap.clear();
|
||||
this._cacheMapCombined.clear();
|
||||
this._didWarmUp = false;
|
||||
this._pageLayoutVersion++;
|
||||
this._logService.debug(`Evicted ${pageCount} WebGL atlas pages in ${(performance.now() - startTime).toFixed(2)}ms`);
|
||||
}
|
||||
|
||||
public getRasterizedGlyphCombinedChar(chars: string, bg: number, fg: number, ext: number, restrictToCellHeight: boolean, domContainer: HTMLElement | undefined): IRasterizedGlyph {
|
||||
return this._getFromCacheMap(this._cacheMapCombined, chars, bg, fg, ext, restrictToCellHeight, domContainer);
|
||||
}
|
||||
@@ -835,15 +815,11 @@ export class TextureAtlas implements ITextureAtlas {
|
||||
// Create a new page for oversized glyphs as they come up
|
||||
if (rasterizedGlyph.size.x > this._textureSize) {
|
||||
if (!this._overflowSizePage) {
|
||||
// Make room for the oversized page without exceeding texture capacity.
|
||||
if (TextureAtlas.maxAtlasPages && this._pages.length >= TextureAtlas.maxAtlasPages) {
|
||||
this._evictAllPages();
|
||||
}
|
||||
this._overflowSizePage = new AtlasPage(this._document, this._config.deviceMaxTextureSize);
|
||||
this.pages.push(this._overflowSizePage);
|
||||
|
||||
// Invalidate renderer models so all texture pages are refreshed.
|
||||
this._pageLayoutVersion++;
|
||||
// Request the model to be cleared to refresh all texture pages.
|
||||
this._requestClearModel = true;
|
||||
this._onAddTextureAtlasCanvas.fire(this._overflowSizePage.canvas);
|
||||
}
|
||||
activePage = this._overflowSizePage;
|
||||
|
||||
@@ -6,8 +6,8 @@
|
||||
import { FontWeight } from '@xterm/xterm';
|
||||
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';
|
||||
import { CursorInactiveStyle, CursorStyle, type IDisposable } from 'common/primitives/Types';
|
||||
import type { IEvent } from 'common/primitives/Event';
|
||||
|
||||
export interface IRenderModel {
|
||||
cells: Uint32Array;
|
||||
@@ -68,12 +68,10 @@ export interface ITextureAtlas extends IDisposable {
|
||||
warmUp(): void;
|
||||
|
||||
/**
|
||||
* Incremented whenever cached glyph texture page mappings may be stale, such as after atlas page
|
||||
* merges or overflow page creation. Renderers compare this against their own last-seen value and
|
||||
* rebuild their model when it changes; a shared atlas can have many renderers, so this must not
|
||||
* be a consume-once flag.
|
||||
* Call when a frame is being drawn, this will return true if the atlas was cleared to make room
|
||||
* for a new set of glyphs.
|
||||
*/
|
||||
readonly pageLayoutVersion: number;
|
||||
beginFrame(): boolean;
|
||||
|
||||
/**
|
||||
* Clear all glyphs from the texture atlas.
|
||||
|
||||
@@ -7,12 +7,12 @@ import type { ITerminalAddon, Terminal } from '@xterm/xterm';
|
||||
import type { IWebglAddonOptions, WebglAddon as IWebglApi } from '@xterm/addon-webgl';
|
||||
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 { Disposable, toDisposable } from 'common/primitives/Lifecycle';
|
||||
import { getSafariVersion, isSafari } from 'common/primitives/Platform';
|
||||
import { ICoreService, IDecorationService, ILogService, IOptionsService } from 'common/services/Services';
|
||||
import { IWebGL2RenderingContext } from './Types';
|
||||
import { WebglRenderer } from './WebglRenderer';
|
||||
import { Emitter, EventUtils } from 'common/Event';
|
||||
import { Emitter, EventUtils } from 'common/primitives/Event';
|
||||
|
||||
export class WebglAddon extends Disposable implements ITerminalAddon, IWebglApi {
|
||||
private _terminal?: Terminal;
|
||||
|
||||
@@ -10,10 +10,10 @@ import { CursorBlinkStateManager } from './CursorBlinkStateManager';
|
||||
import { observeDevicePixelDimensions } from './DevicePixelObserver';
|
||||
import { IRenderDimensions, IRenderer, IRequestRedrawEvent } from 'browser/renderer/shared/Types';
|
||||
import { ICharSizeService, ICharacterJoinerService, ICoreBrowserService, IThemeService } from 'browser/services/Services';
|
||||
import { CharData, IBufferLine, ICellData } from 'common/buffer/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 { CharData, IBufferLine, ICellData } from 'common/primitives/buffer/Types';
|
||||
import { AttributeData } from 'common/primitives/buffer/AttributeData';
|
||||
import { CellData } from 'common/primitives/buffer/CellData';
|
||||
import { Attributes, Content, FgFlags, NULL_CELL_CHAR, NULL_CELL_CODE } from 'common/primitives/buffer/Constants';
|
||||
import { TextBlinkStateManager } from 'browser/renderer/shared/TextBlinkStateManager';
|
||||
import { ICoreService, IDecorationService, ILogService, IOptionsService } from 'common/services/Services';
|
||||
import { Terminal } from '@xterm/xterm';
|
||||
@@ -23,9 +23,9 @@ import { COMBINED_CHAR_BIT_MASK, RenderModel, RenderModelConstants } from './Ren
|
||||
import { IWebGL2RenderingContext, type ITextureAtlas } from './Types';
|
||||
import { LinkRenderLayer } from './renderLayer/LinkRenderLayer';
|
||||
import { IRenderLayer } from './renderLayer/Types';
|
||||
import { Emitter, EventUtils } from 'common/Event';
|
||||
import { Emitter, EventUtils } from 'common/primitives/Event';
|
||||
import { addDisposableListener } from 'browser/Dom';
|
||||
import { combinedDisposable, Disposable, MutableDisposable, toDisposable } from 'common/Lifecycle';
|
||||
import { combinedDisposable, Disposable, MutableDisposable, toDisposable } from 'common/primitives/Lifecycle';
|
||||
import { createRenderDimensions } from 'browser/renderer/shared/RendererUtils';
|
||||
|
||||
const enum Constants {
|
||||
|
||||
@@ -1,54 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2026 The xterm.js authors. All rights reserved.
|
||||
* @license MIT
|
||||
*/
|
||||
|
||||
import { assert } from 'chai';
|
||||
import { createPatternCanvas } from './CustomGlyphRasterizer';
|
||||
|
||||
describe('CustomGlyphRasterizer', () => {
|
||||
describe('createPatternCanvas', () => {
|
||||
it('prefers an offscreen canvas without using the DOM canvas factory', () => {
|
||||
const expectedCanvas = {} as OffscreenCanvas;
|
||||
const calls: string[] = [];
|
||||
|
||||
const canvas = createPatternCanvas(
|
||||
2,
|
||||
3,
|
||||
(width, height) => {
|
||||
calls.push(`offscreen:${width}x${height}`);
|
||||
return expectedCanvas;
|
||||
},
|
||||
() => {
|
||||
calls.push('dom');
|
||||
return {} as HTMLCanvasElement;
|
||||
}
|
||||
);
|
||||
|
||||
assert.deepEqual({ canvas, calls }, {
|
||||
canvas: expectedCanvas,
|
||||
calls: ['offscreen:2x3']
|
||||
});
|
||||
});
|
||||
|
||||
it('uses the main-realm DOM canvas factory when OffscreenCanvas is unavailable', () => {
|
||||
const expectedCanvas = {} as HTMLCanvasElement;
|
||||
const calls: string[] = [];
|
||||
|
||||
const canvas = createPatternCanvas(
|
||||
4,
|
||||
5,
|
||||
undefined,
|
||||
(width, height) => {
|
||||
calls.push(`dom:${width}x${height}`);
|
||||
return expectedCanvas;
|
||||
}
|
||||
);
|
||||
|
||||
assert.deepEqual({ canvas, calls }, {
|
||||
canvas: expectedCanvas,
|
||||
calls: ['dom:4x5']
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -8,29 +8,6 @@ import type { ILogService } from 'common/services/Services';
|
||||
import { customGlyphDefinitions } from './CustomGlyphDefinitions';
|
||||
import { CustomGlyphDefinitionType, CustomGlyphScaleType, CustomGlyphVectorType, type CustomGlyphDefinitionPart, type CustomGlyphPathDrawFunctionDefinition, type CustomGlyphPatternDefinition, type ICustomGlyphSolidOctantBlockVector, type ICustomGlyphVectorShape } from './Types';
|
||||
|
||||
type PatternCanvas = HTMLCanvasElement | OffscreenCanvas;
|
||||
type PatternCanvasFactory = (width: number, height: number) => PatternCanvas;
|
||||
|
||||
const createOffscreenPatternCanvas: PatternCanvasFactory | undefined = typeof OffscreenCanvas === 'undefined'
|
||||
? undefined
|
||||
: (width, height) => new OffscreenCanvas(width, height);
|
||||
|
||||
const createDomPatternCanvas: PatternCanvasFactory = (width, height) => {
|
||||
const canvas = document.createElement('canvas');
|
||||
canvas.width = width;
|
||||
canvas.height = height;
|
||||
return canvas;
|
||||
};
|
||||
|
||||
export function createPatternCanvas(
|
||||
width: number,
|
||||
height: number,
|
||||
offscreenCanvasFactory: PatternCanvasFactory | undefined = createOffscreenPatternCanvas,
|
||||
domCanvasFactory: PatternCanvasFactory = createDomPatternCanvas
|
||||
): PatternCanvas {
|
||||
return offscreenCanvasFactory?.(width, height) ?? domCanvasFactory(width, height);
|
||||
}
|
||||
|
||||
/**
|
||||
* Try drawing a custom block element or box drawing character, returning whether it was
|
||||
* successfully drawn.
|
||||
@@ -481,9 +458,9 @@ function drawPatternChar(
|
||||
if (!pattern) {
|
||||
const width = charDefinition[0].length;
|
||||
const height = charDefinition.length;
|
||||
// The atlas canvas can be adopted into another document, so temporary resources must not use
|
||||
// its mutable ownerDocument.
|
||||
const tmpCanvas = createPatternCanvas(width, height);
|
||||
const tmpCanvas = ctx.canvas.ownerDocument.createElement('canvas');
|
||||
tmpCanvas.width = width;
|
||||
tmpCanvas.height = height;
|
||||
const tmpCtx = throwIfFalsy(tmpCanvas.getContext('2d'));
|
||||
const imageData = new ImageData(width, height);
|
||||
|
||||
|
||||
@@ -7,8 +7,8 @@ import { ReadonlyColorSet } from 'browser/Types';
|
||||
import { acquireTextureAtlas } from '../CharAtlasCache';
|
||||
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 { Disposable, toDisposable } from 'common/primitives/Lifecycle';
|
||||
import { CellData } from 'common/primitives/buffer/CellData';
|
||||
import { IOptionsService } from 'common/services/Services';
|
||||
import { Terminal } from '@xterm/xterm';
|
||||
import { IRenderLayer } from './Types';
|
||||
|
||||
@@ -1,481 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2025 The xterm.js authors. All rights reserved.
|
||||
* @license MIT
|
||||
*/
|
||||
|
||||
import { IImage32, decodePng } from '@lunapaint/png-codec';
|
||||
import test, { expect } from '@playwright/test';
|
||||
import type { Terminal, ITerminalInitOnlyOptions, ITerminalOptions } from '@xterm/xterm';
|
||||
import type { IWebglAddonOptions, WebglAddon } from '@xterm/addon-webgl';
|
||||
import { ITestContext, createTestContext, openTerminal } from '../../../test/playwright/TestUtils';
|
||||
|
||||
type CellSignature = number[];
|
||||
type TestTerminalConstructor = new (options?: ITerminalOptions & ITerminalInitOnlyOptions) => ITestTerminal;
|
||||
type TestWebglAddonConstructor = new (options?: IWebglAddonOptions) => ITestWebglAddon;
|
||||
|
||||
interface ITestTextureAtlasConstructor {
|
||||
maxAtlasPages: number | undefined;
|
||||
maxTextureSize: number | undefined;
|
||||
}
|
||||
|
||||
interface ITestAtlasPage {
|
||||
canvas: HTMLCanvasElement;
|
||||
}
|
||||
|
||||
interface ITestTextureAtlas {
|
||||
constructor: ITestTextureAtlasConstructor;
|
||||
pages: ITestAtlasPage[];
|
||||
pageLayoutVersion: number;
|
||||
_overflowSizePage?: ITestAtlasPage;
|
||||
_textureSize: number;
|
||||
}
|
||||
|
||||
interface ITestGlyphRenderer {
|
||||
_atlasTextures: unknown[];
|
||||
}
|
||||
|
||||
interface ITestRenderer {
|
||||
_charAtlas?: ITestTextureAtlas;
|
||||
_glyphRenderer?: {
|
||||
value?: ITestGlyphRenderer;
|
||||
};
|
||||
dimensions: {
|
||||
device: {
|
||||
cell: {
|
||||
width: number;
|
||||
};
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
interface ITestRenderService {
|
||||
_renderer?: {
|
||||
value?: ITestRenderer;
|
||||
};
|
||||
}
|
||||
|
||||
interface ITestTerminal extends Terminal {
|
||||
_core?: {
|
||||
_renderService?: ITestRenderService;
|
||||
};
|
||||
}
|
||||
|
||||
interface ITestWebglAddon extends WebglAddon {
|
||||
_renderer?: ITestRenderer;
|
||||
}
|
||||
|
||||
interface IAtlasEventStats {
|
||||
removals: number;
|
||||
maxPages: number;
|
||||
}
|
||||
|
||||
interface ITestWindow extends Window {
|
||||
Terminal: TestTerminalConstructor;
|
||||
WebglAddon: TestWebglAddonConstructor;
|
||||
term: ITestTerminal;
|
||||
termB?: ITestTerminal;
|
||||
addon?: ITestWebglAddon;
|
||||
addonB?: ITestWebglAddon;
|
||||
atlasEventStats?: IAtlasEventStats;
|
||||
}
|
||||
|
||||
interface IAtlasLimits {
|
||||
maxAtlasPages: number;
|
||||
maxTextureSize: number;
|
||||
}
|
||||
|
||||
interface IAtlasState {
|
||||
pages: number;
|
||||
atlasTextures: number;
|
||||
pageLayoutVersion: number;
|
||||
overflowPageCreated: boolean;
|
||||
removals: number;
|
||||
maxPages: number;
|
||||
}
|
||||
|
||||
const NORMAL_HEADER = ' NORMAL_REF_0123456789';
|
||||
const POST_EVICTION_TEXT = ' AFTER_EVICTION_0123456789';
|
||||
const NORMAL_HEADER_COLS = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
|
||||
const POST_EVICTION_COLS = [2, 3, 4, 5, 6, 7, 8, 9, 10, 11];
|
||||
const TERM_B_SELECTOR = '#terminal-container-b .xterm-screen';
|
||||
const TERM_SELECTOR = '#terminal-container .xterm-screen';
|
||||
|
||||
async function loadWebglStrict(ctx: ITestContext): Promise<void> {
|
||||
await ctx.page.evaluate(() => {
|
||||
const w = window as unknown as ITestWindow;
|
||||
w.addon = new w.WebglAddon({ preserveDrawingBuffer: true });
|
||||
w.term.loadAddon(w.addon);
|
||||
});
|
||||
const isWebglRenderer = await ctx.page.evaluate(() => {
|
||||
const w = window as unknown as ITestWindow;
|
||||
return !!w.addon && w.term._core?._renderService?._renderer?.value === w.addon._renderer;
|
||||
});
|
||||
expect(isWebglRenderer, 'WebGL renderer must be active').toBe(true);
|
||||
}
|
||||
|
||||
async function createTerminalB(ctx: ITestContext, cols: number): Promise<void> {
|
||||
await ctx.page.evaluate(columnCount => {
|
||||
const w = window as unknown as ITestWindow;
|
||||
w.termB?.dispose();
|
||||
document.getElementById('terminal-container-b')?.remove();
|
||||
const element = document.createElement('div');
|
||||
element.id = 'terminal-container-b';
|
||||
document.body.appendChild(element);
|
||||
w.termB = new w.Terminal({ cols: columnCount, rows: 24, allowProposedApi: true });
|
||||
w.termB.open(element);
|
||||
w.addonB = new w.WebglAddon({ preserveDrawingBuffer: true });
|
||||
w.termB.loadAddon(w.addonB);
|
||||
}, cols);
|
||||
}
|
||||
|
||||
async function writeToBAndWaitForRender(ctx: ITestContext, data: string): Promise<void> {
|
||||
await ctx.page.evaluate(value => new Promise<void>(resolve => {
|
||||
const w = window as unknown as ITestWindow;
|
||||
if (!w.termB) {
|
||||
throw new Error('Terminal B must be created before writing to it');
|
||||
}
|
||||
const disposable = w.termB.onRender(() => {
|
||||
disposable.dispose();
|
||||
resolve();
|
||||
});
|
||||
w.termB.write(value);
|
||||
}), data);
|
||||
}
|
||||
|
||||
async function writeAndWaitForRender(ctx: ITestContext, data: string): Promise<void> {
|
||||
const renderPromise = new Promise<void>(resolve => {
|
||||
const disposable = ctx.proxy.onRender(() => {
|
||||
disposable.dispose();
|
||||
resolve();
|
||||
});
|
||||
});
|
||||
await ctx.proxy.write(data);
|
||||
await renderPromise;
|
||||
}
|
||||
|
||||
async function refreshAndWaitForRender(ctx: ITestContext, terminal: 'term' | 'termB'): Promise<void> {
|
||||
await ctx.page.evaluate(name => new Promise<void>(resolve => {
|
||||
const w = window as unknown as ITestWindow;
|
||||
const term = w[name];
|
||||
if (!term) {
|
||||
throw new Error(`${name} must exist before refreshing it`);
|
||||
}
|
||||
const disposable = term.onRender(() => {
|
||||
disposable.dispose();
|
||||
resolve();
|
||||
});
|
||||
term.refresh(0, term.rows - 1);
|
||||
}), terminal);
|
||||
}
|
||||
|
||||
async function configureAtlasLimits(ctx: ITestContext): Promise<IAtlasLimits> {
|
||||
const limits = await ctx.page.evaluate(() => {
|
||||
const w = window as unknown as ITestWindow;
|
||||
const atlas = w.term._core?._renderService?._renderer?.value?._charAtlas;
|
||||
if (!atlas || atlas.constructor.maxAtlasPages === undefined || atlas.constructor.maxTextureSize === undefined) {
|
||||
return undefined;
|
||||
}
|
||||
const result = {
|
||||
maxAtlasPages: atlas.constructor.maxAtlasPages,
|
||||
maxTextureSize: atlas.constructor.maxTextureSize
|
||||
};
|
||||
atlas.constructor.maxAtlasPages = 4;
|
||||
atlas.constructor.maxTextureSize = 512;
|
||||
return result;
|
||||
});
|
||||
expect(limits, 'TextureAtlas limits must be initialized').toBeDefined();
|
||||
return limits!;
|
||||
}
|
||||
|
||||
async function restoreAtlasLimits(ctx: ITestContext, limits: IAtlasLimits | undefined): Promise<void> {
|
||||
if (!limits) {
|
||||
return;
|
||||
}
|
||||
await ctx.page.evaluate(original => {
|
||||
const w = window as unknown as ITestWindow;
|
||||
const atlas = w.term._core?._renderService?._renderer?.value?._charAtlas;
|
||||
if (atlas) {
|
||||
atlas.constructor.maxAtlasPages = original.maxAtlasPages;
|
||||
atlas.constructor.maxTextureSize = original.maxTextureSize;
|
||||
}
|
||||
}, limits);
|
||||
}
|
||||
|
||||
async function installAtlasEventStats(ctx: ITestContext): Promise<void> {
|
||||
const installed = await ctx.page.evaluate(() => {
|
||||
const w = window as unknown as ITestWindow;
|
||||
const atlas = w.term._core?._renderService?._renderer?.value?._charAtlas;
|
||||
if (!atlas || !w.addon) {
|
||||
return false;
|
||||
}
|
||||
const stats: IAtlasEventStats = {
|
||||
removals: 0,
|
||||
maxPages: atlas.pages.length
|
||||
};
|
||||
w.atlasEventStats = stats;
|
||||
w.addon.onAddTextureAtlasCanvas(() => {
|
||||
stats.maxPages = Math.max(stats.maxPages, atlas.pages.length);
|
||||
});
|
||||
w.addon.onRemoveTextureAtlasCanvas(() => stats.removals++);
|
||||
return true;
|
||||
});
|
||||
expect(installed, 'atlas event tracking must be installed').toBe(true);
|
||||
}
|
||||
|
||||
async function getAtlasState(ctx: ITestContext): Promise<IAtlasState> {
|
||||
const state = await ctx.page.evaluate(() => {
|
||||
const w = window as unknown as ITestWindow;
|
||||
const renderer = w.term._core?._renderService?._renderer?.value;
|
||||
const atlas = renderer?._charAtlas;
|
||||
const glyphRenderer = renderer?._glyphRenderer?.value;
|
||||
const stats = w.atlasEventStats;
|
||||
if (!atlas || !glyphRenderer || !stats) {
|
||||
return undefined;
|
||||
}
|
||||
return {
|
||||
pages: atlas.pages.length,
|
||||
atlasTextures: glyphRenderer._atlasTextures.length,
|
||||
pageLayoutVersion: atlas.pageLayoutVersion,
|
||||
overflowPageCreated: !!atlas._overflowSizePage,
|
||||
removals: stats.removals,
|
||||
maxPages: stats.maxPages
|
||||
};
|
||||
});
|
||||
expect(state, 'atlas state must be available').toBeDefined();
|
||||
return state!;
|
||||
}
|
||||
|
||||
function generateUniqueGlyphFlood(count: number, cols: number, offset: number): string {
|
||||
const base = 0x4E00;
|
||||
const range = 0x9FFF - base;
|
||||
const perRow = Math.max(1, Math.floor(cols / 2));
|
||||
let result = '';
|
||||
for (let i = 0; i < count; i++) {
|
||||
result += String.fromCodePoint(base + ((offset + i) % range));
|
||||
if ((i + 1) % perRow === 0 && i + 1 < count) {
|
||||
result += '\r\n';
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
async function captureRowSignatures(ctx: ITestContext, selector: string, cols: number, row: number, sampleCols: number[], debugName: string): Promise<CellSignature[]> {
|
||||
const screenshotOptions = process.env.DEBUG ? { path: `out-esbuild-test/playwright/${debugName}.png` } : undefined;
|
||||
const buffer = await ctx.page.locator(selector).screenshot(screenshotOptions);
|
||||
const frame = {
|
||||
cols,
|
||||
rows: 24,
|
||||
decoded: (await decodePng(new Uint8Array(buffer), { force32: true })).image
|
||||
};
|
||||
return sampleCols.map(col => cellSignature(frame, col, row));
|
||||
}
|
||||
|
||||
function cellSignature(frame: { cols: number, rows: number, decoded: IImage32 }, col: number, row: number): CellSignature {
|
||||
const grid = 4;
|
||||
const data = frame.decoded.data;
|
||||
const imageWidth = frame.decoded.width;
|
||||
const cellWidth = frame.decoded.width / frame.cols;
|
||||
const cellHeight = frame.decoded.height / frame.rows;
|
||||
const x0 = (col - 1) * cellWidth;
|
||||
const y0 = (row - 1) * cellHeight;
|
||||
const sums = new Array<number>(grid * grid * 4).fill(0);
|
||||
const counts = new Array<number>(grid * grid).fill(0);
|
||||
const startX = Math.max(0, Math.floor(x0));
|
||||
const startY = Math.max(0, Math.floor(y0));
|
||||
const endX = Math.min(frame.decoded.width, Math.ceil(x0 + cellWidth));
|
||||
const endY = Math.min(frame.decoded.height, Math.ceil(y0 + cellHeight));
|
||||
|
||||
for (let y = startY; y < endY; y++) {
|
||||
const gy = Math.min(grid - 1, Math.max(0, Math.floor(((y - y0) / cellHeight) * grid)));
|
||||
for (let x = startX; x < endX; x++) {
|
||||
const gx = Math.min(grid - 1, Math.max(0, Math.floor(((x - x0) / cellWidth) * grid)));
|
||||
const sub = gy * grid + gx;
|
||||
const i = (y * imageWidth + x) * 4;
|
||||
sums[sub * 4] += data[i];
|
||||
sums[sub * 4 + 1] += data[i + 1];
|
||||
sums[sub * 4 + 2] += data[i + 2];
|
||||
sums[sub * 4 + 3] += data[i + 3];
|
||||
counts[sub]++;
|
||||
}
|
||||
}
|
||||
|
||||
const signature = new Array<number>(grid * grid * 4).fill(0);
|
||||
for (let sub = 0; sub < counts.length; sub++) {
|
||||
if (counts[sub] > 0) {
|
||||
signature[sub * 4] = sums[sub * 4] / counts[sub];
|
||||
signature[sub * 4 + 1] = sums[sub * 4 + 1] / counts[sub];
|
||||
signature[sub * 4 + 2] = sums[sub * 4 + 2] / counts[sub];
|
||||
signature[sub * 4 + 3] = sums[sub * 4 + 3] / counts[sub];
|
||||
}
|
||||
}
|
||||
return signature;
|
||||
}
|
||||
|
||||
function signatureDiff(actual: CellSignature, reference: CellSignature): number {
|
||||
let diff = 0;
|
||||
for (let i = 0; i < actual.length; i++) {
|
||||
diff = Math.max(diff, Math.abs(actual[i] - reference[i]));
|
||||
}
|
||||
return diff;
|
||||
}
|
||||
|
||||
function expectSignatureMatches(actual: CellSignature, reference: CellSignature, message: string): void {
|
||||
const diff = signatureDiff(actual, reference);
|
||||
expect(diff, `${message}; max channel diff ${diff}`).toBeLessThanOrEqual(14);
|
||||
}
|
||||
|
||||
test.describe('atlas page overflow (#6038)', () => {
|
||||
test.skip(({ browserName }) => browserName !== 'chromium');
|
||||
test.describe.configure({ timeout: 90000 });
|
||||
|
||||
test('evicts normal pages when the page cap cannot be reduced by merging', async ({ browser }) => {
|
||||
const ctx = await createTestContext(browser);
|
||||
const errors: string[] = [];
|
||||
const onError = (error: Error): void => { errors.push(error.message); };
|
||||
let limits: IAtlasLimits | undefined;
|
||||
ctx.page.on('pageerror', onError);
|
||||
try {
|
||||
await openTerminal(ctx, { cols: 80, rows: 24 });
|
||||
await loadWebglStrict(ctx);
|
||||
limits = await configureAtlasLimits(ctx);
|
||||
|
||||
await openTerminal(ctx, { cols: 80, rows: 24 });
|
||||
await loadWebglStrict(ctx);
|
||||
await createTerminalB(ctx, 80);
|
||||
await installAtlasEventStats(ctx);
|
||||
|
||||
const atlasShared = await ctx.page.evaluate(() => {
|
||||
const w = window as unknown as ITestWindow;
|
||||
return w.term._core?._renderService?._renderer?.value?._charAtlas === w.termB?._core?._renderService?._renderer?.value?._charAtlas;
|
||||
});
|
||||
expect(atlasShared, 'terminals with equal configs should share one texture atlas').toBe(true);
|
||||
|
||||
await writeToBAndWaitForRender(ctx, `\x1b[H${NORMAL_HEADER}\x1b[2;1H${POST_EVICTION_TEXT}`);
|
||||
const headerReference = await captureRowSignatures(ctx, TERM_B_SELECTOR, 80, 1, NORMAL_HEADER_COLS, 'atlas-overflow-header-reference');
|
||||
const postReference = await captureRowSignatures(ctx, TERM_B_SELECTOR, 80, 2, POST_EVICTION_COLS, 'atlas-overflow-post-reference');
|
||||
await writeToBAndWaitForRender(ctx, '\x1b[2;1H\x1b[2K');
|
||||
|
||||
const initialState = await getAtlasState(ctx);
|
||||
const glyphsPerChunk = 23 * 40 - 1;
|
||||
for (let chunk = 0; chunk < 24; chunk++) {
|
||||
await ctx.proxy.write('\x1b[H\x1b[2J' + generateUniqueGlyphFlood(glyphsPerChunk, 80, chunk * glyphsPerChunk));
|
||||
await ctx.page.waitForTimeout(50);
|
||||
const state = await getAtlasState(ctx);
|
||||
if (state.removals > 0 || state.pages > state.atlasTextures || errors.length > 0) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
const evictionState = await getAtlasState(ctx);
|
||||
expect(evictionState.removals, 'normal atlas pages must be evicted at the page cap').toBeGreaterThan(0);
|
||||
expect(evictionState.pageLayoutVersion, 'eviction must invalidate renderer models').toBeGreaterThan(initialState.pageLayoutVersion);
|
||||
expect(evictionState.maxPages, 'atlas pages must stay within renderer texture capacity').toBeLessThanOrEqual(evictionState.atlasTextures);
|
||||
expect(errors, `renderer must not throw during atlas eviction: ${errors[0] ?? ''}`).toEqual([]);
|
||||
|
||||
await writeToBAndWaitForRender(ctx, `\x1b[2;1H${POST_EVICTION_TEXT}`);
|
||||
const termBHeader = await ctx.page.evaluate(() => {
|
||||
const w = window as unknown as ITestWindow;
|
||||
return w.termB?.buffer.active.getLine(0)?.translateToString(true);
|
||||
});
|
||||
expect(termBHeader, 'terminal B buffer must still contain the header').toBe(NORMAL_HEADER);
|
||||
|
||||
const headerAfter = await captureRowSignatures(ctx, TERM_B_SELECTOR, 80, 1, NORMAL_HEADER_COLS, 'atlas-overflow-header-after');
|
||||
const postAfter = await captureRowSignatures(ctx, TERM_B_SELECTOR, 80, 2, POST_EVICTION_COLS, 'atlas-overflow-post-after');
|
||||
for (let i = 0; i < NORMAL_HEADER_COLS.length; i++) {
|
||||
expectSignatureMatches(headerAfter[i], headerReference[i], `existing header col ${NORMAL_HEADER_COLS[i]} changed after eviction`);
|
||||
}
|
||||
for (let i = 0; i < POST_EVICTION_COLS.length; i++) {
|
||||
expectSignatureMatches(postAfter[i], postReference[i], `new text col ${POST_EVICTION_COLS[i]} changed after eviction`);
|
||||
}
|
||||
|
||||
const stableState = await getAtlasState(ctx);
|
||||
await refreshAndWaitForRender(ctx, 'termB');
|
||||
const refreshedState = await getAtlasState(ctx);
|
||||
expect(refreshedState.pageLayoutVersion, 'an unchanged full refresh must not evict again').toBe(stableState.pageLayoutVersion);
|
||||
expect(refreshedState.removals, 'an unchanged full refresh must not remove more pages').toBe(stableState.removals);
|
||||
} finally {
|
||||
ctx.page.off('pageerror', onError);
|
||||
await restoreAtlasLimits(ctx, limits).catch(() => {});
|
||||
await ctx.page.close();
|
||||
}
|
||||
});
|
||||
|
||||
test('evicts before adding an oversized glyph page at the page cap', async ({ browser }) => {
|
||||
const ctx = await createTestContext(browser);
|
||||
const errors: string[] = [];
|
||||
const onError = (error: Error): void => { errors.push(error.message); };
|
||||
let limits: IAtlasLimits | undefined;
|
||||
ctx.page.on('pageerror', onError);
|
||||
try {
|
||||
await openTerminal(ctx, { cols: 160, rows: 24 });
|
||||
await loadWebglStrict(ctx);
|
||||
limits = await configureAtlasLimits(ctx);
|
||||
|
||||
await openTerminal(ctx, { cols: 160, rows: 24 });
|
||||
await loadWebglStrict(ctx);
|
||||
await installAtlasEventStats(ctx);
|
||||
|
||||
let state = await getAtlasState(ctx);
|
||||
let offset = 0;
|
||||
for (let chunk = 0; chunk < 120 && state.pages < state.atlasTextures; chunk++) {
|
||||
const glyphCount = state.pages < state.atlasTextures - 1 ? 200 : 25;
|
||||
await ctx.proxy.write('\x1b[H\x1b[2J' + generateUniqueGlyphFlood(glyphCount, 160, offset));
|
||||
await ctx.page.waitForTimeout(50);
|
||||
offset += glyphCount;
|
||||
state = await getAtlasState(ctx);
|
||||
}
|
||||
expect(state.pages, 'normal atlas pages must reach the texture capacity before the oversized glyph').toBe(state.atlasTextures);
|
||||
expect(state.overflowPageCreated, 'normal glyph filling must not create the oversized page').toBe(false);
|
||||
expect(errors, `renderer must not fail while filling normal pages: ${errors[0] ?? ''}`).toEqual([]);
|
||||
|
||||
await writeAndWaitForRender(ctx, '\x1b[H\x1b[2K\x1b[24;1H');
|
||||
const sampleCols = [1, 8, 16, 24, 32];
|
||||
const blankSignatures = await captureRowSignatures(ctx, TERM_SELECTOR, 160, 1, sampleCols, 'atlas-overflow-wide-blank');
|
||||
const joinedLength = await ctx.page.evaluate(() => {
|
||||
const w = window as unknown as ITestWindow;
|
||||
const renderer = w.term._core?._renderService?._renderer?.value;
|
||||
const atlas = renderer?._charAtlas;
|
||||
if (!renderer || !atlas) {
|
||||
throw new Error('renderer and atlas must exist');
|
||||
}
|
||||
const cellWidth = renderer.dimensions.device.cell.width;
|
||||
const length = Math.min(w.term.cols - 1, Math.ceil(atlas._textureSize / cellWidth) + 32);
|
||||
if (length * cellWidth <= atlas._textureSize) {
|
||||
throw new Error('joined glyph must be wider than a normal atlas page');
|
||||
}
|
||||
const marker = 'W'.repeat(length);
|
||||
w.term.registerCharacterJoiner(text => text.startsWith(marker) ? [[0, length]] : []);
|
||||
return length;
|
||||
});
|
||||
|
||||
const beforeOversized = await getAtlasState(ctx);
|
||||
await ctx.proxy.write(`\x1b[H${'W'.repeat(joinedLength)}`);
|
||||
await ctx.page.waitForTimeout(50);
|
||||
const oversizedState = await getAtlasState(ctx);
|
||||
expect(oversizedState.overflowPageCreated, 'the joined glyph must use the oversized atlas page').toBe(true);
|
||||
expect(oversizedState.removals, 'normal pages must be evicted before adding the oversized page').toBeGreaterThan(beforeOversized.removals);
|
||||
expect(oversizedState.maxPages, 'oversized page creation must stay within renderer texture capacity').toBeLessThanOrEqual(oversizedState.atlasTextures);
|
||||
expect(oversizedState.pageLayoutVersion, 'oversized eviction must invalidate renderer models').toBeGreaterThan(beforeOversized.pageLayoutVersion);
|
||||
expect(errors, `renderer must not throw while adding the oversized page: ${errors[0] ?? ''}`).toEqual([]);
|
||||
|
||||
const wideSignatures = await captureRowSignatures(ctx, TERM_SELECTOR, 160, 1, sampleCols, 'atlas-overflow-wide-after');
|
||||
for (let i = 0; i < sampleCols.length; i++) {
|
||||
expect(signatureDiff(wideSignatures[i], blankSignatures[i]), `wide glyph col ${sampleCols[i]} must render visible pixels`).toBeGreaterThan(14);
|
||||
}
|
||||
|
||||
const stableState = await getAtlasState(ctx);
|
||||
await refreshAndWaitForRender(ctx, 'term');
|
||||
const refreshedState = await getAtlasState(ctx);
|
||||
expect(refreshedState.pageLayoutVersion, 'oversized glyph refresh must settle without another eviction').toBe(stableState.pageLayoutVersion);
|
||||
expect(refreshedState.removals, 'oversized glyph refresh must not remove more pages').toBe(stableState.removals);
|
||||
const wideAfterRefresh = await captureRowSignatures(ctx, TERM_SELECTOR, 160, 1, sampleCols, 'atlas-overflow-wide-refreshed');
|
||||
for (let i = 0; i < sampleCols.length; i++) {
|
||||
expectSignatureMatches(wideAfterRefresh[i], wideSignatures[i], `wide glyph col ${sampleCols[i]} changed after refresh`);
|
||||
}
|
||||
} finally {
|
||||
ctx.page.off('pageerror', onError);
|
||||
await restoreAtlasLimits(ctx, limits).catch(() => {});
|
||||
await ctx.page.close();
|
||||
}
|
||||
});
|
||||
});
|
||||
@@ -1,288 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2025 The xterm.js authors. All rights reserved.
|
||||
* @license MIT
|
||||
*/
|
||||
|
||||
import { decodePng, type IImage32 } from '@lunapaint/png-codec';
|
||||
import test, { expect } from '@playwright/test';
|
||||
import type { Terminal, ITerminalInitOnlyOptions, ITerminalOptions } from '@xterm/xterm';
|
||||
import type { IWebglAddonOptions, WebglAddon } from '@xterm/addon-webgl';
|
||||
import { ITestContext, createTestContext, openTerminal } from '../../../test/playwright/TestUtils';
|
||||
|
||||
type CellSignature = number[];
|
||||
type TestTerminalConstructor = new (options?: ITerminalOptions & ITerminalInitOnlyOptions) => ITestTerminal;
|
||||
type TestWebglAddonConstructor = new (options?: IWebglAddonOptions) => ITestWebglAddon;
|
||||
|
||||
interface ITestTextureAtlas {
|
||||
readonly pages: ReadonlyArray<{ readonly canvas: HTMLCanvasElement }>;
|
||||
}
|
||||
|
||||
interface ITestGlyphRenderer {
|
||||
readonly _atlasTextures: ReadonlyArray<unknown>;
|
||||
}
|
||||
|
||||
interface ITestRenderer {
|
||||
_charAtlas?: ITestTextureAtlas;
|
||||
_glyphRenderer?: {
|
||||
value?: ITestGlyphRenderer;
|
||||
};
|
||||
}
|
||||
|
||||
interface ITestRenderService {
|
||||
_renderer?: {
|
||||
value?: ITestRenderer;
|
||||
};
|
||||
}
|
||||
|
||||
interface ITestTerminal extends Terminal {
|
||||
_core?: {
|
||||
_renderService?: ITestRenderService;
|
||||
};
|
||||
}
|
||||
|
||||
interface ITestWebglAddon extends WebglAddon {
|
||||
_renderer?: ITestRenderer;
|
||||
}
|
||||
|
||||
interface IAtlasEventStats {
|
||||
additions: number;
|
||||
maxPages: number;
|
||||
}
|
||||
|
||||
interface ITestWindow {
|
||||
WebglAddon: TestWebglAddonConstructor;
|
||||
term: ITestTerminal;
|
||||
addon?: ITestWebglAddon;
|
||||
atlasEventStats?: IAtlasEventStats;
|
||||
}
|
||||
|
||||
interface IAtlasState {
|
||||
additions: number;
|
||||
atlasTextures: number;
|
||||
maxPages: number;
|
||||
}
|
||||
|
||||
const REFERENCE_LINE = 'Ref0123456789ABCDEF';
|
||||
const REFERENCE_COLS = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19];
|
||||
const TERM_SELECTOR = '#terminal-container .xterm-screen';
|
||||
|
||||
async function loadWebglStrict(ctx: ITestContext): Promise<void> {
|
||||
await ctx.page.evaluate(() => {
|
||||
const w = window as unknown as ITestWindow;
|
||||
w.addon = new w.WebglAddon({ preserveDrawingBuffer: true });
|
||||
w.term.loadAddon(w.addon);
|
||||
});
|
||||
const isWebglRenderer = await ctx.page.evaluate(() => {
|
||||
const w = window as unknown as ITestWindow;
|
||||
return !!w.addon && w.term._core?._renderService?._renderer?.value === w.addon._renderer;
|
||||
});
|
||||
expect(isWebglRenderer, 'WebGL renderer must be active').toBe(true);
|
||||
}
|
||||
|
||||
async function installAtlasEventStats(ctx: ITestContext): Promise<void> {
|
||||
const installed = await ctx.page.evaluate(() => {
|
||||
const w = window as unknown as ITestWindow;
|
||||
const atlas = w.term._core?._renderService?._renderer?.value?._charAtlas;
|
||||
if (!atlas || !w.addon) {
|
||||
return false;
|
||||
}
|
||||
const stats: IAtlasEventStats = {
|
||||
additions: 0,
|
||||
maxPages: atlas.pages.length
|
||||
};
|
||||
w.atlasEventStats = stats;
|
||||
w.addon.onAddTextureAtlasCanvas(() => {
|
||||
stats.additions++;
|
||||
stats.maxPages = Math.max(stats.maxPages, atlas.pages.length);
|
||||
});
|
||||
return true;
|
||||
});
|
||||
expect(installed, 'atlas event tracking must be installed').toBe(true);
|
||||
}
|
||||
|
||||
async function getAtlasState(ctx: ITestContext): Promise<IAtlasState> {
|
||||
const state = await ctx.page.evaluate(() => {
|
||||
const w = window as unknown as ITestWindow;
|
||||
const renderer = w.term._core?._renderService?._renderer?.value;
|
||||
const glyphRenderer = renderer?._glyphRenderer?.value;
|
||||
const stats = w.atlasEventStats;
|
||||
if (!glyphRenderer || !stats) {
|
||||
return undefined;
|
||||
}
|
||||
return {
|
||||
additions: stats.additions,
|
||||
atlasTextures: glyphRenderer._atlasTextures.length,
|
||||
maxPages: stats.maxPages
|
||||
};
|
||||
});
|
||||
expect(state, 'atlas state must be available').toBeDefined();
|
||||
return state!;
|
||||
}
|
||||
|
||||
async function writeAndWaitForRender(ctx: ITestContext, data: string): Promise<void> {
|
||||
const renderPromise = new Promise<void>(resolve => {
|
||||
const disposable = ctx.proxy.onRender(() => {
|
||||
disposable.dispose();
|
||||
resolve();
|
||||
});
|
||||
});
|
||||
await ctx.proxy.write(data);
|
||||
await renderPromise;
|
||||
}
|
||||
|
||||
async function refreshAndWaitForRender(ctx: ITestContext): Promise<void> {
|
||||
const renderPromise = new Promise<void>(resolve => {
|
||||
const disposable = ctx.proxy.onRender(() => {
|
||||
disposable.dispose();
|
||||
resolve();
|
||||
});
|
||||
});
|
||||
await ctx.proxy.refresh(0, (await ctx.proxy.rows) - 1);
|
||||
await renderPromise;
|
||||
}
|
||||
|
||||
function generateSyntheticTuiFrames(frameCount: number, cols: number, rows: number): string[] {
|
||||
const spinner = ['|', '/', '-', '\\'];
|
||||
const words = ['reading', 'rendering', 'atlas', 'terminal', 'buffer', 'tokens', 'diff', 'patch'];
|
||||
const frames: string[] = [];
|
||||
for (let frameIndex = 0; frameIndex < frameCount; frameIndex++) {
|
||||
const lines = [
|
||||
`+${'-'.repeat(cols - 2)}+`,
|
||||
`|${(` ${spinner[frameIndex % spinner.length]} Copilot is working`).padEnd(cols - 2).slice(0, cols - 2)}|`,
|
||||
`+${'-'.repeat(cols - 2)}+`
|
||||
];
|
||||
for (let row = 3; row < rows - 1; row++) {
|
||||
const foreground = 31 + ((frameIndex + row) % 6);
|
||||
const text = `${words[(frameIndex + row) % words.length]} ${frameIndex}:${row}`;
|
||||
lines.push(`\x1b[${foreground}m${text}\x1b[0m`);
|
||||
}
|
||||
lines.push(`\x1b[2mtokens ${1000 + frameIndex * 37}\x1b[0m`);
|
||||
frames.push(`\x1b[2J\x1b[H${lines.join('\r\n')}`);
|
||||
}
|
||||
return frames;
|
||||
}
|
||||
|
||||
function generateUniqueGlyphFlood(count: number, cols: number, offset: number): string {
|
||||
const base = 0x4E00;
|
||||
const range = 0x9FFF - base;
|
||||
const perRow = Math.max(1, Math.floor(cols / 2));
|
||||
let result = '';
|
||||
for (let i = 0; i < count; i++) {
|
||||
result += String.fromCodePoint(base + ((offset + i) % range));
|
||||
if ((i + 1) % perRow === 0 && i + 1 < count) {
|
||||
result += '\r\n';
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
async function captureRowSignatures(ctx: ITestContext, row: number, cols: number[], debugName: string): Promise<CellSignature[]> {
|
||||
const screenshotOptions = process.env.DEBUG ? { path: `out-esbuild-test/playwright/${debugName}.png` } : undefined;
|
||||
const buffer = await ctx.page.locator(TERM_SELECTOR).screenshot(screenshotOptions);
|
||||
const frame = {
|
||||
cols: await ctx.proxy.cols,
|
||||
rows: await ctx.proxy.rows,
|
||||
decoded: (await decodePng(new Uint8Array(buffer), { force32: true })).image
|
||||
};
|
||||
return cols.map(col => cellSignature(frame, col, row));
|
||||
}
|
||||
|
||||
function cellSignature(frame: { cols: number, rows: number, decoded: IImage32 }, col: number, row: number): CellSignature {
|
||||
const grid = 4;
|
||||
const data = frame.decoded.data;
|
||||
const imageWidth = frame.decoded.width;
|
||||
const cellWidth = frame.decoded.width / frame.cols;
|
||||
const cellHeight = frame.decoded.height / frame.rows;
|
||||
const x0 = (col - 1) * cellWidth;
|
||||
const y0 = (row - 1) * cellHeight;
|
||||
const sums = new Array<number>(grid * grid * 4).fill(0);
|
||||
const counts = new Array<number>(grid * grid).fill(0);
|
||||
const startX = Math.max(0, Math.floor(x0));
|
||||
const startY = Math.max(0, Math.floor(y0));
|
||||
const endX = Math.min(frame.decoded.width, Math.ceil(x0 + cellWidth));
|
||||
const endY = Math.min(frame.decoded.height, Math.ceil(y0 + cellHeight));
|
||||
|
||||
for (let y = startY; y < endY; y++) {
|
||||
const gy = Math.min(grid - 1, Math.max(0, Math.floor(((y - y0) / cellHeight) * grid)));
|
||||
for (let x = startX; x < endX; x++) {
|
||||
const gx = Math.min(grid - 1, Math.max(0, Math.floor(((x - x0) / cellWidth) * grid)));
|
||||
const sub = gy * grid + gx;
|
||||
const i = (y * imageWidth + x) * 4;
|
||||
sums[sub * 4] += data[i];
|
||||
sums[sub * 4 + 1] += data[i + 1];
|
||||
sums[sub * 4 + 2] += data[i + 2];
|
||||
sums[sub * 4 + 3] += data[i + 3];
|
||||
counts[sub]++;
|
||||
}
|
||||
}
|
||||
|
||||
const signature = new Array<number>(grid * grid * 4).fill(0);
|
||||
for (let sub = 0; sub < counts.length; sub++) {
|
||||
if (counts[sub] > 0) {
|
||||
signature[sub * 4] = sums[sub * 4] / counts[sub];
|
||||
signature[sub * 4 + 1] = sums[sub * 4 + 1] / counts[sub];
|
||||
signature[sub * 4 + 2] = sums[sub * 4 + 2] / counts[sub];
|
||||
signature[sub * 4 + 3] = sums[sub * 4 + 3] / counts[sub];
|
||||
}
|
||||
}
|
||||
return signature;
|
||||
}
|
||||
|
||||
function expectSignatureMatches(actual: CellSignature, reference: CellSignature, message: string): void {
|
||||
let diff = 0;
|
||||
for (let i = 0; i < actual.length; i++) {
|
||||
diff = Math.max(diff, Math.abs(actual[i] - reference[i]));
|
||||
}
|
||||
expect(diff, `${message}; max channel diff ${diff}`).toBeLessThanOrEqual(14);
|
||||
}
|
||||
|
||||
test.describe('WebGL atlas TUI stress (#6038)', () => {
|
||||
test.skip(({ browserName }) => browserName !== 'chromium', 'WebGL atlas tests run on Chromium/SwiftShader only');
|
||||
test.describe.configure({ timeout: 90000 });
|
||||
|
||||
test('primary-buffer glyphs survive alternate-buffer redraws and atlas page churn', async ({ browser }) => {
|
||||
const ctx = await createTestContext(browser);
|
||||
const errors: string[] = [];
|
||||
const onError = (error: Error): void => { errors.push(error.message); };
|
||||
ctx.page.on('pageerror', onError);
|
||||
try {
|
||||
await openTerminal(ctx, { cols: 80, rows: 24, scrollback: 5000 });
|
||||
await loadWebglStrict(ctx);
|
||||
await installAtlasEventStats(ctx);
|
||||
|
||||
await writeAndWaitForRender(ctx, REFERENCE_LINE);
|
||||
const reference = await captureRowSignatures(ctx, 1, REFERENCE_COLS, 'atlas-stress-reference');
|
||||
|
||||
await writeAndWaitForRender(ctx, '\x1b[?1049h');
|
||||
for (const frame of generateSyntheticTuiFrames(30, 80, 24)) {
|
||||
await writeAndWaitForRender(ctx, frame);
|
||||
}
|
||||
const glyphsPerViewport = 23 * 40 - 1;
|
||||
for (let chunk = 0; chunk < 4; chunk++) {
|
||||
await writeAndWaitForRender(ctx, `\x1b[2J\x1b[H${generateUniqueGlyphFlood(glyphsPerViewport, 80, chunk * glyphsPerViewport)}`);
|
||||
}
|
||||
await writeAndWaitForRender(ctx, '\x1b[?1049l');
|
||||
await refreshAndWaitForRender(ctx);
|
||||
|
||||
const state = await getAtlasState(ctx);
|
||||
expect(state.additions, 'stress workload must add atlas pages').toBeGreaterThan(0);
|
||||
expect(state.maxPages, 'stress workload must exercise multiple atlas pages').toBeGreaterThan(1);
|
||||
expect(state.maxPages, 'atlas pages must remain within renderer texture capacity').toBeLessThanOrEqual(state.atlasTextures);
|
||||
expect(errors, `renderer must not throw during TUI stress: ${errors[0] ?? ''}`).toEqual([]);
|
||||
|
||||
const primaryText = await ctx.page.evaluate(() => {
|
||||
const w = window as unknown as ITestWindow;
|
||||
return w.term.buffer.active.getLine(0)?.translateToString(true);
|
||||
});
|
||||
expect(primaryText, 'primary buffer must still contain the reference line').toBe(REFERENCE_LINE);
|
||||
|
||||
const after = await captureRowSignatures(ctx, 1, REFERENCE_COLS, 'atlas-stress-after');
|
||||
for (let i = 0; i < REFERENCE_COLS.length; i++) {
|
||||
expectSignatureMatches(after[i], reference[i], `primary-buffer glyph at col ${REFERENCE_COLS[i]} changed after TUI stress`);
|
||||
}
|
||||
} finally {
|
||||
ctx.page.off('pageerror', onError);
|
||||
await ctx.page.close();
|
||||
}
|
||||
});
|
||||
});
|
||||
@@ -1,77 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2026 The xterm.js authors. All rights reserved.
|
||||
* @license MIT
|
||||
*/
|
||||
|
||||
import test, { expect } from '@playwright/test';
|
||||
import { platform } from 'os';
|
||||
import { ITestContext, createTestContext, openTerminal } from '../../../test/playwright/TestUtils';
|
||||
|
||||
interface ITestRendererWithAtlasCanvas {
|
||||
readonly _charAtlas?: {
|
||||
readonly _tmpCanvas?: HTMLCanvasElement;
|
||||
};
|
||||
}
|
||||
|
||||
async function writeAndWaitForRender(ctx: ITestContext, data: string): Promise<void> {
|
||||
const renderPromise = new Promise<void>(resolve => {
|
||||
const disposable = ctx.proxy.onRender(() => {
|
||||
disposable.dispose();
|
||||
resolve();
|
||||
});
|
||||
});
|
||||
await ctx.proxy.write(data);
|
||||
await renderPromise;
|
||||
}
|
||||
|
||||
test.describe('WebGL custom glyphs', () => {
|
||||
if (platform() === 'linux') {
|
||||
test.skip(({ browserName }) => browserName === 'firefox' || browserName === 'webkit');
|
||||
}
|
||||
|
||||
test('pattern glyphs render after the terminal is adopted into another document', async ({ browser }) => {
|
||||
const ctx = await createTestContext(browser);
|
||||
const errors: string[] = [];
|
||||
const onError = (error: Error): void => { errors.push(error.message); };
|
||||
ctx.page.on('pageerror', onError);
|
||||
try {
|
||||
await openTerminal(ctx);
|
||||
await ctx.page.evaluate(`
|
||||
window.addon = new window.WebglAddon({ customGlyphs: true });
|
||||
window.term.loadAddon(window.addon);
|
||||
`);
|
||||
await ctx.page.evaluate(() => {
|
||||
const frame = document.createElement('iframe');
|
||||
document.body.appendChild(frame);
|
||||
const auxiliaryDocument = frame.contentDocument;
|
||||
if (!auxiliaryDocument?.body || !window.term.element) {
|
||||
throw new Error('Auxiliary document and terminal element must be available');
|
||||
}
|
||||
const renderer = window.term._core?._renderService?._renderer?.value as ITestRendererWithAtlasCanvas | undefined;
|
||||
const atlasCanvas = renderer?._charAtlas?._tmpCanvas;
|
||||
if (!atlasCanvas) {
|
||||
throw new Error('Texture atlas canvas must be available');
|
||||
}
|
||||
// Model the atlas state after an uncached glyph has attached the measurement canvas.
|
||||
window.term.element.appendChild(atlasCanvas);
|
||||
auxiliaryDocument.body.appendChild(window.term.element);
|
||||
if (atlasCanvas.ownerDocument !== auxiliaryDocument) {
|
||||
throw new Error('Texture atlas canvas must be adopted into the auxiliary document');
|
||||
}
|
||||
auxiliaryDocument.createElement = () => {
|
||||
throw new Error('Not allowed to create elements in the auxiliary document');
|
||||
};
|
||||
});
|
||||
|
||||
await writeAndWaitForRender(ctx, '\u2591');
|
||||
await writeAndWaitForRender(ctx, 'X');
|
||||
|
||||
const line = await ctx.page.evaluate(() => window.term.buffer.active.getLine(0)?.translateToString(true));
|
||||
expect(errors, `renderer must not create pattern canvases in the auxiliary document: ${errors[0] ?? ''}`).toEqual([]);
|
||||
expect(line).toBe('\u2591X');
|
||||
} finally {
|
||||
ctx.page.off('pageerror', onError);
|
||||
await ctx.page.close();
|
||||
}
|
||||
});
|
||||
});
|
||||
@@ -1,282 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2025 The xterm.js authors. All rights reserved.
|
||||
* @license MIT
|
||||
*/
|
||||
|
||||
import { IImage32, decodePng } from '@lunapaint/png-codec';
|
||||
import test, { expect } from '@playwright/test';
|
||||
import type { Terminal, ITerminalInitOnlyOptions, ITerminalOptions } from '@xterm/xterm';
|
||||
import type { IWebglAddonOptions, WebglAddon } from '@xterm/addon-webgl';
|
||||
import { ITestContext, createTestContext, openTerminal } from '../../../test/playwright/TestUtils';
|
||||
|
||||
type CellSignature = number[];
|
||||
type TestTerminalConstructor = new (options?: ITerminalOptions & ITerminalInitOnlyOptions) => ITestTerminal;
|
||||
type TestWebglAddonConstructor = new (options?: IWebglAddonOptions) => ITestWebglAddon;
|
||||
|
||||
interface ITestTextureAtlasConstructor {
|
||||
maxAtlasPages: number | undefined;
|
||||
}
|
||||
|
||||
interface ITestTextureAtlas {
|
||||
constructor: ITestTextureAtlasConstructor;
|
||||
}
|
||||
|
||||
interface ITestRenderer {
|
||||
_charAtlas?: ITestTextureAtlas;
|
||||
}
|
||||
|
||||
interface ITestRenderService {
|
||||
_renderer?: {
|
||||
value?: ITestRenderer;
|
||||
};
|
||||
}
|
||||
|
||||
interface ITestTerminal extends Terminal {
|
||||
_core?: {
|
||||
_renderService?: ITestRenderService;
|
||||
};
|
||||
}
|
||||
|
||||
interface ITestWebglAddon extends WebglAddon {
|
||||
_renderer?: ITestRenderer;
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface Window { // eslint-disable-line @typescript-eslint/naming-convention
|
||||
Terminal: TestTerminalConstructor;
|
||||
WebglAddon: TestWebglAddonConstructor;
|
||||
term: ITestTerminal;
|
||||
termB?: ITestTerminal;
|
||||
addon?: ITestWebglAddon;
|
||||
addonB?: ITestWebglAddon;
|
||||
}
|
||||
}
|
||||
|
||||
const HEADER = ' HEADER_REF_0123456789';
|
||||
const HEADER_COLS = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
|
||||
const TERM_B_SELECTOR = '#terminal-container-b .xterm-screen';
|
||||
|
||||
async function loadWebglStrict(ctx: ITestContext): Promise<void> {
|
||||
await ctx.page.evaluate(() => {
|
||||
window.addon = new window.WebglAddon({ preserveDrawingBuffer: true });
|
||||
window.term.loadAddon(window.addon);
|
||||
});
|
||||
const isWebglRenderer = await ctx.page.evaluate(() => {
|
||||
return !!window.addon && window.term?._core?._renderService?._renderer?.value === window.addon._renderer;
|
||||
});
|
||||
expect(isWebglRenderer, 'WebGL renderer must be active').toBe(true);
|
||||
}
|
||||
|
||||
async function createTerminalB(ctx: ITestContext): Promise<void> {
|
||||
await ctx.page.evaluate(() => {
|
||||
window.termB?.dispose();
|
||||
document.getElementById('terminal-container-b')?.remove();
|
||||
const el = document.createElement('div');
|
||||
el.id = 'terminal-container-b';
|
||||
document.body.appendChild(el);
|
||||
window.termB = new window.Terminal({ cols: 80, rows: 24, allowProposedApi: true });
|
||||
window.termB.open(el);
|
||||
window.addonB = new window.WebglAddon({ preserveDrawingBuffer: true });
|
||||
window.termB.loadAddon(window.addonB);
|
||||
});
|
||||
}
|
||||
|
||||
async function writeToBAndWaitForRender(ctx: ITestContext, data: string): Promise<void> {
|
||||
await ctx.page.evaluate(d => new Promise<void>(resolve => {
|
||||
const termB = window.termB;
|
||||
if (!termB) {
|
||||
throw new Error('Terminal B must be created before writing to it');
|
||||
}
|
||||
const disposable = termB.onRender(() => {
|
||||
disposable.dispose();
|
||||
resolve();
|
||||
});
|
||||
termB.write(d);
|
||||
}), data);
|
||||
}
|
||||
|
||||
async function writeAndWaitForRender(ctx: ITestContext, data: string): Promise<void> {
|
||||
const renderPromise = new Promise<void>(resolve => {
|
||||
const disposable = ctx.proxy.onRender(() => {
|
||||
disposable.dispose();
|
||||
resolve();
|
||||
});
|
||||
});
|
||||
await ctx.proxy.write(data);
|
||||
await renderPromise;
|
||||
}
|
||||
|
||||
async function setMaxAtlasPages(ctx: ITestContext, maxAtlasPages: number): Promise<void> {
|
||||
const applied = await ctx.page.evaluate(max => {
|
||||
const atlas = window.term?._core?._renderService?._renderer?.value?._charAtlas;
|
||||
if (!atlas) {
|
||||
return false;
|
||||
}
|
||||
atlas.constructor.maxAtlasPages = max;
|
||||
return true;
|
||||
}, maxAtlasPages);
|
||||
expect(applied, 'should be able to set TextureAtlas.maxAtlasPages').toBe(true);
|
||||
}
|
||||
|
||||
async function resetMaxAtlasPages(ctx: ITestContext): Promise<void> {
|
||||
await ctx.page.evaluate(() => {
|
||||
const atlas = window.term?._core?._renderService?._renderer?.value?._charAtlas;
|
||||
if (atlas) {
|
||||
atlas.constructor.maxAtlasPages = undefined;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function generateColoredAsciiFlood(cells: number, offset: number = 0): string {
|
||||
const letters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
|
||||
let out = '';
|
||||
for (let i = 0; i < cells; i++) {
|
||||
const ch = letters[(offset + i) % letters.length];
|
||||
const fg = (offset + i) % 256;
|
||||
const bg = (offset + i * 7) % 256;
|
||||
out += `\x1b[38;5;${fg}m\x1b[48;5;${bg}m${ch}`;
|
||||
if ((i + 1) % 78 === 0 && i + 1 < cells) {
|
||||
out += '\x1b[0m\r\n';
|
||||
}
|
||||
}
|
||||
return out + '\x1b[0m';
|
||||
}
|
||||
|
||||
async function captureRowSignatures(ctx: ITestContext, row: number, cols: number[]): Promise<CellSignature[]> {
|
||||
const screenshotOptions = process.env.DEBUG ? { path: 'out-esbuild-test/playwright/shared-atlas-term-b.png' } : undefined;
|
||||
const buffer = await ctx.page.locator(TERM_B_SELECTOR).screenshot(screenshotOptions);
|
||||
const frame = {
|
||||
cols: 80,
|
||||
rows: 24,
|
||||
decoded: (await decodePng(new Uint8Array(buffer), { force32: true })).image
|
||||
};
|
||||
return cols.map(col => cellSignature(frame, col, row));
|
||||
}
|
||||
|
||||
function cellSignature(frame: { cols: number, rows: number, decoded: IImage32 }, col: number, row: number): CellSignature {
|
||||
const grid = 4;
|
||||
const data = frame.decoded.data;
|
||||
const imageWidth = frame.decoded.width;
|
||||
const cellWidth = frame.decoded.width / frame.cols;
|
||||
const cellHeight = frame.decoded.height / frame.rows;
|
||||
const x0 = (col - 1) * cellWidth;
|
||||
const y0 = (row - 1) * cellHeight;
|
||||
const sums = new Array<number>(grid * grid * 4).fill(0);
|
||||
const counts = new Array<number>(grid * grid).fill(0);
|
||||
const startX = Math.max(0, Math.floor(x0));
|
||||
const startY = Math.max(0, Math.floor(y0));
|
||||
const endX = Math.min(frame.decoded.width, Math.ceil(x0 + cellWidth));
|
||||
const endY = Math.min(frame.decoded.height, Math.ceil(y0 + cellHeight));
|
||||
|
||||
for (let y = startY; y < endY; y++) {
|
||||
const gy = Math.min(grid - 1, Math.max(0, Math.floor(((y - y0) / cellHeight) * grid)));
|
||||
for (let x = startX; x < endX; x++) {
|
||||
const gx = Math.min(grid - 1, Math.max(0, Math.floor(((x - x0) / cellWidth) * grid)));
|
||||
const sub = gy * grid + gx;
|
||||
const i = (y * imageWidth + x) * 4;
|
||||
sums[sub * 4] += data[i];
|
||||
sums[sub * 4 + 1] += data[i + 1];
|
||||
sums[sub * 4 + 2] += data[i + 2];
|
||||
sums[sub * 4 + 3] += data[i + 3];
|
||||
counts[sub]++;
|
||||
}
|
||||
}
|
||||
|
||||
const signature = new Array<number>(grid * grid * 4).fill(0);
|
||||
for (let sub = 0; sub < counts.length; sub++) {
|
||||
if (counts[sub] > 0) {
|
||||
signature[sub * 4] = sums[sub * 4] / counts[sub];
|
||||
signature[sub * 4 + 1] = sums[sub * 4 + 1] / counts[sub];
|
||||
signature[sub * 4 + 2] = sums[sub * 4 + 2] / counts[sub];
|
||||
signature[sub * 4 + 3] = sums[sub * 4 + 3] / counts[sub];
|
||||
}
|
||||
}
|
||||
return signature;
|
||||
}
|
||||
|
||||
function expectSignatureMatches(actual: CellSignature, reference: CellSignature, message: string): void {
|
||||
let diff = 0;
|
||||
for (let i = 0; i < actual.length; i++) {
|
||||
diff = Math.max(diff, Math.abs(actual[i] - reference[i]));
|
||||
}
|
||||
expect(diff, `${message}; max channel diff ${diff}`).toBeLessThanOrEqual(14);
|
||||
}
|
||||
|
||||
test.describe('shared-atlas garble across terminals (#6038)', () => {
|
||||
test.skip(({ browserName }) => browserName !== 'chromium');
|
||||
test.describe.configure({ timeout: 60000 });
|
||||
|
||||
test('keeps a second terminal rendering correctly after shared atlas page merges', async ({ browser }) => {
|
||||
const ctx = await createTestContext(browser);
|
||||
try {
|
||||
await openTerminal(ctx, { cols: 80, rows: 24 });
|
||||
await loadWebglStrict(ctx);
|
||||
await createTerminalB(ctx);
|
||||
|
||||
const atlasShared = await ctx.page.evaluate(() => {
|
||||
return window.term._core?._renderService?._renderer?.value?._charAtlas === window.termB?._core?._renderService?._renderer?.value?._charAtlas;
|
||||
});
|
||||
expect(atlasShared, 'terminals with equal configs should share one texture atlas').toBe(true);
|
||||
|
||||
await setMaxAtlasPages(ctx, 4);
|
||||
|
||||
await writeToBAndWaitForRender(ctx, HEADER);
|
||||
const reference = await captureRowSignatures(ctx, 1, HEADER_COLS);
|
||||
|
||||
for (let c = 0; c < 10; c++) {
|
||||
await writeAndWaitForRender(ctx, '\x1b[2;1H' + generateColoredAsciiFlood(23 * 78, c * 23 * 78));
|
||||
}
|
||||
|
||||
await writeToBAndWaitForRender(ctx, '\x1b[24;1Hx');
|
||||
const termBHeader = await ctx.page.evaluate(() => window.termB?.buffer.active.getLine(0)?.translateToString(true));
|
||||
expect(termBHeader, 'terminal B buffer must still contain the header').toBe(HEADER);
|
||||
|
||||
const after = await captureRowSignatures(ctx, 1, HEADER_COLS);
|
||||
for (let i = 0; i < HEADER_COLS.length; i++) {
|
||||
expectSignatureMatches(after[i], reference[i], `terminal B header col ${HEADER_COLS[i]} garbled by terminal A's atlas merges`);
|
||||
}
|
||||
} finally {
|
||||
await resetMaxAtlasPages(ctx).catch(() => {});
|
||||
await ctx.page.close();
|
||||
}
|
||||
});
|
||||
|
||||
test('keeps a second terminal rendering correctly after terminal A clears the shared atlas', async ({ browser }) => {
|
||||
const ctx = await createTestContext(browser);
|
||||
try {
|
||||
await openTerminal(ctx, { cols: 80, rows: 24 });
|
||||
await loadWebglStrict(ctx);
|
||||
await createTerminalB(ctx);
|
||||
|
||||
const atlasShared = await ctx.page.evaluate(() => {
|
||||
return window.term._core?._renderService?._renderer?.value?._charAtlas === window.termB?._core?._renderService?._renderer?.value?._charAtlas;
|
||||
});
|
||||
expect(atlasShared, 'terminals with equal configs should share one texture atlas').toBe(true);
|
||||
|
||||
await writeToBAndWaitForRender(ctx, HEADER);
|
||||
const reference = await captureRowSignatures(ctx, 1, HEADER_COLS);
|
||||
|
||||
// Terminal A alone clears the shared atlas, as embedders do to force a redraw (VS Code calls
|
||||
// this for every terminal on OS resume). This wipes the pages that terminal B's model still
|
||||
// holds texture coords into.
|
||||
await ctx.proxy.clearTextureAtlas();
|
||||
|
||||
// Terminal A repopulates the cleared pages with a different glyph set, so B's stale coords now
|
||||
// resolve to whatever A rasterized into those slots.
|
||||
await writeAndWaitForRender(ctx, '\x1b[2;1H' + generateColoredAsciiFlood(23 * 78));
|
||||
|
||||
// Nudge B into a render that does not touch row 1, mirroring a terminal that is idle but
|
||||
// visible. Row 1 must still be drawn from a model B rebuilt against the current atlas.
|
||||
await writeToBAndWaitForRender(ctx, '\x1b[24;1Hx');
|
||||
const termBHeader = await ctx.page.evaluate(() => window.termB?.buffer.active.getLine(0)?.translateToString(true));
|
||||
expect(termBHeader, 'terminal B buffer must still contain the header').toBe(HEADER);
|
||||
|
||||
const after = await captureRowSignatures(ctx, 1, HEADER_COLS);
|
||||
for (let i = 0; i < HEADER_COLS.length; i++) {
|
||||
expectSignatureMatches(after[i], reference[i], `terminal B header col ${HEADER_COLS[i]} garbled by terminal A's atlas clear`);
|
||||
}
|
||||
} finally {
|
||||
await ctx.page.close();
|
||||
}
|
||||
});
|
||||
});
|
||||
@@ -67,8 +67,6 @@ export class AddonImageWindow extends BaseWindow implements IControlWindow {
|
||||
dlIip.appendChild(dtIip);
|
||||
this._addDdWithButton(dlIip, 'image-demo3', 'palette (png File)');
|
||||
this._addDdWithButton(dlIip, 'image-demo4', 'dice (qoi MultipartFile)');
|
||||
this._addDdWithButton(dlIip, 'image-demo5', 'rose (webp File)');
|
||||
this._addDdWithButton(dlIip, 'image-demo6', 'kimono (avif MultipartFile)');
|
||||
container.appendChild(dlIip);
|
||||
|
||||
// Kitty demos
|
||||
@@ -175,15 +173,11 @@ export class AddonImageWindow extends BaseWindow implements IControlWindow {
|
||||
document.getElementById('image-demo2')!.addEventListener('click',
|
||||
sixelDemo('https://raw.githubusercontent.com/jerch/node-sixel/master/testfiles/test2.sixel'));
|
||||
document.getElementById('image-demo3')!.addEventListener('click',
|
||||
iipDemo('https://raw.githubusercontent.com/xtermjs/xterm.js/master/addons/addon-image/fixture/palette.png'));
|
||||
iipDemo('https://raw.githubusercontent.com/jerch/node-sixel/master/palette.png'));
|
||||
document.getElementById('image-demo4')!.addEventListener('click',
|
||||
iipDemoMulti('https://raw.githubusercontent.com/xtermjs/xterm.js/master/addons/addon-image/fixture/testimages/dice.qoi'));
|
||||
document.getElementById('image-demo5')!.addEventListener('click',
|
||||
iipDemo('https://raw.githubusercontent.com/xtermjs/xterm.js/master/addons/addon-image/fixture/testimages/1_webp_a.webp'));
|
||||
document.getElementById('image-demo6')!.addEventListener('click',
|
||||
iipDemoMulti('https://raw.githubusercontent.com/xtermjs/xterm.js/master/addons/addon-image/fixture/testimages/kimono.crop.avif'));
|
||||
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/xtermjs/xterm.js/master/addons/addon-image/fixture/palette.png'));
|
||||
kittyDemo('https://raw.githubusercontent.com/jerch/node-sixel/master/palette.png'));
|
||||
|
||||
// demo for image retrieval API
|
||||
this._terminal.element!.addEventListener('click', (ev: MouseEvent) => {
|
||||
|
||||
@@ -7,7 +7,7 @@ import { BaseWindow } from './baseWindow';
|
||||
import type { IControlWindow } from '../controlBar';
|
||||
import type { IBufferCell } from '@xterm/xterm';
|
||||
|
||||
// Underline style values from common/buffer/Constants.ts
|
||||
// Underline style values from common/primitives/buffer/Constants.ts
|
||||
const enum UnderlineStyle {
|
||||
NONE = 0,
|
||||
SINGLE = 1,
|
||||
|
||||
@@ -12,33 +12,31 @@ export class WebglWindow extends BaseWindow implements IControlWindow {
|
||||
|
||||
private _textureAtlasContainer!: HTMLElement;
|
||||
private _stressRunning = false;
|
||||
private _scrollOutput: HTMLInputElement | undefined;
|
||||
private _stressScrolling: HTMLInputElement | undefined;
|
||||
|
||||
public build(container: HTMLElement): void {
|
||||
const stressButtons = document.createElement('div');
|
||||
const stressDiv = document.createElement('div');
|
||||
const stressStart = document.createElement('button');
|
||||
stressStart.id = 'stress-start';
|
||||
stressStart.textContent = 'atlas stress start';
|
||||
stressStart.addEventListener('click', () => this._stress());
|
||||
stressButtons.appendChild(stressStart);
|
||||
stressDiv.appendChild(stressStart);
|
||||
|
||||
const stressStop = document.createElement('button');
|
||||
stressStop.id = 'stress-stop';
|
||||
stressStop.textContent = 'atlas stress stop';
|
||||
stressStop.addEventListener('click', () => this._stressRunning = false);
|
||||
stressButtons.appendChild(stressStop);
|
||||
container.appendChild(stressButtons);
|
||||
stressDiv.appendChild(stressStop);
|
||||
|
||||
const stressOptions = document.createElement('div');
|
||||
this._scrollOutput = document.createElement('input');
|
||||
this._scrollOutput.type = 'checkbox';
|
||||
this._scrollOutput.id = 'scroll-output';
|
||||
stressOptions.appendChild(this._scrollOutput);
|
||||
const scrollOutputLabel = document.createElement('label');
|
||||
scrollOutputLabel.htmlFor = 'scroll-output';
|
||||
scrollOutputLabel.textContent = 'Scroll output';
|
||||
stressOptions.appendChild(scrollOutputLabel);
|
||||
container.appendChild(stressOptions);
|
||||
this._stressScrolling = document.createElement('input');
|
||||
this._stressScrolling.type = 'checkbox';
|
||||
this._stressScrolling.id = 'stress-scrolling';
|
||||
stressDiv.appendChild(this._stressScrolling);
|
||||
const stressScrollingLabel = document.createElement('label');
|
||||
stressScrollingLabel.htmlFor = 'stress-scrolling';
|
||||
stressScrollingLabel.textContent = 'stress scrolling';
|
||||
stressDiv.appendChild(stressScrollingLabel);
|
||||
container.appendChild(stressDiv);
|
||||
|
||||
const zoomCheckbox = document.createElement('input');
|
||||
zoomCheckbox.type = 'checkbox';
|
||||
@@ -82,7 +80,7 @@ export class WebglWindow extends BaseWindow implements IControlWindow {
|
||||
return;
|
||||
}
|
||||
this._stressRunning = true;
|
||||
const scrollOutput = !!(this._scrollOutput?.checked);
|
||||
const scrolling = !!(this._stressScrolling?.checked);
|
||||
for (let r = 0; r < 256; r += 2) {
|
||||
for (let g = 0; g < 256; g += 2) {
|
||||
let s: string[] = [];
|
||||
@@ -93,7 +91,7 @@ export class WebglWindow extends BaseWindow implements IControlWindow {
|
||||
const rbg = `RGB: ${[r, g, b]}`;
|
||||
s.push(`\r\x1b[38;2;${r};${g};${b}m${rbg.padEnd(18, ' ')}${TEXT}`);
|
||||
if (s.length >= 16) {
|
||||
if (scrollOutput) {
|
||||
if (scrolling) {
|
||||
await new Promise<void>(r => this._terminal.write(s.join('\r\n') + '\r\n', r));
|
||||
} else {
|
||||
await new Promise<void>(r => this._terminal.write('\x1b[H' + s.join('\r\n'), r));
|
||||
|
||||
@@ -663,9 +663,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/aix-ppc64": {
|
||||
"version": "0.28.1",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.28.1.tgz",
|
||||
"integrity": "sha512-Svl7tq8k/08+p6CXPpRjQ1fKX+1odH/BQbb48fV6fj3CWHhsoIOoY87w1oHXm0qEpkIK3ZfVgp0hed3XBXzXMQ==",
|
||||
"version": "0.28.0",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.28.0.tgz",
|
||||
"integrity": "sha512-lhRUCeuOyJQURhTxl4WkpFTjIsbDayJHih5kZC1giwE+MhIzAb7mEsQMqMf18rHLsrb5qI1tafG20mLxEWcWlA==",
|
||||
"cpu": [
|
||||
"ppc64"
|
||||
],
|
||||
@@ -680,9 +680,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/android-arm": {
|
||||
"version": "0.28.1",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.28.1.tgz",
|
||||
"integrity": "sha512-0k2F129Xdio1TdJfzJ8sy1Q47vUD2NnwdhiAf7drUN1EBTfPf4hsFCtmMgu/6m8JSzsBrlmVjudMBQqOfG8usQ==",
|
||||
"version": "0.28.0",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.28.0.tgz",
|
||||
"integrity": "sha512-wqh0ByljabXLKHeWXYLqoJ5jKC4XBaw6Hk08OfMrCRd2nP2ZQ5eleDZC41XHyCNgktBGYMbqnrJKq/K/lzPMSQ==",
|
||||
"cpu": [
|
||||
"arm"
|
||||
],
|
||||
@@ -697,9 +697,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/android-arm64": {
|
||||
"version": "0.28.1",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.28.1.tgz",
|
||||
"integrity": "sha512-34EGEbCIAgosYz6goLcopX6Mo7NyGv9tfwEM2/7Ce2VcVRk568iSvniGWcUXIy7wEDR1wzolcxcriFVrWYcwBg==",
|
||||
"version": "0.28.0",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.28.0.tgz",
|
||||
"integrity": "sha512-+WzIXQOSaGs33tLEgYPYe/yQHf0WTU0X42Jca3y8NWMbUVhp7rUnw+vAsRC/QiDrdD31IszMrZy+qwPOPjd+rw==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
@@ -714,9 +714,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/android-x64": {
|
||||
"version": "0.28.1",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.28.1.tgz",
|
||||
"integrity": "sha512-dbwY7ltSMDWsRatcRpCnES4F+im88OCUgGZjy52shC7GqHRE/cYlxNbB4Z4UpJswpcc4Qxd2oE/ufM0p61IKng==",
|
||||
"version": "0.28.0",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.28.0.tgz",
|
||||
"integrity": "sha512-+VJggoaKhk2VNNqVL7f6S189UzShHC/mR9EE8rDdSkdpN0KflSwWY/gWjDrNxxisg8Fp1ZCD9jLMo4m0OUfeUA==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
@@ -731,9 +731,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/darwin-arm64": {
|
||||
"version": "0.28.1",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.28.1.tgz",
|
||||
"integrity": "sha512-TZbWkQY7kvTAXbXUT7uVACR5cMHsDiSz9z7ZKAX/RTq/WJEk3QyRr0wZpNhBDX+/0CtdqUIJlOiodQcta6tY3Q==",
|
||||
"version": "0.28.0",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.28.0.tgz",
|
||||
"integrity": "sha512-0T+A9WZm+bZ84nZBtk1ckYsOvyA3x7e2Acj1KdVfV4/2tdG4fzUp91YHx+GArWLtwqp77pBXVCPn2We7Letr0Q==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
@@ -748,9 +748,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/darwin-x64": {
|
||||
"version": "0.28.1",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.28.1.tgz",
|
||||
"integrity": "sha512-zfdzgK9ACBNZLI/CyHTOx81SyNbM6YXn7rxSgX97VjyiPl9W1i4Ka4fgKECEoFCKGpvBj5qArWIGgQjOwkgskQ==",
|
||||
"version": "0.28.0",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.28.0.tgz",
|
||||
"integrity": "sha512-fyzLm/DLDl/84OCfp2f/XQ4flmORsjU7VKt8HLjvIXChJoFFOIL6pLJPH4Yhd1n1gGFF9mPwtlN5Wf82DZs+LQ==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
@@ -765,9 +765,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/freebsd-arm64": {
|
||||
"version": "0.28.1",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.28.1.tgz",
|
||||
"integrity": "sha512-wG2EA8ENdEI0qhkSZMjfqrdY+ziCYCPMmtZjjIwOmXFjmyzEHn+UUxk5of+SYsjtfs3VpnlC7QLzSI5hY/rOAw==",
|
||||
"version": "0.28.0",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.28.0.tgz",
|
||||
"integrity": "sha512-l9GeW5UZBT9k9brBYI+0WDffcRxgHQD8ShN2Ur4xWq/NFzUKm3k5lsH4PdaRgb2w7mI9u61nr2gI2mLI27Nh3Q==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
@@ -782,9 +782,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/freebsd-x64": {
|
||||
"version": "0.28.1",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.28.1.tgz",
|
||||
"integrity": "sha512-i7dZ9vQgnvSCzi/rYCXNgtF/U+eKZNJBzu3eTQbRgHnM7tNSizLOkRFAl3qzVc/Op/u5YkHHa4pf/3DOYHthLQ==",
|
||||
"version": "0.28.0",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.28.0.tgz",
|
||||
"integrity": "sha512-BXoQai/A0wPO6Es3yFJ7APCiKGc1tdAEOgeTNy3SsB491S3aHn4S4r3e976eUnPdU+NbdtmBuLncYir2tMU9Nw==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
@@ -799,9 +799,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/linux-arm": {
|
||||
"version": "0.28.1",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.28.1.tgz",
|
||||
"integrity": "sha512-qVXBOHQS+d5Y722GwJzJUtOLlX7km3CraOaGormF1pDtPd2C/l1SHRPgjLunLGe51Sh5YYWKMFDyV4SxgMQYTQ==",
|
||||
"version": "0.28.0",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.28.0.tgz",
|
||||
"integrity": "sha512-CjaaREJagqJp7iTaNQjjidaNbCKYcd4IDkzbwwxtSvjI7NZm79qiHc8HqciMddQ6CKvJT6aBd8lO9kN/ZudLlw==",
|
||||
"cpu": [
|
||||
"arm"
|
||||
],
|
||||
@@ -816,9 +816,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/linux-arm64": {
|
||||
"version": "0.28.1",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.28.1.tgz",
|
||||
"integrity": "sha512-yHs+0uc8+nvEAfAfxrWQKK5peSNzBc4PegcMO0EJ2hT71uA7vB8Ihg2e77R2P7SG5uYjPbHlLLmve4LLLRCf0g==",
|
||||
"version": "0.28.0",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.28.0.tgz",
|
||||
"integrity": "sha512-RVyzfb3FWsGA55n6WY0MEIEPURL1FcbhFE6BffZEMEekfCzCIMtB5yyDcFnVbTnwk+CLAgTujmV/Lgvih56W+A==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
@@ -833,9 +833,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/linux-ia32": {
|
||||
"version": "0.28.1",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.28.1.tgz",
|
||||
"integrity": "sha512-d1z4ZuP0ajrfz/FhGT4vv278rX8KnPPJx8i5+AtK7TYbx9Le9F1hyzurZpkEyjkGa9dUGhQow4C1NmeGvqxN2w==",
|
||||
"version": "0.28.0",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.28.0.tgz",
|
||||
"integrity": "sha512-KBnSTt1kxl9x70q+ydterVdl+Cn0H18ngRMRCEQfrbqdUuntQQ0LoMZv47uB97NljZFzY6HcfqEZ2SAyIUTQBQ==",
|
||||
"cpu": [
|
||||
"ia32"
|
||||
],
|
||||
@@ -850,9 +850,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/linux-loong64": {
|
||||
"version": "0.28.1",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.28.1.tgz",
|
||||
"integrity": "sha512-M5sRjUVZrkm1OAPR3dlOYzNmN+loZKGVi1VUQGrwuqLcbR6qeAz+famMhjASeH3YVKvZz+zT1jlh/keC3Rj/lg==",
|
||||
"version": "0.28.0",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.28.0.tgz",
|
||||
"integrity": "sha512-zpSlUce1mnxzgBADvxKXX5sl8aYQHo2ezvMNI8I0lbblJtp8V4odlm3Yzlj7gPyt3T8ReksE6bK+pT3WD+aJRg==",
|
||||
"cpu": [
|
||||
"loong64"
|
||||
],
|
||||
@@ -867,9 +867,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/linux-mips64el": {
|
||||
"version": "0.28.1",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.28.1.tgz",
|
||||
"integrity": "sha512-mRObBZeHh2OxcBFPWE/FjylkRgZdYuiTR3vaTozquCGOH14iP9oN4x4Ge81CoIDYQrXmIxpFumJBu5MtZpnQJQ==",
|
||||
"version": "0.28.0",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.28.0.tgz",
|
||||
"integrity": "sha512-2jIfP6mmjkdmeTlsX/9vmdmhBmKADrWqN7zcdtHIeNSCH1SqIoNI63cYsjQR8J+wGa4Y5izRcSHSm8K3QWmk3w==",
|
||||
"cpu": [
|
||||
"mips64el"
|
||||
],
|
||||
@@ -884,9 +884,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/linux-ppc64": {
|
||||
"version": "0.28.1",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.28.1.tgz",
|
||||
"integrity": "sha512-slScBsMAb3GFDcdrCgLwZtPYRoH2H/youv10QiZyRjmsP48fznoveWytSgCI/R0ZcUgpc0ZhIUEx6LHts8yrfQ==",
|
||||
"version": "0.28.0",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.28.0.tgz",
|
||||
"integrity": "sha512-bc0FE9wWeC0WBm49IQMPSPILRocGTQt3j5KPCA8os6VprfuJ7KD+5PzESSrJ6GmPIPJK965ZJHTUlSA6GNYEhg==",
|
||||
"cpu": [
|
||||
"ppc64"
|
||||
],
|
||||
@@ -901,9 +901,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/linux-riscv64": {
|
||||
"version": "0.28.1",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.28.1.tgz",
|
||||
"integrity": "sha512-kw0owk1o0GFETUJyW0jc0G4Yzs0BHZn0JDZ8JRT088vjJYX777BAs1fDGxAC+q831qOs2DTC96mNsG2opdfyyQ==",
|
||||
"version": "0.28.0",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.28.0.tgz",
|
||||
"integrity": "sha512-SQPZOwoTTT/HXFXQJG/vBX8sOFagGqvZyXcgLA3NhIqcBv1BJU1d46c0rGcrij2B56Z2rNiSLaZOYW5cUk7yLQ==",
|
||||
"cpu": [
|
||||
"riscv64"
|
||||
],
|
||||
@@ -918,9 +918,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/linux-s390x": {
|
||||
"version": "0.28.1",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.28.1.tgz",
|
||||
"integrity": "sha512-/lAIjX8aYFRByhh6L5rYtPEDRqa9de/4V/juOXcta5frjvzXO4/sqEtyytse0g3zZFuWu5cDN0MkLz2qRDD2Ag==",
|
||||
"version": "0.28.0",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.28.0.tgz",
|
||||
"integrity": "sha512-SCfR0HN8CEEjnYnySJTd2cw0k9OHB/YFzt5zgJEwa+wL/T/raGWYMBqwDNAC6dqFKmJYZoQBRfHjgwLHGSrn3Q==",
|
||||
"cpu": [
|
||||
"s390x"
|
||||
],
|
||||
@@ -935,9 +935,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/linux-x64": {
|
||||
"version": "0.28.1",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.28.1.tgz",
|
||||
"integrity": "sha512-u/anNYF2mmVOEDwLtnQ1wOr3EZ9sTNGLWrsYGYwHWzGA3Si84IOkHXlbWTD1NB+9/1lcnweYKO54uhxZydNzfA==",
|
||||
"version": "0.28.0",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.28.0.tgz",
|
||||
"integrity": "sha512-us0dSb9iFxIi8srnpl931Nvs65it/Jd2a2K3qs7fz2WfGPHqzfzZTfec7oxZJRNPXPnNYZtanmRc4AL/JwVzHQ==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
@@ -952,9 +952,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/netbsd-arm64": {
|
||||
"version": "0.28.1",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.28.1.tgz",
|
||||
"integrity": "sha512-oks0DYbLwWMmaakTsCb+zL4E+aHRVLom9IJZOAthMQEPiQmydXHkziYEsGYRx0uNV/IjEKGAV941JzH02pflqw==",
|
||||
"version": "0.28.0",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.28.0.tgz",
|
||||
"integrity": "sha512-CR/RYotgtCKwtftMwJlUU7xCVNg3lMYZ0RzTmAHSfLCXw3NtZtNpswLEj/Kkf6kEL3Gw+BpOekRX0BYCtklhUw==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
@@ -969,9 +969,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/netbsd-x64": {
|
||||
"version": "0.28.1",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.28.1.tgz",
|
||||
"integrity": "sha512-aeL6lAnN89Hz43Mlh1G8ARasbuoYvSITDEx0tHh5b7jJnHcssqgjy9Yx430GDpmCa6OyrKoS0aNRjKundRizGg==",
|
||||
"version": "0.28.0",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.28.0.tgz",
|
||||
"integrity": "sha512-nU1yhmYutL+fQ71Kxnhg8uEOdC0pwEW9entHykTgEbna2pw2dkbFSMeqjjyHZoCmt8SBkOSvV+yNmm94aUrrqw==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
@@ -986,9 +986,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/openbsd-arm64": {
|
||||
"version": "0.28.1",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.28.1.tgz",
|
||||
"integrity": "sha512-MEFJe5C3R8pwXdZ5Y21oo6m7ePiS0d9pWucn99O/wvyJZChoIQKrQDxKrGeW8F5+T0okTHesAmDeiHDTIq0V/Q==",
|
||||
"version": "0.28.0",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.28.0.tgz",
|
||||
"integrity": "sha512-cXb5vApOsRsxsEl4mcZ1XY3D4DzcoMxR/nnc4IyqYs0rTI8ZKmW6kyyg+11Z8yvgMfAEldKzP7AdP64HnSC/6g==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
@@ -1003,9 +1003,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/openbsd-x64": {
|
||||
"version": "0.28.1",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.28.1.tgz",
|
||||
"integrity": "sha512-i/ZLIOafE0Z8cI/XANJAixoJL/uRAoS2xOA3rb0xN+KK0K177cMAsQYkzHtBrtMXAKuAc7HGgcWiZ/sRC1Nxgw==",
|
||||
"version": "0.28.0",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.28.0.tgz",
|
||||
"integrity": "sha512-8wZM2qqtv9UP3mzy7HiGYNH/zjTA355mpeuA+859TyR+e+Tc08IHYpLJuMsfpDJwoLo1ikIJI8jC3GFjnRClzA==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
@@ -1020,9 +1020,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/openharmony-arm64": {
|
||||
"version": "0.28.1",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.28.1.tgz",
|
||||
"integrity": "sha512-ge+Z7EXFNt2BO1oAMsVpiQ8EwndV9i1xXerAeTIK7AtPs3bKFXQM7nlRxDSIUIMeueR1CNXxqztLzdNeReKBJg==",
|
||||
"version": "0.28.0",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.28.0.tgz",
|
||||
"integrity": "sha512-FLGfyizszcef5C3YtoyQDACyg95+dndv79i2EekILBofh5wpCa1KuBqOWKrEHZg3zrL3t5ouE5jgr94vA+Wb2w==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
@@ -1037,9 +1037,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/sunos-x64": {
|
||||
"version": "0.28.1",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.28.1.tgz",
|
||||
"integrity": "sha512-BEjgtECkL3vY+SaSQ6nzVfiALUeFxpawyp8Jmf5PtYhf1Ug40N1h/hxlhts+f1FvSvarEigdxS3BlSMI2PJLcQ==",
|
||||
"version": "0.28.0",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.28.0.tgz",
|
||||
"integrity": "sha512-1ZgjUoEdHZZl/YlV76TSCz9Hqj9h9YmMGAgAPYd+q4SicWNX3G5GCyx9uhQWSLcbvPW8Ni7lj4gDa1T40akdlw==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
@@ -1054,9 +1054,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/win32-arm64": {
|
||||
"version": "0.28.1",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.28.1.tgz",
|
||||
"integrity": "sha512-lCv9eK/H6ZJWbE7bh2nw54CZ9M2nupBxJcTsdk/QQnWkdSjKGuxmmH8/GWrlT1eMmZfn4dGcCjRte397WqfQXA==",
|
||||
"version": "0.28.0",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.28.0.tgz",
|
||||
"integrity": "sha512-Q9StnDmQ/enxnpxCCLSg0oo4+34B9TdXpuyPeTedN/6+iXBJ4J+zwfQI28u/Jl40nOYAxGoNi7mFP40RUtkmUA==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
@@ -1071,9 +1071,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/win32-ia32": {
|
||||
"version": "0.28.1",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.28.1.tgz",
|
||||
"integrity": "sha512-zvb/mB2bSCoJOpoCBgYKKpX6YM6mJBlBUVUtVj41DlZJVEB6/0CKlRYxP5wWl1C1ILiCoAU5wZZ4q1P3qeS6Eg==",
|
||||
"version": "0.28.0",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.28.0.tgz",
|
||||
"integrity": "sha512-zF3ag/gfiCe6U2iczcRzSYJKH1DCI+ByzSENHlM2FcDbEeo5Zd2C86Aq0tKUYAJJ1obRP84ymxIAksZUcdztHA==",
|
||||
"cpu": [
|
||||
"ia32"
|
||||
],
|
||||
@@ -1088,9 +1088,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/win32-x64": {
|
||||
"version": "0.28.1",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.28.1.tgz",
|
||||
"integrity": "sha512-bm4Mowrv+GXMlpWX++EcXw/iLyd1o3+bJkC2DkWXYVvgZCqD/bSj9ctZeAMC3cIxgjRVR2Dufaiu4YPxr5gW1A==",
|
||||
"version": "0.28.0",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.28.0.tgz",
|
||||
"integrity": "sha512-pEl1bO9mfAmIC+tW5btTmrKaujg3zGtUmWNdCw/xs70FBjwAL3o9OEKNHvNmnyylD6ubxUERiEhdsL0xBQ9efw==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
@@ -3061,16 +3061,16 @@
|
||||
}
|
||||
},
|
||||
"node_modules/brace-expansion": {
|
||||
"version": "5.0.9",
|
||||
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.9.tgz",
|
||||
"integrity": "sha512-ScQ4IuvIEF1TMlP7Zt+vjJ//9zlPb2SDcxWxM3bk8s6t6GGdJ7KO1dCcTidOPJKePW30LE/2cT7wCyPho9/Wxg==",
|
||||
"version": "5.0.6",
|
||||
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.6.tgz",
|
||||
"integrity": "sha512-kLpxurY4Z4r9sgMsyG0Z9uzsBlgiU/EFKhj/h91/8yHu0edo7XuixOIH3VcJ8kkxs6/jPzoI6U9Vj3WqbMQ94g==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"balanced-match": "^4.0.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": "20 || >=22"
|
||||
"node": "18 || 20 || >=22"
|
||||
}
|
||||
},
|
||||
"node_modules/braces": {
|
||||
@@ -3519,15 +3519,15 @@
|
||||
}
|
||||
},
|
||||
"node_modules/concurrently": {
|
||||
"version": "9.2.4",
|
||||
"resolved": "https://registry.npmjs.org/concurrently/-/concurrently-9.2.4.tgz",
|
||||
"integrity": "sha512-TZ0CEhyzvFjgtAvHTusDMgj7wNdihCh7LLLrzdUOXIhdlnL2JBBGA9eJxR24rtqgmdjh3OA3hrN1rCHj6HM8qA==",
|
||||
"version": "9.2.1",
|
||||
"resolved": "https://registry.npmjs.org/concurrently/-/concurrently-9.2.1.tgz",
|
||||
"integrity": "sha512-fsfrO0MxV64Znoy8/l1vVIjjHa29SZyyqPgQBwhiDcaW8wJc2W3XWVOGx4M3oJBnv/zdUZIIp1gDeS98GzP8Ng==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"chalk": "4.1.2",
|
||||
"rxjs": "7.8.2",
|
||||
"shell-quote": "1.9.0",
|
||||
"shell-quote": "1.8.3",
|
||||
"supports-color": "8.1.1",
|
||||
"tree-kill": "1.2.2",
|
||||
"yargs": "17.7.2"
|
||||
@@ -3869,9 +3869,9 @@
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/esbuild": {
|
||||
"version": "0.28.1",
|
||||
"resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.28.1.tgz",
|
||||
"integrity": "sha512-HrJrvZv5ayxBzPfwphOoNzkzOIIlifzk0KJrGK2c8R4+LKpMtpYLQeUdjnwjWv/LZlkH2laZk+4w78pi99D4Vw==",
|
||||
"version": "0.28.0",
|
||||
"resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.28.0.tgz",
|
||||
"integrity": "sha512-sNR9MHpXSUV/XB4zmsFKN+QgVG82Cc7+/aaxJ8Adi8hyOac+EXptIp45QBPaVyX3N70664wRbTcLTOemCAnyqw==",
|
||||
"dev": true,
|
||||
"hasInstallScript": true,
|
||||
"license": "MIT",
|
||||
@@ -3882,32 +3882,32 @@
|
||||
"node": ">=18"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"@esbuild/aix-ppc64": "0.28.1",
|
||||
"@esbuild/android-arm": "0.28.1",
|
||||
"@esbuild/android-arm64": "0.28.1",
|
||||
"@esbuild/android-x64": "0.28.1",
|
||||
"@esbuild/darwin-arm64": "0.28.1",
|
||||
"@esbuild/darwin-x64": "0.28.1",
|
||||
"@esbuild/freebsd-arm64": "0.28.1",
|
||||
"@esbuild/freebsd-x64": "0.28.1",
|
||||
"@esbuild/linux-arm": "0.28.1",
|
||||
"@esbuild/linux-arm64": "0.28.1",
|
||||
"@esbuild/linux-ia32": "0.28.1",
|
||||
"@esbuild/linux-loong64": "0.28.1",
|
||||
"@esbuild/linux-mips64el": "0.28.1",
|
||||
"@esbuild/linux-ppc64": "0.28.1",
|
||||
"@esbuild/linux-riscv64": "0.28.1",
|
||||
"@esbuild/linux-s390x": "0.28.1",
|
||||
"@esbuild/linux-x64": "0.28.1",
|
||||
"@esbuild/netbsd-arm64": "0.28.1",
|
||||
"@esbuild/netbsd-x64": "0.28.1",
|
||||
"@esbuild/openbsd-arm64": "0.28.1",
|
||||
"@esbuild/openbsd-x64": "0.28.1",
|
||||
"@esbuild/openharmony-arm64": "0.28.1",
|
||||
"@esbuild/sunos-x64": "0.28.1",
|
||||
"@esbuild/win32-arm64": "0.28.1",
|
||||
"@esbuild/win32-ia32": "0.28.1",
|
||||
"@esbuild/win32-x64": "0.28.1"
|
||||
"@esbuild/aix-ppc64": "0.28.0",
|
||||
"@esbuild/android-arm": "0.28.0",
|
||||
"@esbuild/android-arm64": "0.28.0",
|
||||
"@esbuild/android-x64": "0.28.0",
|
||||
"@esbuild/darwin-arm64": "0.28.0",
|
||||
"@esbuild/darwin-x64": "0.28.0",
|
||||
"@esbuild/freebsd-arm64": "0.28.0",
|
||||
"@esbuild/freebsd-x64": "0.28.0",
|
||||
"@esbuild/linux-arm": "0.28.0",
|
||||
"@esbuild/linux-arm64": "0.28.0",
|
||||
"@esbuild/linux-ia32": "0.28.0",
|
||||
"@esbuild/linux-loong64": "0.28.0",
|
||||
"@esbuild/linux-mips64el": "0.28.0",
|
||||
"@esbuild/linux-ppc64": "0.28.0",
|
||||
"@esbuild/linux-riscv64": "0.28.0",
|
||||
"@esbuild/linux-s390x": "0.28.0",
|
||||
"@esbuild/linux-x64": "0.28.0",
|
||||
"@esbuild/netbsd-arm64": "0.28.0",
|
||||
"@esbuild/netbsd-x64": "0.28.0",
|
||||
"@esbuild/openbsd-arm64": "0.28.0",
|
||||
"@esbuild/openbsd-x64": "0.28.0",
|
||||
"@esbuild/openharmony-arm64": "0.28.0",
|
||||
"@esbuild/sunos-x64": "0.28.0",
|
||||
"@esbuild/win32-arm64": "0.28.0",
|
||||
"@esbuild/win32-ia32": "0.28.0",
|
||||
"@esbuild/win32-x64": "0.28.0"
|
||||
}
|
||||
},
|
||||
"node_modules/escalade": {
|
||||
@@ -4308,9 +4308,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/express-ws/node_modules/ws": {
|
||||
"version": "7.5.13",
|
||||
"resolved": "https://registry.npmjs.org/ws/-/ws-7.5.13.tgz",
|
||||
"integrity": "sha512-rsKI6xDBFVf4r/x8XyChGK04QR/XHroxs/jUcoWvtEZM8TPU/X/uIY9B1CsSzYws9ZJb/6bbBu7dPhFW00CAoA==",
|
||||
"version": "7.5.10",
|
||||
"resolved": "https://registry.npmjs.org/ws/-/ws-7.5.10.tgz",
|
||||
"integrity": "sha512-+dbF1tHwZpXcbOJdVOkzLDxZP1ailvSxM6ZweXTegylPny803bFhA+vqBYw4s31NSAk4S2Qz+AKXK9a4wkdjcQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
@@ -4351,9 +4351,9 @@
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/fast-uri": {
|
||||
"version": "3.1.5",
|
||||
"resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.5.tgz",
|
||||
"integrity": "sha512-gHwA1O9LDIcKunMKhObS/HimwtehO1nPUECKAu5TpKgaO19fcWEl4bliWe1jWxVFvIXztJjjQ4L8XQ1EU9f7Jw==",
|
||||
"version": "3.1.2",
|
||||
"resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.2.tgz",
|
||||
"integrity": "sha512-rVjf7ArG3LTk+FS6Yw81V1DLuZl1bRbNrev6Tmd/9RaroeeRRJhAt7jg/6YFxbvAQXUCavSoZhPPj6oOx+5KjQ==",
|
||||
"dev": true,
|
||||
"funding": [
|
||||
{
|
||||
@@ -4749,9 +4749,9 @@
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/glob/node_modules/brace-expansion": {
|
||||
"version": "2.1.4",
|
||||
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.1.4.tgz",
|
||||
"integrity": "sha512-hGfVzPxthbf3+2yjg/RBs60cB0FhqBS/zvdV/4wn4/BmN0bNMMHPc4V/BbFieqf1TKAGGAHnY4eSjajCl0f2Xg==",
|
||||
"version": "2.1.0",
|
||||
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.1.0.tgz",
|
||||
"integrity": "sha512-TN1kCZAgdgweJhWWpgKYrQaMNHcDULHkWwQIspdtjV4Y5aurRdZpjAqn6yX3FPqTA9ngHCc4hJxMAMgGfve85w==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
@@ -5795,9 +5795,9 @@
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/mocha/node_modules/brace-expansion": {
|
||||
"version": "2.1.4",
|
||||
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.1.4.tgz",
|
||||
"integrity": "sha512-hGfVzPxthbf3+2yjg/RBs60cB0FhqBS/zvdV/4wn4/BmN0bNMMHPc4V/BbFieqf1TKAGGAHnY4eSjajCl0f2Xg==",
|
||||
"version": "2.1.0",
|
||||
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.1.0.tgz",
|
||||
"integrity": "sha512-TN1kCZAgdgweJhWWpgKYrQaMNHcDULHkWwQIspdtjV4Y5aurRdZpjAqn6yX3FPqTA9ngHCc4hJxMAMgGfve85w==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
@@ -7127,9 +7127,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/shell-quote": {
|
||||
"version": "1.9.0",
|
||||
"resolved": "https://registry.npmjs.org/shell-quote/-/shell-quote-1.9.0.tgz",
|
||||
"integrity": "sha512-Iov+JwFv/2HcTpcwNMKd8+IWNb8tboQJNQTkAY/LLVK7gGH9jy+LGkVqPxfekHl+yMmiqXszdGWXgkfml7hjqA==",
|
||||
"version": "1.8.3",
|
||||
"resolved": "https://registry.npmjs.org/shell-quote/-/shell-quote-1.8.3.tgz",
|
||||
"integrity": "sha512-ObmnIF4hXNg1BqhnHmgbDETF8dLPCggZWBjkQfhZpbszZnYur5DUljTcCHii5LC3J5E0yeO/1LIMyH+UvHQgyw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
@@ -7975,9 +7975,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/undici": {
|
||||
"version": "7.29.0",
|
||||
"resolved": "https://registry.npmjs.org/undici/-/undici-7.29.0.tgz",
|
||||
"integrity": "sha512-IDxfleLmmbSskfWSUATiN1nfn2rDuvnMOqb5CWR92iIfojA0Ud+ulOAAEQ57LPr9rWmsreUyf5lwyao+7GNNVw==",
|
||||
"version": "7.25.0",
|
||||
"resolved": "https://registry.npmjs.org/undici/-/undici-7.25.0.tgz",
|
||||
"integrity": "sha512-xXnp4kTyor2Zq+J1FfPI6Eq3ew5h6Vl0F/8d9XU5zZQf1tX9s2Su1/3PiMmUANFULpmksxkClamIZcaUqryHsQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
@@ -8452,9 +8452,9 @@
|
||||
"license": "ISC"
|
||||
},
|
||||
"node_modules/ws": {
|
||||
"version": "8.21.3",
|
||||
"resolved": "https://registry.npmjs.org/ws/-/ws-8.21.3.tgz",
|
||||
"integrity": "sha512-201TZ/kPWxoPr/OKWjquZR1SWKXcvxdH+e1xrx89b3YbmzLMFCLfnaG1HFIgWzJOEWZ7MvpK++odZufgYR50Rw==",
|
||||
"version": "8.20.1",
|
||||
"resolved": "https://registry.npmjs.org/ws/-/ws-8.20.1.tgz",
|
||||
"integrity": "sha512-It4dO0K5v//JtTXuPkfEOaI3uUN87iYPnqo/ZzqCoG3g8uhA66QUMs/SrM0YK7/NAu+r4LMh/9dq2A7k+rHs+w==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
|
||||
@@ -6,9 +6,9 @@
|
||||
import * as Strings from './LocalizableStrings';
|
||||
import { ITerminal, IRenderDebouncer } from './Types';
|
||||
import { TimeBasedDebouncer } from './TimeBasedDebouncer';
|
||||
import { Disposable, toDisposable } from '../common/Lifecycle';
|
||||
import { Disposable, toDisposable } from '../common/primitives/Lifecycle';
|
||||
import { ICoreBrowserService, IRenderService } from './services/Services';
|
||||
import { IBuffer } from '../common/buffer/Types';
|
||||
import { IBuffer } from '../common/primitives/buffer/Types';
|
||||
import { IInstantiationService } from '../common/services/Services';
|
||||
import { addDisposableListener } from './Dom';
|
||||
|
||||
|
||||
@@ -4,8 +4,8 @@
|
||||
*/
|
||||
|
||||
import { IColorContrastCache } from './Types';
|
||||
import { IColor } from '../common/Types';
|
||||
import { TwoKeyMap } from '../common/MultiKeyMap';
|
||||
import { IColor } from '../common/primitives/Types';
|
||||
import { TwoKeyMap } from '../common/primitives/MultiKeyMap';
|
||||
|
||||
export class ColorContrastCache implements IColorContrastCache {
|
||||
private _color: TwoKeyMap</* bg */number, /* fg */number, IColor | null> = new TwoKeyMap();
|
||||
|
||||
@@ -43,22 +43,23 @@ import { SelectionService } from './services/SelectionService';
|
||||
import { ICharSizeService, ICharacterJoinerService, ICoreBrowserService, IKeyboardService, ILinkProviderService, IMouseCoordsService, IMouseService, IRenderService, ISelectionService, IThemeService } from './services/Services';
|
||||
import { ThemeService } from './services/ThemeService';
|
||||
import { KeyboardService } from './services/KeyboardService';
|
||||
import { channels, color, rgb } from '../common/Color';
|
||||
import { channels, color, rgb } from '../common/primitives/Color';
|
||||
import { CoreTerminal } from '../common/CoreTerminal';
|
||||
import * as Browser from '../common/Platform';
|
||||
import { ColorRequestType, IColorEvent, ITerminalOptions, KeyboardResultType, SpecialColorIndex } from '../common/Types';
|
||||
import { DEFAULT_ATTR_DATA } from '../common/buffer/BufferLine';
|
||||
import { IBuffer } from '../common/buffer/Types';
|
||||
import { C0, C1ESCAPED } from '../common/data/EscapeSequences';
|
||||
import { toRgbString } from '../common/input/XParseColor';
|
||||
import * as Browser from '../common/primitives/Platform';
|
||||
import { ColorRequestType, IColorEvent, KeyboardResultType, SpecialColorIndex } from '../common/primitives/Types';
|
||||
import { ITerminalOptions } from '../common/Types';
|
||||
import { DEFAULT_ATTR_DATA } from '../common/primitives/buffer/BufferLine';
|
||||
import { IBuffer } from '../common/primitives/buffer/Types';
|
||||
import { C0, C1ESCAPED } from '../common/primitives/data/EscapeSequences';
|
||||
import { toRgbString } from '../common/primitives/input/XParseColor';
|
||||
import { DecorationService } from '../common/services/DecorationService';
|
||||
import { IDecorationService } from '../common/services/Services';
|
||||
import { WindowsOptionsReportType } from '../common/InputHandler';
|
||||
import { AccessibilityManager } from './AccessibilityManager';
|
||||
import { Linkifier } from './Linkifier';
|
||||
import { Emitter, EventUtils, type IEvent } from '../common/Event';
|
||||
import { Emitter, EventUtils, type IEvent } from '../common/primitives/Event';
|
||||
import { addDisposableListener } from './Dom';
|
||||
import { MutableDisposable, toDisposable } from '../common/Lifecycle';
|
||||
import { MutableDisposable, toDisposable } from '../common/primitives/Lifecycle';
|
||||
|
||||
export class CoreBrowserTerminal extends CoreTerminal implements ITerminal {
|
||||
public textarea: HTMLTextAreaElement | undefined;
|
||||
@@ -492,7 +493,6 @@ export class CoreBrowserTerminal extends CoreTerminal implements ITerminal {
|
||||
// https://issuetracker.google.com/issues/260170397
|
||||
this.textarea.setAttribute('aria-multiline', 'false');
|
||||
}
|
||||
this.textarea.setAttribute('autocomplete', 'off');
|
||||
this.textarea.setAttribute('autocorrect', 'off');
|
||||
this.textarea.setAttribute('autocapitalize', 'off');
|
||||
this.textarea.setAttribute('spellcheck', 'false');
|
||||
|
||||
@@ -5,8 +5,8 @@
|
||||
* Minimal DOM helpers for xterm.js browser code.
|
||||
*/
|
||||
|
||||
import { IntervalTimer } from '../common/Async';
|
||||
import { IDisposable } from '../common/Lifecycle';
|
||||
import { IntervalTimer } from '../common/primitives/Async';
|
||||
import { IDisposable } from '../common/primitives/Lifecycle';
|
||||
|
||||
export function getWindow(e: Node | UIEvent | undefined | null): Window {
|
||||
const candidateNode = e as Node | undefined | null;
|
||||
|
||||
@@ -4,11 +4,11 @@
|
||||
*/
|
||||
|
||||
import { IBufferCellPosition, ILink, ILinkDecorations, ILinkWithState, ILinkifier2, ILinkifierEvent } from './Types';
|
||||
import { Disposable, dispose, toDisposable } from '../common/Lifecycle';
|
||||
import { IDisposable } from '../common/Types';
|
||||
import { Disposable, dispose, toDisposable } from '../common/primitives/Lifecycle';
|
||||
import { IDisposable } from '../common/primitives/Types';
|
||||
import { IBufferService } from '../common/services/Services';
|
||||
import { ILinkProviderService, IMouseCoordsService, IRenderService } from './services/Services';
|
||||
import { Emitter } from '../common/Event';
|
||||
import { Emitter } from '../common/primitives/Event';
|
||||
import { addDisposableListener } from './Dom';
|
||||
|
||||
export class Linkifier extends Disposable implements ILinkifier2 {
|
||||
|
||||
@@ -8,8 +8,8 @@ import { OscLinkProvider } from './OscLinkProvider';
|
||||
import { ILink } from './Types';
|
||||
import { createCellData, MockBufferService, MockOptionsService } from '../common/TestUtils.test';
|
||||
import { IBufferService, IOscLinkService } from '../common/services/Services';
|
||||
import { IOscLinkData } from '../common/Types';
|
||||
import { IBufferLine } from '../common/buffer/Types';
|
||||
import { IOscLinkData } from '../common/primitives/Types';
|
||||
import { IBufferLine } from '../common/primitives/buffer/Types';
|
||||
|
||||
class TestOscLinkService implements IOscLinkService {
|
||||
public serviceBrand: any;
|
||||
|
||||
@@ -5,8 +5,8 @@
|
||||
|
||||
import { IBufferRange, ILink } from './Types';
|
||||
import { ILinkProvider } from './services/Services';
|
||||
import { CellData } from '../common/buffer/CellData';
|
||||
import { IBufferLine } from '../common/buffer/Types';
|
||||
import { CellData } from '../common/primitives/buffer/CellData';
|
||||
import { IBufferLine } from '../common/primitives/buffer/Types';
|
||||
import { IBufferService, IOptionsService, IOscLinkService } from '../common/services/Services';
|
||||
|
||||
export class OscLinkProvider implements ILinkProvider {
|
||||
|
||||
@@ -6,10 +6,10 @@
|
||||
import { MockCompositionHelper, MockRenderer, MockViewport, TestTerminal } from './TestUtils.test';
|
||||
import type { IBrowser } from './Types';
|
||||
import { assert } from 'chai';
|
||||
import { DEFAULT_ATTR_DATA } from '../common/buffer/BufferLine';
|
||||
import { CellData } from '../common/buffer/CellData';
|
||||
import { DEFAULT_ATTR_DATA } from '../common/primitives/buffer/BufferLine';
|
||||
import { CellData } from '../common/primitives/buffer/CellData';
|
||||
import { MockUnicodeService, createCellData } from '../common/TestUtils.test';
|
||||
import { IMarker } from '../common/buffer/Types';
|
||||
import { IMarker } from '../common/primitives/buffer/Types';
|
||||
|
||||
const INIT_COLS = 80;
|
||||
const INIT_ROWS = 24;
|
||||
|
||||
@@ -7,19 +7,20 @@ import { IDisposable, IMarker, ILinkProvider, IDecorationOptions, IDecoration, I
|
||||
import { ICharacterJoinerService, ICharSizeService, ICoreBrowserService, IMouseService, IRenderService, ISelectionService, IThemeService } from './services/Services';
|
||||
import { IRenderDimensions, IRenderer, IRequestRedrawEvent } from './renderer/shared/Types';
|
||||
import { IColorSet, ITerminal, ILinkifier2, IBrowser, IViewport, ICompositionHelper, CharacterJoinerHandler, IBufferRange, ReadonlyColorSet, IBufferElementProvider } from './Types';
|
||||
import { IAttributeData, IBuffer, IBufferLine, IBufferSet, ICellData } from '../common/buffer/Types';
|
||||
import { ICircularList } from '../common/CircularList';
|
||||
import { XtermListener, ICharset, ITerminalOptions, ColorIndex } from '../common/Types';
|
||||
import { Buffer } from '../common/buffer/Buffer';
|
||||
import * as Browser from '../common/Platform';
|
||||
import { IAttributeData, IBuffer, IBufferLine, IBufferSet, ICellData } from '../common/primitives/buffer/Types';
|
||||
import { ICircularList } from '../common/primitives/CircularList';
|
||||
import { XtermListener, ICharset, ColorIndex } from '../common/primitives/Types';
|
||||
import { ITerminalOptions } from '../common/Types';
|
||||
import { Buffer } from '../common/primitives/buffer/Buffer';
|
||||
import * as Browser from '../common/primitives/Platform';
|
||||
import { CoreBrowserTerminal } from './CoreBrowserTerminal';
|
||||
import { IUnicodeService, IOptionsService, ICoreService, IMouseStateService } from '../common/services/Services';
|
||||
import { IFunctionIdentifier, IParams } from '../common/parser/Types';
|
||||
import { AttributeData } from '../common/buffer/AttributeData';
|
||||
import { IFunctionIdentifier, IParams } from '../common/primitives/parser/Types';
|
||||
import { AttributeData } from '../common/primitives/buffer/AttributeData';
|
||||
import { ISelectionRedrawRequestEvent, ISelectionRequestScrollLinesEvent } from './selection/Types';
|
||||
import { css } from '../common/Color';
|
||||
import { css } from '../common/primitives/Color';
|
||||
import { createRenderDimensions } from './renderer/shared/RendererUtils';
|
||||
import { Emitter, type IEvent } from '../common/Event';
|
||||
import { Emitter, type IEvent } from '../common/primitives/Event';
|
||||
|
||||
export class TestTerminal extends CoreBrowserTerminal {
|
||||
public get curAttrData(): IAttributeData { return (this as any)._inputHandler._curAttrData; }
|
||||
|
||||
@@ -3,12 +3,13 @@
|
||||
* @license MIT
|
||||
*/
|
||||
|
||||
import { IColor, ITerminalOptions } from '../common/Types';
|
||||
import { CharData, IBuffer } from '../common/buffer/Types';
|
||||
import { IColor } from '../common/primitives/Types';
|
||||
import { ITerminalOptions } from '../common/Types';
|
||||
import { CharData, IBuffer } from '../common/primitives/buffer/Types';
|
||||
import { ICoreTerminal } from '../common/CoreTerminal';
|
||||
import { IDisposable, IRenderDimensions as IRenderDimensionsApi, Terminal as ITerminalApi } from '@xterm/xterm';
|
||||
import { channels, css } from '../common/Color';
|
||||
import type { IEvent } from '../common/Event';
|
||||
import { channels, css } from '../common/primitives/Color';
|
||||
import type { IEvent } from '../common/primitives/Event';
|
||||
|
||||
/**
|
||||
* A portion of the public API that are implemented identially internally and simply passed through.
|
||||
|
||||
@@ -5,13 +5,13 @@
|
||||
|
||||
import { ICoreBrowserService, IRenderService, IThemeService } from './services/Services';
|
||||
import { ViewportConstants } from './shared/Constants';
|
||||
import { Disposable, toDisposable } from '../common/Lifecycle';
|
||||
import { Disposable, toDisposable } from '../common/primitives/Lifecycle';
|
||||
import { IBufferService, ICoreService, IMouseStateService, IOptionsService } from '../common/services/Services';
|
||||
import { CoreMouseEventType } from '../common/Types';
|
||||
import { CoreMouseEventType } from '../common/primitives/Types';
|
||||
import { scheduleAtNextAnimationFrame } from './Dom';
|
||||
import { SmoothScrollableElement } from './scrollable/scrollableElement';
|
||||
import type { IScrollableElementChangeOptions } from './scrollable/scrollableElementOptions';
|
||||
import { Emitter, EventUtils } from '../common/Event';
|
||||
import { Emitter, EventUtils } from '../common/primitives/Event';
|
||||
import { Scrollable, ScrollbarVisibility, type IScrollEvent } from './scrollable/scrollable';
|
||||
|
||||
export class Viewport extends Disposable {
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { ICoreBrowserService, IRenderService } from '../services/Services';
|
||||
import { Disposable, toDisposable } from '../../common/Lifecycle';
|
||||
import { Disposable, toDisposable } from '../../common/primitives/Lifecycle';
|
||||
import { IBufferService, IDecorationService, IInternalDecoration } from '../../common/services/Services';
|
||||
|
||||
export class BufferDecorationRenderer extends Disposable {
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
|
||||
import { ColorZoneStore, IColorZone, IColorZoneStore } from './ColorZoneStore';
|
||||
import { ICoreBrowserService, IRenderService, IThemeService } from '../services/Services';
|
||||
import { Disposable, toDisposable } from '../../common/Lifecycle';
|
||||
import { Disposable, toDisposable } from '../../common/primitives/Lifecycle';
|
||||
import { IBufferService, IDecorationService, IOptionsService } from '../../common/services/Services';
|
||||
|
||||
const enum Constants {
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
|
||||
import { IRenderService } from '../services/Services';
|
||||
import { IBufferService, ICoreService, IOptionsService } from '../../common/services/Services';
|
||||
import { C0 } from '../../common/data/EscapeSequences';
|
||||
import { C0 } from '../../common/primitives/data/EscapeSequences';
|
||||
|
||||
interface IPosition {
|
||||
start: number;
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
* @license MIT
|
||||
*/
|
||||
|
||||
import { C0 } from '../../common/data/EscapeSequences';
|
||||
import { C0 } from '../../common/primitives/data/EscapeSequences';
|
||||
import { IBufferService } from '../../common/services/Services';
|
||||
|
||||
const enum Direction {
|
||||
|
||||
@@ -6,14 +6,14 @@
|
||||
import * as Strings from '../LocalizableStrings';
|
||||
import { CoreBrowserTerminal as TerminalCore } from '../CoreBrowserTerminal';
|
||||
import { IBufferRange, ITerminal } from '../Types';
|
||||
import { Disposable } from '../../common/Lifecycle';
|
||||
import { Disposable } from '../../common/primitives/Lifecycle';
|
||||
import { ITerminalOptions } from '../../common/Types';
|
||||
import { AddonManager } from '../../common/public/AddonManager';
|
||||
import { BufferNamespaceApi } from '../../common/public/BufferNamespaceApi';
|
||||
import { ParserApi } from '../../common/public/ParserApi';
|
||||
import { UnicodeApi } from '../../common/public/UnicodeApi';
|
||||
import { IBufferNamespace as IBufferNamespaceApi, IDecoration, IDecorationOptions, IDisposable, ILinkProvider, ILocalizableStrings, IMarker, IModes, IParser, IRenderDimensions, ITerminalAddon, Terminal as ITerminalApi, ITerminalInitOnlyOptions, IUnicodeHandling } from '@xterm/xterm';
|
||||
import type { IEvent } from '../../common/Event';
|
||||
import type { IEvent } from '../../common/primitives/Event';
|
||||
|
||||
/**
|
||||
* The set of options that only have an effect when set in the Terminal constructor.
|
||||
|
||||
@@ -12,10 +12,10 @@ import { TextBlinkStateManager } from '../shared/TextBlinkStateManager';
|
||||
import { IRenderDimensions, IRenderer, IRequestRedrawEvent, ISelectionRenderModel } from '../shared/Types';
|
||||
import { ICharSizeService, ICoreBrowserService, IThemeService } from '../../services/Services';
|
||||
import { ILinkifier2, ILinkifierEvent, ITerminal, ReadonlyColorSet } from '../../Types';
|
||||
import { color } from '../../../common/Color';
|
||||
import { Disposable, toDisposable } from '../../../common/Lifecycle';
|
||||
import { color } from '../../../common/primitives/Color';
|
||||
import { Disposable, toDisposable } from '../../../common/primitives/Lifecycle';
|
||||
import { IBufferService, ICoreService, IInstantiationService, IOptionsService } from '../../../common/services/Services';
|
||||
import { Emitter } from '../../../common/Event';
|
||||
import { Emitter } from '../../../common/primitives/Event';
|
||||
import { addDisposableListener } from '../../Dom';
|
||||
|
||||
|
||||
|
||||
@@ -6,14 +6,16 @@
|
||||
import jsdom = require('jsdom');
|
||||
import { assert } from 'chai';
|
||||
import { DomRendererRowFactory } from './DomRendererRowFactory';
|
||||
import { DEFAULT_ATTR, FgFlags, BgFlags, Attributes, UnderlineStyle } from '../../../common/buffer/Constants';
|
||||
import { BufferLine, DEFAULT_ATTR_DATA } from '../../../common/buffer/BufferLine';
|
||||
import { IBufferLine } from '../../../common/buffer/Types';
|
||||
import { CellData } from '../../../common/buffer/CellData';
|
||||
import { NULL_CELL_CODE, NULL_CELL_WIDTH, NULL_CELL_CHAR, DEFAULT_ATTR, FgFlags, BgFlags, Attributes, UnderlineStyle } from '../../../common/primitives/buffer/Constants';
|
||||
import { BufferLine, DEFAULT_ATTR_DATA } from '../../../common/primitives/buffer/BufferLine';
|
||||
import { BufferLineStringCache } from '../../../common/primitives/buffer/BufferLineStringCache';
|
||||
import { IBufferLine } from '../../../common/primitives/buffer/Types';
|
||||
import { CellData } from '../../../common/primitives/buffer/CellData';
|
||||
import { MockCoreService, MockDecorationService, MockOptionsService, createCellData, NULL_CELL_DATA } from '../../../common/TestUtils.test';
|
||||
import { MockCharacterJoinerService, MockCoreBrowserService, MockThemeService } from '../../TestUtils.test';
|
||||
import { TestWidthCache } from './WidthCache.test';
|
||||
|
||||
const TEST_STRING_CACHE = new BufferLineStringCache();
|
||||
|
||||
describe('DomRendererRowFactory', () => {
|
||||
let dom: jsdom.JSDOM;
|
||||
@@ -515,7 +517,7 @@ describe('DomRendererRowFactory', () => {
|
||||
}
|
||||
|
||||
function createEmptyLineData(cols: number): IBufferLine {
|
||||
const lineData = new BufferLine(cols);
|
||||
const lineData = new BufferLine(TEST_STRING_CACHE, cols);
|
||||
for (let i = 0; i < cols; i++) {
|
||||
lineData.setCell(i, NULL_CELL_DATA);
|
||||
}
|
||||
|
||||
@@ -3,17 +3,17 @@
|
||||
* @license MIT
|
||||
*/
|
||||
|
||||
import { IColor } from '../../../common/Types';
|
||||
import { IBufferLine, ICellData } from '../../../common/buffer/Types';
|
||||
import { IColor } from '../../../common/primitives/Types';
|
||||
import { IBufferLine, ICellData } from '../../../common/primitives/buffer/Types';
|
||||
import { INVERTED_DEFAULT_COLOR } from '../shared/Constants';
|
||||
import { WHITESPACE_CELL_CHAR, Attributes } from '../../../common/buffer/Constants';
|
||||
import { CellData } from '../../../common/buffer/CellData';
|
||||
import { WHITESPACE_CELL_CHAR, Attributes } from '../../../common/primitives/buffer/Constants';
|
||||
import { CellData } from '../../../common/primitives/buffer/CellData';
|
||||
import { ICoreService, IDecorationService, IOptionsService } from '../../../common/services/Services';
|
||||
import { channels, color } from '../../../common/Color';
|
||||
import { channels, color } from '../../../common/primitives/Color';
|
||||
import { ICharacterJoinerService, ICoreBrowserService, IThemeService } from '../../services/Services';
|
||||
import { JoinedCellData } from '../../services/CharacterJoinerService';
|
||||
import { treatGlyphAsBackgroundColor } from '../shared/RendererUtils';
|
||||
import { AttributeData } from '../../../common/buffer/AttributeData';
|
||||
import { AttributeData } from '../../../common/primitives/buffer/AttributeData';
|
||||
import { WidthCache } from './WidthCache';
|
||||
import { IColorContrastCache } from '../../Types';
|
||||
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
*/
|
||||
|
||||
import { throwIfFalsy } from '../shared/RendererUtils';
|
||||
import { IDisposable } from '../../../common/Types';
|
||||
import { IDisposable } from '../../../common/primitives/Types';
|
||||
import { FontWeight } from '../../../common/services/Services';
|
||||
|
||||
|
||||
|
||||
@@ -7,7 +7,7 @@ import { assert } from 'chai';
|
||||
import { TextBlinkStateManager } from './TextBlinkStateManager';
|
||||
import { MockOptionsService } from '../../../common/TestUtils.test';
|
||||
import type { ICoreBrowserService } from '../../services/Services';
|
||||
import { Emitter } from '../../../common/Event';
|
||||
import { Emitter } from '../../../common/primitives/Event';
|
||||
|
||||
class FakeWindow {
|
||||
public nextId = 1;
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
*/
|
||||
|
||||
import { ICoreBrowserService } from '../../services/Services';
|
||||
import { Disposable, toDisposable } from '../../../common/Lifecycle';
|
||||
import { Disposable, toDisposable } from '../../../common/primitives/Lifecycle';
|
||||
import { IOptionsService } from '../../../common/services/Services';
|
||||
|
||||
export class TextBlinkStateManager extends Disposable {
|
||||
|
||||
@@ -5,8 +5,8 @@
|
||||
|
||||
import { Terminal } from '@xterm/xterm';
|
||||
import { ITerminal } from '../../Types';
|
||||
import { IDisposable } from '../../../common/Types';
|
||||
import type { IEvent } from '../../../common/Event';
|
||||
import { IDisposable } from '../../../common/primitives/Types';
|
||||
import type { IEvent } from '../../../common/primitives/Event';
|
||||
|
||||
export interface IDimensions {
|
||||
width: number;
|
||||
|
||||
@@ -11,7 +11,7 @@ import { ScrollbarArrow, IScrollbarArrowOptions } from './scrollbarArrow';
|
||||
import { ScrollbarState } from './scrollbarState';
|
||||
import { ScrollbarVisibilityController } from './scrollbarVisibilityController';
|
||||
import { Widget } from './widget';
|
||||
import * as platform from '../../common/Platform';
|
||||
import * as platform from '../../common/primitives/Platform';
|
||||
import { INewScrollPosition, Scrollable, ScrollbarVisibility } from './scrollable';
|
||||
|
||||
/**
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import * as dom from '../Dom';
|
||||
import { DisposableStore, IDisposable, toDisposable } from '../../common/Lifecycle';
|
||||
import { DisposableStore, IDisposable, toDisposable } from '../../common/primitives/Lifecycle';
|
||||
|
||||
type PointerMoveCallback = (event: PointerEvent) => void;
|
||||
type OnStopCallback = () => void;
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import * as platform from '../../common/Platform';
|
||||
import * as platform from '../../common/primitives/Platform';
|
||||
|
||||
interface IWindowChainElement {
|
||||
readonly window: WeakRef<Window>;
|
||||
|
||||
@@ -3,8 +3,8 @@
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { Emitter, IEvent } from '../../common/Event';
|
||||
import { Disposable, IDisposable } from '../../common/Lifecycle';
|
||||
import { Emitter, IEvent } from '../../common/primitives/Event';
|
||||
import { Disposable, IDisposable } from '../../common/primitives/Lifecycle';
|
||||
|
||||
export const enum ScrollbarVisibility {
|
||||
AUTO = 1,
|
||||
|
||||
@@ -11,10 +11,10 @@ import { HorizontalScrollbar } from './horizontalScrollbar';
|
||||
import { IScrollableElementChangeOptions, IScrollableElementCreationOptions, IScrollableElementResolvedOptions } from './scrollableElementOptions';
|
||||
import { VerticalScrollbar } from './verticalScrollbar';
|
||||
import { Widget } from './widget';
|
||||
import { TimeoutTimer } from '../../common/Async';
|
||||
import { Emitter, IEvent } from '../../common/Event';
|
||||
import { IDisposable, dispose } from '../../common/Lifecycle';
|
||||
import * as platform from '../../common/Platform';
|
||||
import { TimeoutTimer } from '../../common/primitives/Async';
|
||||
import { Emitter, IEvent } from '../../common/primitives/Event';
|
||||
import { IDisposable, dispose } from '../../common/primitives/Lifecycle';
|
||||
import * as platform from '../../common/primitives/Platform';
|
||||
import { INewScrollDimensions, INewScrollPosition, IScrollDimensions, IScrollPosition, IScrollEvent, Scrollable, ScrollbarVisibility } from './scrollable';
|
||||
// import 'vs/css!./media/scrollbars';
|
||||
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
|
||||
import { GlobalPointerMoveMonitor } from './globalPointerMoveMonitor';
|
||||
import { Widget } from './widget';
|
||||
import { TimeoutTimer } from '../../common/Async';
|
||||
import { TimeoutTimer } from '../../common/primitives/Async';
|
||||
import * as dom from '../Dom';
|
||||
|
||||
export interface IScrollbarArrowOptions {
|
||||
|
||||
@@ -4,8 +4,8 @@
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { FastDomNode } from './fastDomNode';
|
||||
import { TimeoutTimer } from '../../common/Async';
|
||||
import { Disposable } from '../../common/Lifecycle';
|
||||
import { TimeoutTimer } from '../../common/primitives/Async';
|
||||
import { Disposable } from '../../common/primitives/Lifecycle';
|
||||
import { ScrollbarVisibility } from './scrollable';
|
||||
|
||||
export class ScrollbarVisibilityController extends Disposable {
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import * as DomUtils from '../Dom';
|
||||
import { Disposable, IDisposable, toDisposable } from '../../common/Lifecycle';
|
||||
import { Disposable, IDisposable, toDisposable } from '../../common/primitives/Lifecycle';
|
||||
|
||||
const mainWindow = (typeof window === 'object' ? window : globalThis) as Window & typeof globalThis;
|
||||
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
|
||||
import * as dom from '../Dom';
|
||||
import { IMouseEvent, StandardMouseEvent } from './mouseEvent';
|
||||
import { Disposable } from '../../common/Lifecycle';
|
||||
import { Disposable } from '../../common/primitives/Lifecycle';
|
||||
|
||||
export abstract class Widget extends Disposable {
|
||||
|
||||
|
||||
@@ -5,8 +5,8 @@
|
||||
|
||||
import { IOptionsService } from '../../common/services/Services';
|
||||
import { ICharSizeService } from './Services';
|
||||
import { Disposable } from '../../common/Lifecycle';
|
||||
import { Emitter } from '../../common/Event';
|
||||
import { Disposable } from '../../common/primitives/Lifecycle';
|
||||
import { Emitter } from '../../common/primitives/Event';
|
||||
|
||||
export class CharSizeService extends Disposable implements ICharSizeService {
|
||||
public serviceBrand: undefined;
|
||||
|
||||
@@ -6,11 +6,13 @@
|
||||
import { assert } from 'chai';
|
||||
import { ICharacterJoinerService } from './Services';
|
||||
import { CharacterJoinerService } from './CharacterJoinerService';
|
||||
import { BufferLine } from '../../common/buffer/BufferLine';
|
||||
import { IBufferLine } from '../../common/buffer/Types';
|
||||
import { CellData } from '../../common/buffer/CellData';
|
||||
import { BufferLine } from '../../common/primitives/buffer/BufferLine';
|
||||
import { BufferLineStringCache } from '../../common/primitives/buffer/BufferLineStringCache';
|
||||
import { IBufferLine } from '../../common/primitives/buffer/Types';
|
||||
import { CellData } from '../../common/primitives/buffer/CellData';
|
||||
import { MockBufferService, createCellData } from '../../common/TestUtils.test';
|
||||
|
||||
const TEST_STRING_CACHE = new BufferLineStringCache();
|
||||
|
||||
describe('CharacterJoinerService', () => {
|
||||
let service: ICharacterJoinerService;
|
||||
@@ -23,7 +25,7 @@ describe('CharacterJoinerService', () => {
|
||||
lines.set(2, lineData([['a -> b -', 0xFFFFFFFF], ['> c -> d', 0]]));
|
||||
|
||||
lines.set(3, lineData([['no joined ranges']]));
|
||||
lines.set(4, new BufferLine(0));
|
||||
lines.set(4, new BufferLine(TEST_STRING_CACHE, 0));
|
||||
lines.set(5, lineData([['a', 0x11111111], [' -> b -> c -> '], ['d', 0x22222222]]));
|
||||
const line6 = lineData([['wi']]);
|
||||
line6.resize(line6.length + 1, createCellData(0, '¥', 2));
|
||||
@@ -268,7 +270,7 @@ describe('CharacterJoinerService', () => {
|
||||
type IPartialLineData = ([string] | [string, number]);
|
||||
|
||||
function lineData(data: IPartialLineData[]): IBufferLine {
|
||||
const tline = new BufferLine(0);
|
||||
const tline = new BufferLine(TEST_STRING_CACHE, 0);
|
||||
for (let i = 0; i < data.length; ++i) {
|
||||
const line = data[i][0];
|
||||
const attr = (data[i][1] || 0) as number;
|
||||
|
||||