Compare commits

..
Author SHA1 Message Date
Cursor AgentandDaniel Imms 467eb5a227 fix(addon-image): cast buffer line lookups to IBufferLineExt | undefined
Co-authored-by: Daniel Imms <Tyriar@users.noreply.github.com>
2026-06-06 16:26:54 +00:00
Daniel ImmsandCursor a8632435a6 fix(addon-image): skip missing lines in viewportResize
viewportResize walks the full buffer when the terminal widens but
called line.getBg without checking that lines.get(row) returned a
line. During resize or trim gaps that could throw and abort tile
expansion for remaining rows.

Skip missing buffer lines with continue, consistent with render().

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-03 03:36:42 +00:00
Cursor AgentandDaniel Imms a0919b9004 fix(addon-image): skip missing buffer lines in render
Co-authored-by: Daniel Imms <Tyriar@users.noreply.github.com>
2026-06-03 03:36:41 +00:00
Daniel ImmsandCursor b5e0cdb20c fix(addon-image): scan new columns on viewport width increase
viewportResize() should check whether cells in newly exposed columns
(oldCol + 1 through cols - 1) already have content before expanding
image tiles to the right. The loop used `rightCol > metrics.cols`,
which is false when widening (rightCol starts at oldCol + 1), so the
loop never ran, hasData stayed false, and tiles could overwrite cells
that already held text.

Use `rightCol < metrics.cols` so the emptiness check runs over the
intended column range.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-03 03:36:41 +00:00
26 changed files with 221 additions and 500 deletions
+27 -19
View File
@@ -11,9 +11,9 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
timeout-minutes: 10 timeout-minutes: 10
steps: steps:
- uses: actions/checkout@v5 - uses: actions/checkout@v3
- name: Use Node.js 22.x - name: Use Node.js 22.x
uses: actions/setup-node@v5 uses: actions/setup-node@v3
with: with:
node-version: 22.x node-version: 22.x
cache: 'npm' cache: 'npm'
@@ -69,7 +69,7 @@ jobs:
./addons/addon-webgl/out/* \ ./addons/addon-webgl/out/* \
./addons/addon-webgl/out-*st/* ./addons/addon-webgl/out-*st/*
- name: Upload artifacts - name: Upload artifacts
uses: actions/upload-artifact@v6 uses: actions/upload-artifact@v4
with: with:
name: build-artifacts name: build-artifacts
path: compressed-build.zip path: compressed-build.zip
@@ -79,9 +79,9 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
timeout-minutes: 10 timeout-minutes: 10
steps: steps:
- uses: actions/checkout@v5 - uses: actions/checkout@v3
- name: Use Node.js 22.x - name: Use Node.js 22.x
uses: actions/setup-node@v5 uses: actions/setup-node@v3
with: with:
node-version: 22.x node-version: 22.x
cache: 'npm' cache: 'npm'
@@ -100,16 +100,16 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
timeout-minutes: 10 timeout-minutes: 10
steps: steps:
- uses: actions/checkout@v5 - uses: actions/checkout@v3
- name: Use Node.js 22.x - name: Use Node.js 22.x
uses: actions/setup-node@v5 uses: actions/setup-node@v3
with: with:
node-version: 22.x node-version: 22.x
cache: 'npm' cache: 'npm'
- name: Install dependencies - name: Install dependencies
run: | run: |
npm ci npm ci
- uses: actions/download-artifact@v7 - uses: actions/download-artifact@v4
with: with:
name: build-artifacts name: build-artifacts
- name: Unzip artifacts - name: Unzip artifacts
@@ -129,7 +129,6 @@ jobs:
exit $EXIT_CODE exit $EXIT_CODE
test-unit: test-unit:
needs: build
timeout-minutes: 20 timeout-minutes: 20
strategy: strategy:
matrix: matrix:
@@ -137,16 +136,21 @@ jobs:
runs-on: [ubuntu, macos, windows] runs-on: [ubuntu, macos, windows]
runs-on: ${{ matrix.runs-on }}-latest runs-on: ${{ matrix.runs-on }}-latest
steps: steps:
- uses: actions/checkout@v5 - uses: actions/checkout@v3
- name: Use Node.js ${{ matrix.node-version }}.x - name: Use Node.js ${{ matrix.node-version }}.x
uses: actions/setup-node@v5 uses: actions/setup-node@v3
with: with:
node-version: ${{ matrix.node-version }}.x node-version: ${{ matrix.node-version }}.x
cache: 'npm' cache: 'npm'
- name: Install dependencies - name: Install dependencies
run: | run: |
npm ci npm ci
- uses: actions/download-artifact@v7 - name: Wait for build job
uses: NathanFirmo/wait-for-other-job@v1.1.1
with:
token: ${{ secrets.GITHUB_TOKEN }}
job: build
- uses: actions/download-artifact@v4
with: with:
name: build-artifacts name: build-artifacts
- name: Unzip artifacts - name: Unzip artifacts
@@ -162,7 +166,6 @@ jobs:
run: npm run test-unit --forbid-only run: npm run test-unit --forbid-only
test-integration: test-integration:
needs: build
timeout-minutes: 20 timeout-minutes: 20
strategy: strategy:
matrix: matrix:
@@ -171,9 +174,9 @@ jobs:
browser: [chromium, firefox, webkit] browser: [chromium, firefox, webkit]
runs-on: ${{ matrix.runs-on }} runs-on: ${{ matrix.runs-on }}
steps: steps:
- uses: actions/checkout@v5 - uses: actions/checkout@v3
- name: Use Node.js ${{ matrix.node-version }}.x - name: Use Node.js ${{ matrix.node-version }}.x
uses: actions/setup-node@v5 uses: actions/setup-node@v3
with: with:
node-version: ${{ matrix.node-version }}.x node-version: ${{ matrix.node-version }}.x
cache: 'npm' cache: 'npm'
@@ -182,7 +185,12 @@ jobs:
npm ci npm ci
- name: Install playwright - name: Install playwright
run: npx playwright install --with-deps ${{ matrix.browser }} run: npx playwright install --with-deps ${{ matrix.browser }}
- uses: actions/download-artifact@v7 - name: Wait for build job
uses: NathanFirmo/wait-for-other-job@v1.1.1
with:
token: ${{ secrets.GITHUB_TOKEN }}
job: build
- uses: actions/download-artifact@v4
with: with:
name: build-artifacts name: build-artifacts
- name: Unzip artifacts - name: Unzip artifacts
@@ -232,9 +240,9 @@ jobs:
matrix: matrix:
node-version: [22] node-version: [22]
steps: steps:
- uses: actions/checkout@v5 - uses: actions/checkout@v3
- name: Use Node.js ${{ matrix.node-version }}.x - name: Use Node.js ${{ matrix.node-version }}.x
uses: actions/setup-node@v5 uses: actions/setup-node@v3
with: with:
node-version: ${{ matrix.node-version }}.x node-version: ${{ matrix.node-version }}.x
cache: 'npm' cache: 'npm'
@@ -243,7 +251,7 @@ jobs:
npm ci npm ci
- name: Install playwright - name: Install playwright
run: npx playwright install run: npx playwright install
- uses: actions/download-artifact@v7 - uses: actions/download-artifact@v4
with: with:
name: build-artifacts name: build-artifacts
- name: Unzip artifacts - name: Unzip artifacts
+4 -4
View File
@@ -24,12 +24,12 @@ jobs:
steps: steps:
- name: Checkout repository - name: Checkout repository
uses: actions/checkout@v5 uses: actions/checkout@v3
- name: Initialize CodeQL - name: Initialize CodeQL
uses: github/codeql-action/init@v4 uses: github/codeql-action/init@v2
with: with:
languages: ${{ matrix.language }} languages: ${{ matrix.language }}
- name: Autobuild - name: Autobuild
uses: github/codeql-action/autobuild@v4 uses: github/codeql-action/autobuild@v2
- name: Perform CodeQL Analysis - name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@v4 uses: github/codeql-action/analyze@v2
+2 -2
View File
@@ -18,9 +18,9 @@ jobs:
steps: steps:
- name: Checkout code - name: Checkout code
uses: actions/checkout@v5 uses: actions/checkout@v4
- name: Use Node.js 22.x - name: Use Node.js 22.x
uses: actions/setup-node@v5 uses: actions/setup-node@v3
with: with:
node-version: 22.x node-version: 22.x
cache: 'npm' cache: 'npm'
+2 -2
View File
@@ -10,9 +10,9 @@ jobs:
release: release:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v5 - uses: actions/checkout@v3
- name: Use Node.js 22.x - name: Use Node.js 22.x
uses: actions/setup-node@v5 uses: actions/setup-node@v3
with: with:
node-version: 22.x node-version: 22.x
cache: 'npm' cache: 'npm'
-1
View File
@@ -254,7 +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. - [**WooTTY**](https://github.com/icoretech/wootty): Flawless browser terminal for real operators.
- [**Orca**](https://github.com/stablyai/orca): Agentic development environment for 100x builders. - [**Orca**](https://github.com/stablyai/orca): Agentic development environment for 100x builders.
- [**4bit**](https://ciembor.github.io/4bit): Terminal Color Scheme Designer. - [**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.
- [And much more...](https://github.com/xtermjs/xterm.js/network/dependents?package_id=UGFja2FnZS0xNjYzMjc4OQ%3D%3D) - [And much more...](https://github.com/xtermjs/xterm.js/network/dependents?package_id=UGFja2FnZS0xNjYzMjc4OQ%3D%3D)
Do you use xterm.js in your application as well? Please [open a Pull Request](https://github.com/sourcelair/xterm.js/pulls) to include it here. We would love to have it on our list. Please add any new contributions to the end of the list. Do you use xterm.js in your application as well? Please [open a Pull Request](https://github.com/sourcelair/xterm.js/pulls) to include it here. We would love to have it on our list. Please add any new contributions to the end of the list.
+2 -32
View File
@@ -78,43 +78,13 @@ export class BrowserClipboardProvider implements IClipboardProvider {
} }
} }
/**
* TODO: Once the base64 handling on Uint8Arrays is more common,
* remove the btoa/atob fallbacks below.
*/
interface IUint8ArrayB64 extends Uint8Array {
toBase64(): string;
}
interface IUint8ArrayB64Ctor extends Uint8ArrayConstructor {
fromBase64(s: string): IUint8ArrayB64;
}
export class Base64 implements IBase64 { export class Base64 implements IBase64 {
public encodeText(data: string): string { public encodeText(data: string): string {
const bytes = new TextEncoder().encode(data) as IUint8ArrayB64; return btoa(data);
if (bytes.toBase64 !== undefined) {
return bytes.toBase64();
}
let bin = '';
for (let i = 0; i < bytes.length; i++) {
bin += String.fromCharCode(bytes[i]);
}
return btoa(bin);
} }
public decodeText(data: string): string { public decodeText(data: string): string {
if ((Uint8Array as IUint8ArrayB64Ctor).fromBase64 !== undefined) {
try {
return new TextDecoder().decode((Uint8Array as IUint8ArrayB64Ctor).fromBase64(data));
} catch {}
return '';
}
try { try {
const bin = atob(data); return atob(data);
const bytes = new Uint8Array(bin.length);
for (let i = 0; i < bin.length; ++i) {
bytes[i] = bin.charCodeAt(i);
}
return new TextDecoder().decode(bytes);
} catch {} } catch {}
return ''; return '';
} }
@@ -29,7 +29,7 @@ test.describe('ClipboardAddon', () => {
await ctx.page.context().grantPermissions(['clipboard-read', 'clipboard-write']); await ctx.page.context().grantPermissions(['clipboard-read', 'clipboard-write']);
} }
await ctx.page.evaluate(` await ctx.page.evaluate(`
window.term.reset(); window.term.reset()
window.clipboard?.dispose(); window.clipboard?.dispose();
window.clipboard = new ClipboardAddon(); window.clipboard = new ClipboardAddon();
window.term.loadAddon(window.clipboard); window.term.loadAddon(window.clipboard);
@@ -92,22 +92,4 @@ test.describe('ClipboardAddon', () => {
deepEqual(await ctx.page.evaluate(() => window.navigator.clipboard.readText()), ''); deepEqual(await ctx.page.evaluate(() => window.navigator.clipboard.readText()), '');
}); });
}); });
test.describe('non-ASCII', () => {
const testDataEncoded = '4oKsbWzDpMO8dMOf';
const testDataDecoded = '€mläütß';
test('write simple string', async () => {
await ctx.proxy.write(`\x1b]52;c;${testDataEncoded}\x07`);
deepEqual(await ctx.page.evaluate(() => window.navigator.clipboard.readText()), testDataDecoded);
});
test('read simple string', async () => {
await ctx.page.evaluate(`
window.data2 = [];
window.term.onData(e => data2.push(e));
`);
await ctx.page.evaluate((d) => window.navigator.clipboard.writeText(d), testDataDecoded);
await ctx.proxy.write(`\x1b]52;c;?\x07`);
deepEqual(await ctx.page.evaluate('window.data2'), [`\x1b]52;c;${testDataEncoded}\x07`]);
});
});
}); });
+7 -4
View File
@@ -431,8 +431,8 @@ export class ImageStorage implements IDisposable {
// because text writes clear the BG flag but leave image tile data intact. // 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). // This lets top-layer images survive text overwrites (kitty C=1 behavior).
for (let row = start; row <= end; ++row) { for (let row = start; row <= end; ++row) {
const line = buffer.lines.get(row + buffer.ydisp) as IBufferLineExt; const line = buffer.lines.get(row + buffer.ydisp) as IBufferLineExt | undefined;
if (!line) return; if (!line) continue;
for (let col = 0; col < cols; ++col) { for (let col = 0; col < cols; ++col) {
let e: IExtendedAttrsImage; let e: IExtendedAttrsImage;
if (line.getBg(col) & BgFlags.HAS_EXTENDED) { if (line.getBg(col) & BgFlags.HAS_EXTENDED) {
@@ -513,7 +513,10 @@ export class ImageStorage implements IDisposable {
const rows = buffer.lines.length; const rows = buffer.lines.length;
const oldCol = this._viewportMetrics.cols - 1; const oldCol = this._viewportMetrics.cols - 1;
for (let row = 0; row < rows; ++row) { for (let row = 0; row < rows; ++row) {
const line = buffer.lines.get(row) as IBufferLineExt; const line = buffer.lines.get(row) as IBufferLineExt | undefined;
if (!line) {
continue;
}
if (line.getBg(oldCol) & BgFlags.HAS_EXTENDED) { if (line.getBg(oldCol) & BgFlags.HAS_EXTENDED) {
const e: IExtendedAttrsImage = line._extendedAttrs[oldCol] ?? EMPTY_ATTRS; const e: IExtendedAttrsImage = line._extendedAttrs[oldCol] ?? EMPTY_ATTRS;
const imageId = e.imageId; const imageId = e.imageId;
@@ -531,7 +534,7 @@ export class ImageStorage implements IDisposable {
} }
// expand only if right side is empty (nothing got wrapped from below) // expand only if right side is empty (nothing got wrapped from below)
let hasData = false; let hasData = false;
for (let rightCol = oldCol + 1; rightCol > metrics.cols; ++rightCol) { for (let rightCol = oldCol + 1; rightCol < metrics.cols; ++rightCol) {
if (line._data[rightCol * Cell.SIZE + Cell.CONTENT] & Content.HAS_CONTENT_MASK) { if (line._data[rightCol * Cell.SIZE + Cell.CONTENT] & Content.HAS_CONTENT_MASK) {
hasData = true; hasData = true;
break; break;
+6 -8
View File
@@ -142,14 +142,12 @@ export class SearchAddon extends Disposable implements ITerminalAddon, ISearchAp
} }
prevResult = result; prevResult = result;
results.push(prevResult); results.push(prevResult);
const cols = this._terminal.cols; result = this._engine.find(
let nextCol = prevResult.col + prevResult.size; term,
let nextRow = prevResult.row; prevResult.col + prevResult.term.length >= this._terminal.cols ? prevResult.row + 1 : prevResult.row,
if (nextCol >= cols) { prevResult.col + prevResult.term.length >= this._terminal.cols ? 0 : prevResult.col + 1,
nextRow += Math.floor(nextCol / cols); searchOptions
nextCol = nextCol % cols; );
}
result = this._engine.find(term, nextRow, nextCol, searchOptions);
} }
this._resultTracker.updateResults(results, this._highlightLimit); this._resultTracker.updateResults(results, this._highlightLimit);
@@ -414,20 +414,6 @@ test.describe('Search Tests', () => {
}); });
test.describe('Regression tests', () => { test.describe('Regression tests', () => {
test('should advance highlight-all scan by buffer match size for wide characters', async () => {
await ctx.page.evaluate(`
window.calls = [];
window.search.onDidChangeResults(e => window.calls.push(e));
`);
await ctx.proxy.resize(3, 5);
await ctx.proxy.write('𝄞𝄞𝄞');
strictEqual(await ctx.page.evaluate(`window.search.findNext('𝄞', { decorations: { activeMatchColorOverviewRuler: '#ff0000', matchOverviewRuler: '#ffff00' } })`), true);
deepStrictEqual(await ctx.page.evaluate('window.calls'), [
{ resultCount: 3, resultIndex: 0 }
]);
await ctx.proxy.resize(80, 24);
});
test.describe('#2444 wrapped line content not being found', () => { test.describe('#2444 wrapped line content not being found', () => {
let fixture: string; let fixture: string;
test.beforeAll(async () => { test.beforeAll(async () => {
+1 -2
View File
@@ -389,9 +389,8 @@ export class GlyphRenderer extends Disposable {
gl.bindTexture(gl.TEXTURE_2D, this._atlasTextures[i].texture); gl.bindTexture(gl.TEXTURE_2D, this._atlasTextures[i].texture);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE); gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE); gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR);
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, atlas.pages[i].canvas); gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, atlas.pages[i].canvas);
gl.generateMipmap(gl.TEXTURE_2D);
this._atlasTextures[i].version = atlas.pages[i].version; this._atlasTextures[i].version = atlas.pages[i].version;
} }
+6 -22
View File
@@ -1080,33 +1080,17 @@ class AtlasPage {
size: number, size: number,
sourcePages?: AtlasPage[] sourcePages?: AtlasPage[]
) { ) {
if (sourcePages) {
for (const p of sourcePages) {
this._glyphs.push(...p.glyphs);
this._usedPixels += p._usedPixels;
}
}
this.canvas = createCanvas(document, size, size); this.canvas = createCanvas(document, size, size);
// The canvas needs alpha because we use clearColor to convert the background color to alpha. // The canvas needs alpha because we use clearColor to convert the background color to alpha.
// It might also contain some characters with transparent backgrounds if allowTransparency is // It might also contain some characters with transparent backgrounds if allowTransparency is
// set. // set.
this.ctx = throwIfFalsy(this.canvas.getContext('2d', { alpha: true })); this.ctx = throwIfFalsy(this.canvas.getContext('2d', { alpha: true }));
if (sourcePages) {
if (sourcePages.length === 4) {
// optimized for quadmerge
this._glyphs = this._glyphs.concat(
sourcePages[0].glyphs,
sourcePages[1].glyphs,
sourcePages[2].glyphs,
sourcePages[3].glyphs
);
this._usedPixels = sourcePages[0]._usedPixels +
sourcePages[1]._usedPixels +
sourcePages[2]._usedPixels +
sourcePages[3]._usedPixels;
} else {
// fallback for non quadmerges (should never be used)
for (let i = 0; i < sourcePages.length; ++i) {
this._glyphs = this._glyphs.concat(sourcePages[i].glyphs);
this._usedPixels += sourcePages[i]._usedPixels;
}
}
}
} }
public clear(): void { public clear(): void {
+6 -21
View File
@@ -456,18 +456,7 @@ export class WebglRenderer extends Disposable implements IRenderer {
for (y = start; y <= end; y++) { for (y = start; y <= end; y++) {
row = y + terminal.buffer.ydisp; row = y + terminal.buffer.ydisp;
const bufferLine = terminal.buffer.lines.get(row); line = terminal.buffer.lines.get(row)!;
if (!bufferLine) {
this._model.lineLengths[y] = 0;
for (x = 0; x < terminal.cols; x++) {
j = ((y * terminal.cols) + x) * RenderModelConstants.INDICIES_PER_CELL;
modelUpdated = true;
this._nullModelCell(x, y, j, 0, 0, 0);
}
this._setRowBlinkState(y, false);
continue;
}
line = bufferLine;
let rowHasBlinkingCells = false; let rowHasBlinkingCells = false;
this._model.lineLengths[y] = 0; this._model.lineLengths[y] = 0;
isCursorRow = cursorY === row; isCursorRow = cursorY === row;
@@ -598,9 +587,13 @@ export class WebglRenderer extends Disposable implements IRenderer {
// Null out non-first cells // Null out non-first cells
for (x++; x <= lastCharX; x++) { for (x++; x <= lastCharX; x++) {
j = ((y * terminal.cols) + x) * RenderModelConstants.INDICIES_PER_CELL; j = ((y * terminal.cols) + x) * RenderModelConstants.INDICIES_PER_CELL;
this._glyphRenderer.value!.updateCell(x, y, NULL_CELL_CODE, 0, 0, 0, NULL_CELL_CHAR, 0, 0);
this._model.cells[j] = NULL_CELL_CODE;
// Don't re-resolve the cell color since multi-colored ligature backgrounds are not // Don't re-resolve the cell color since multi-colored ligature backgrounds are not
// supported // supported
this._nullModelCell(x, y, j, this._cellColorResolver.result.bg, this._cellColorResolver.result.fg, this._cellColorResolver.result.ext); this._model.cells[j + RenderModelConstants.BG_OFFSET] = this._cellColorResolver.result.bg;
this._model.cells[j + RenderModelConstants.FG_OFFSET] = this._cellColorResolver.result.fg;
this._model.cells[j + RenderModelConstants.EXT_OFFSET] = this._cellColorResolver.result.ext;
} }
x--; // Go back to the previous update cell for next iteration x--; // Go back to the previous update cell for next iteration
} }
@@ -614,14 +607,6 @@ export class WebglRenderer extends Disposable implements IRenderer {
this._updateTextBlinkState(); this._updateTextBlinkState();
} }
private _nullModelCell(x: number, y: number, cellIndex: number, bg: number, fg: number, ext: number): void {
this._glyphRenderer.value!.updateCell(x, y, NULL_CELL_CODE, bg, fg, ext, NULL_CELL_CHAR, 0, 0);
this._model.cells[cellIndex] = NULL_CELL_CODE;
this._model.cells[cellIndex + RenderModelConstants.BG_OFFSET] = bg;
this._model.cells[cellIndex + RenderModelConstants.FG_OFFSET] = fg;
this._model.cells[cellIndex + RenderModelConstants.EXT_OFFSET] = ext;
}
private _resetBlinkingRowState(): void { private _resetBlinkingRowState(): void {
this._rowHasBlinkingCells = new Array(this._terminal.rows).fill(false); this._rowHasBlinkingCells = new Array(this._terminal.rows).fill(false);
this._rowHasBlinkingCellsCount = 0; this._rowHasBlinkingCellsCount = 0;
@@ -11,33 +11,8 @@ export class WebglWindow extends BaseWindow implements IControlWindow {
public readonly label = 'webgl'; public readonly label = 'webgl';
private _textureAtlasContainer!: HTMLElement; private _textureAtlasContainer!: HTMLElement;
private _stressRunning = false;
private _stressScrolling: HTMLInputElement | undefined;
public build(container: HTMLElement): void { public build(container: HTMLElement): void {
const stressDiv = document.createElement('div');
const stressStart = document.createElement('button');
stressStart.id = 'stress-start';
stressStart.textContent = 'atlas stress start';
stressStart.addEventListener('click', () => this._stress());
stressDiv.appendChild(stressStart);
const stressStop = document.createElement('button');
stressStop.id = 'stress-stop';
stressStop.textContent = 'atlas stress stop';
stressStop.addEventListener('click', () => this._stressRunning = false);
stressDiv.appendChild(stressStop);
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'); const zoomCheckbox = document.createElement('input');
zoomCheckbox.type = 'checkbox'; zoomCheckbox.type = 'checkbox';
zoomCheckbox.id = 'texture-atlas-zoom'; zoomCheckbox.id = 'texture-atlas-zoom';
@@ -73,33 +48,4 @@ export class WebglWindow extends BaseWindow implements IControlWindow {
canvas.style.width = `${canvas.width / dpr}px`; canvas.style.width = `${canvas.width / dpr}px`;
canvas.style.height = `${canvas.height / dpr}px`; canvas.style.height = `${canvas.height / dpr}px`;
} }
private async _stress(): Promise<void> {
const TEXT = '!"#$%&\'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijklmnopqrstuvwxyz{|}~';
if (this._stressRunning) {
return;
}
this._stressRunning = true;
const scrolling = !!(this._stressScrolling?.checked);
for (let r = 0; r < 256; r += 2) {
for (let g = 0; g < 256; g += 2) {
let s: string[] = [];
for (let b = 0; b < 256; b += 2) {
if (!this._stressRunning) {
return;
}
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 (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));
}
s = [];
}
}
}
}
}
} }
+107 -107
View File
@@ -663,9 +663,9 @@
} }
}, },
"node_modules/@esbuild/aix-ppc64": { "node_modules/@esbuild/aix-ppc64": {
"version": "0.28.1", "version": "0.28.0",
"resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.28.1.tgz", "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.28.0.tgz",
"integrity": "sha512-Svl7tq8k/08+p6CXPpRjQ1fKX+1odH/BQbb48fV6fj3CWHhsoIOoY87w1oHXm0qEpkIK3ZfVgp0hed3XBXzXMQ==", "integrity": "sha512-lhRUCeuOyJQURhTxl4WkpFTjIsbDayJHih5kZC1giwE+MhIzAb7mEsQMqMf18rHLsrb5qI1tafG20mLxEWcWlA==",
"cpu": [ "cpu": [
"ppc64" "ppc64"
], ],
@@ -680,9 +680,9 @@
} }
}, },
"node_modules/@esbuild/android-arm": { "node_modules/@esbuild/android-arm": {
"version": "0.28.1", "version": "0.28.0",
"resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.28.1.tgz", "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.28.0.tgz",
"integrity": "sha512-0k2F129Xdio1TdJfzJ8sy1Q47vUD2NnwdhiAf7drUN1EBTfPf4hsFCtmMgu/6m8JSzsBrlmVjudMBQqOfG8usQ==", "integrity": "sha512-wqh0ByljabXLKHeWXYLqoJ5jKC4XBaw6Hk08OfMrCRd2nP2ZQ5eleDZC41XHyCNgktBGYMbqnrJKq/K/lzPMSQ==",
"cpu": [ "cpu": [
"arm" "arm"
], ],
@@ -697,9 +697,9 @@
} }
}, },
"node_modules/@esbuild/android-arm64": { "node_modules/@esbuild/android-arm64": {
"version": "0.28.1", "version": "0.28.0",
"resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.28.1.tgz", "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.28.0.tgz",
"integrity": "sha512-34EGEbCIAgosYz6goLcopX6Mo7NyGv9tfwEM2/7Ce2VcVRk568iSvniGWcUXIy7wEDR1wzolcxcriFVrWYcwBg==", "integrity": "sha512-+WzIXQOSaGs33tLEgYPYe/yQHf0WTU0X42Jca3y8NWMbUVhp7rUnw+vAsRC/QiDrdD31IszMrZy+qwPOPjd+rw==",
"cpu": [ "cpu": [
"arm64" "arm64"
], ],
@@ -714,9 +714,9 @@
} }
}, },
"node_modules/@esbuild/android-x64": { "node_modules/@esbuild/android-x64": {
"version": "0.28.1", "version": "0.28.0",
"resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.28.1.tgz", "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.28.0.tgz",
"integrity": "sha512-dbwY7ltSMDWsRatcRpCnES4F+im88OCUgGZjy52shC7GqHRE/cYlxNbB4Z4UpJswpcc4Qxd2oE/ufM0p61IKng==", "integrity": "sha512-+VJggoaKhk2VNNqVL7f6S189UzShHC/mR9EE8rDdSkdpN0KflSwWY/gWjDrNxxisg8Fp1ZCD9jLMo4m0OUfeUA==",
"cpu": [ "cpu": [
"x64" "x64"
], ],
@@ -731,9 +731,9 @@
} }
}, },
"node_modules/@esbuild/darwin-arm64": { "node_modules/@esbuild/darwin-arm64": {
"version": "0.28.1", "version": "0.28.0",
"resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.28.1.tgz", "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.28.0.tgz",
"integrity": "sha512-TZbWkQY7kvTAXbXUT7uVACR5cMHsDiSz9z7ZKAX/RTq/WJEk3QyRr0wZpNhBDX+/0CtdqUIJlOiodQcta6tY3Q==", "integrity": "sha512-0T+A9WZm+bZ84nZBtk1ckYsOvyA3x7e2Acj1KdVfV4/2tdG4fzUp91YHx+GArWLtwqp77pBXVCPn2We7Letr0Q==",
"cpu": [ "cpu": [
"arm64" "arm64"
], ],
@@ -748,9 +748,9 @@
} }
}, },
"node_modules/@esbuild/darwin-x64": { "node_modules/@esbuild/darwin-x64": {
"version": "0.28.1", "version": "0.28.0",
"resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.28.1.tgz", "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.28.0.tgz",
"integrity": "sha512-zfdzgK9ACBNZLI/CyHTOx81SyNbM6YXn7rxSgX97VjyiPl9W1i4Ka4fgKECEoFCKGpvBj5qArWIGgQjOwkgskQ==", "integrity": "sha512-fyzLm/DLDl/84OCfp2f/XQ4flmORsjU7VKt8HLjvIXChJoFFOIL6pLJPH4Yhd1n1gGFF9mPwtlN5Wf82DZs+LQ==",
"cpu": [ "cpu": [
"x64" "x64"
], ],
@@ -765,9 +765,9 @@
} }
}, },
"node_modules/@esbuild/freebsd-arm64": { "node_modules/@esbuild/freebsd-arm64": {
"version": "0.28.1", "version": "0.28.0",
"resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.28.1.tgz", "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.28.0.tgz",
"integrity": "sha512-wG2EA8ENdEI0qhkSZMjfqrdY+ziCYCPMmtZjjIwOmXFjmyzEHn+UUxk5of+SYsjtfs3VpnlC7QLzSI5hY/rOAw==", "integrity": "sha512-l9GeW5UZBT9k9brBYI+0WDffcRxgHQD8ShN2Ur4xWq/NFzUKm3k5lsH4PdaRgb2w7mI9u61nr2gI2mLI27Nh3Q==",
"cpu": [ "cpu": [
"arm64" "arm64"
], ],
@@ -782,9 +782,9 @@
} }
}, },
"node_modules/@esbuild/freebsd-x64": { "node_modules/@esbuild/freebsd-x64": {
"version": "0.28.1", "version": "0.28.0",
"resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.28.1.tgz", "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.28.0.tgz",
"integrity": "sha512-i7dZ9vQgnvSCzi/rYCXNgtF/U+eKZNJBzu3eTQbRgHnM7tNSizLOkRFAl3qzVc/Op/u5YkHHa4pf/3DOYHthLQ==", "integrity": "sha512-BXoQai/A0wPO6Es3yFJ7APCiKGc1tdAEOgeTNy3SsB491S3aHn4S4r3e976eUnPdU+NbdtmBuLncYir2tMU9Nw==",
"cpu": [ "cpu": [
"x64" "x64"
], ],
@@ -799,9 +799,9 @@
} }
}, },
"node_modules/@esbuild/linux-arm": { "node_modules/@esbuild/linux-arm": {
"version": "0.28.1", "version": "0.28.0",
"resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.28.1.tgz", "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.28.0.tgz",
"integrity": "sha512-qVXBOHQS+d5Y722GwJzJUtOLlX7km3CraOaGormF1pDtPd2C/l1SHRPgjLunLGe51Sh5YYWKMFDyV4SxgMQYTQ==", "integrity": "sha512-CjaaREJagqJp7iTaNQjjidaNbCKYcd4IDkzbwwxtSvjI7NZm79qiHc8HqciMddQ6CKvJT6aBd8lO9kN/ZudLlw==",
"cpu": [ "cpu": [
"arm" "arm"
], ],
@@ -816,9 +816,9 @@
} }
}, },
"node_modules/@esbuild/linux-arm64": { "node_modules/@esbuild/linux-arm64": {
"version": "0.28.1", "version": "0.28.0",
"resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.28.1.tgz", "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.28.0.tgz",
"integrity": "sha512-yHs+0uc8+nvEAfAfxrWQKK5peSNzBc4PegcMO0EJ2hT71uA7vB8Ihg2e77R2P7SG5uYjPbHlLLmve4LLLRCf0g==", "integrity": "sha512-RVyzfb3FWsGA55n6WY0MEIEPURL1FcbhFE6BffZEMEekfCzCIMtB5yyDcFnVbTnwk+CLAgTujmV/Lgvih56W+A==",
"cpu": [ "cpu": [
"arm64" "arm64"
], ],
@@ -833,9 +833,9 @@
} }
}, },
"node_modules/@esbuild/linux-ia32": { "node_modules/@esbuild/linux-ia32": {
"version": "0.28.1", "version": "0.28.0",
"resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.28.1.tgz", "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.28.0.tgz",
"integrity": "sha512-d1z4ZuP0ajrfz/FhGT4vv278rX8KnPPJx8i5+AtK7TYbx9Le9F1hyzurZpkEyjkGa9dUGhQow4C1NmeGvqxN2w==", "integrity": "sha512-KBnSTt1kxl9x70q+ydterVdl+Cn0H18ngRMRCEQfrbqdUuntQQ0LoMZv47uB97NljZFzY6HcfqEZ2SAyIUTQBQ==",
"cpu": [ "cpu": [
"ia32" "ia32"
], ],
@@ -850,9 +850,9 @@
} }
}, },
"node_modules/@esbuild/linux-loong64": { "node_modules/@esbuild/linux-loong64": {
"version": "0.28.1", "version": "0.28.0",
"resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.28.1.tgz", "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.28.0.tgz",
"integrity": "sha512-M5sRjUVZrkm1OAPR3dlOYzNmN+loZKGVi1VUQGrwuqLcbR6qeAz+famMhjASeH3YVKvZz+zT1jlh/keC3Rj/lg==", "integrity": "sha512-zpSlUce1mnxzgBADvxKXX5sl8aYQHo2ezvMNI8I0lbblJtp8V4odlm3Yzlj7gPyt3T8ReksE6bK+pT3WD+aJRg==",
"cpu": [ "cpu": [
"loong64" "loong64"
], ],
@@ -867,9 +867,9 @@
} }
}, },
"node_modules/@esbuild/linux-mips64el": { "node_modules/@esbuild/linux-mips64el": {
"version": "0.28.1", "version": "0.28.0",
"resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.28.1.tgz", "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.28.0.tgz",
"integrity": "sha512-mRObBZeHh2OxcBFPWE/FjylkRgZdYuiTR3vaTozquCGOH14iP9oN4x4Ge81CoIDYQrXmIxpFumJBu5MtZpnQJQ==", "integrity": "sha512-2jIfP6mmjkdmeTlsX/9vmdmhBmKADrWqN7zcdtHIeNSCH1SqIoNI63cYsjQR8J+wGa4Y5izRcSHSm8K3QWmk3w==",
"cpu": [ "cpu": [
"mips64el" "mips64el"
], ],
@@ -884,9 +884,9 @@
} }
}, },
"node_modules/@esbuild/linux-ppc64": { "node_modules/@esbuild/linux-ppc64": {
"version": "0.28.1", "version": "0.28.0",
"resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.28.1.tgz", "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.28.0.tgz",
"integrity": "sha512-slScBsMAb3GFDcdrCgLwZtPYRoH2H/youv10QiZyRjmsP48fznoveWytSgCI/R0ZcUgpc0ZhIUEx6LHts8yrfQ==", "integrity": "sha512-bc0FE9wWeC0WBm49IQMPSPILRocGTQt3j5KPCA8os6VprfuJ7KD+5PzESSrJ6GmPIPJK965ZJHTUlSA6GNYEhg==",
"cpu": [ "cpu": [
"ppc64" "ppc64"
], ],
@@ -901,9 +901,9 @@
} }
}, },
"node_modules/@esbuild/linux-riscv64": { "node_modules/@esbuild/linux-riscv64": {
"version": "0.28.1", "version": "0.28.0",
"resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.28.1.tgz", "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.28.0.tgz",
"integrity": "sha512-kw0owk1o0GFETUJyW0jc0G4Yzs0BHZn0JDZ8JRT088vjJYX777BAs1fDGxAC+q831qOs2DTC96mNsG2opdfyyQ==", "integrity": "sha512-SQPZOwoTTT/HXFXQJG/vBX8sOFagGqvZyXcgLA3NhIqcBv1BJU1d46c0rGcrij2B56Z2rNiSLaZOYW5cUk7yLQ==",
"cpu": [ "cpu": [
"riscv64" "riscv64"
], ],
@@ -918,9 +918,9 @@
} }
}, },
"node_modules/@esbuild/linux-s390x": { "node_modules/@esbuild/linux-s390x": {
"version": "0.28.1", "version": "0.28.0",
"resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.28.1.tgz", "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.28.0.tgz",
"integrity": "sha512-/lAIjX8aYFRByhh6L5rYtPEDRqa9de/4V/juOXcta5frjvzXO4/sqEtyytse0g3zZFuWu5cDN0MkLz2qRDD2Ag==", "integrity": "sha512-SCfR0HN8CEEjnYnySJTd2cw0k9OHB/YFzt5zgJEwa+wL/T/raGWYMBqwDNAC6dqFKmJYZoQBRfHjgwLHGSrn3Q==",
"cpu": [ "cpu": [
"s390x" "s390x"
], ],
@@ -935,9 +935,9 @@
} }
}, },
"node_modules/@esbuild/linux-x64": { "node_modules/@esbuild/linux-x64": {
"version": "0.28.1", "version": "0.28.0",
"resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.28.1.tgz", "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.28.0.tgz",
"integrity": "sha512-u/anNYF2mmVOEDwLtnQ1wOr3EZ9sTNGLWrsYGYwHWzGA3Si84IOkHXlbWTD1NB+9/1lcnweYKO54uhxZydNzfA==", "integrity": "sha512-us0dSb9iFxIi8srnpl931Nvs65it/Jd2a2K3qs7fz2WfGPHqzfzZTfec7oxZJRNPXPnNYZtanmRc4AL/JwVzHQ==",
"cpu": [ "cpu": [
"x64" "x64"
], ],
@@ -952,9 +952,9 @@
} }
}, },
"node_modules/@esbuild/netbsd-arm64": { "node_modules/@esbuild/netbsd-arm64": {
"version": "0.28.1", "version": "0.28.0",
"resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.28.1.tgz", "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.28.0.tgz",
"integrity": "sha512-oks0DYbLwWMmaakTsCb+zL4E+aHRVLom9IJZOAthMQEPiQmydXHkziYEsGYRx0uNV/IjEKGAV941JzH02pflqw==", "integrity": "sha512-CR/RYotgtCKwtftMwJlUU7xCVNg3lMYZ0RzTmAHSfLCXw3NtZtNpswLEj/Kkf6kEL3Gw+BpOekRX0BYCtklhUw==",
"cpu": [ "cpu": [
"arm64" "arm64"
], ],
@@ -969,9 +969,9 @@
} }
}, },
"node_modules/@esbuild/netbsd-x64": { "node_modules/@esbuild/netbsd-x64": {
"version": "0.28.1", "version": "0.28.0",
"resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.28.1.tgz", "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.28.0.tgz",
"integrity": "sha512-aeL6lAnN89Hz43Mlh1G8ARasbuoYvSITDEx0tHh5b7jJnHcssqgjy9Yx430GDpmCa6OyrKoS0aNRjKundRizGg==", "integrity": "sha512-nU1yhmYutL+fQ71Kxnhg8uEOdC0pwEW9entHykTgEbna2pw2dkbFSMeqjjyHZoCmt8SBkOSvV+yNmm94aUrrqw==",
"cpu": [ "cpu": [
"x64" "x64"
], ],
@@ -986,9 +986,9 @@
} }
}, },
"node_modules/@esbuild/openbsd-arm64": { "node_modules/@esbuild/openbsd-arm64": {
"version": "0.28.1", "version": "0.28.0",
"resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.28.1.tgz", "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.28.0.tgz",
"integrity": "sha512-MEFJe5C3R8pwXdZ5Y21oo6m7ePiS0d9pWucn99O/wvyJZChoIQKrQDxKrGeW8F5+T0okTHesAmDeiHDTIq0V/Q==", "integrity": "sha512-cXb5vApOsRsxsEl4mcZ1XY3D4DzcoMxR/nnc4IyqYs0rTI8ZKmW6kyyg+11Z8yvgMfAEldKzP7AdP64HnSC/6g==",
"cpu": [ "cpu": [
"arm64" "arm64"
], ],
@@ -1003,9 +1003,9 @@
} }
}, },
"node_modules/@esbuild/openbsd-x64": { "node_modules/@esbuild/openbsd-x64": {
"version": "0.28.1", "version": "0.28.0",
"resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.28.1.tgz", "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.28.0.tgz",
"integrity": "sha512-i/ZLIOafE0Z8cI/XANJAixoJL/uRAoS2xOA3rb0xN+KK0K177cMAsQYkzHtBrtMXAKuAc7HGgcWiZ/sRC1Nxgw==", "integrity": "sha512-8wZM2qqtv9UP3mzy7HiGYNH/zjTA355mpeuA+859TyR+e+Tc08IHYpLJuMsfpDJwoLo1ikIJI8jC3GFjnRClzA==",
"cpu": [ "cpu": [
"x64" "x64"
], ],
@@ -1020,9 +1020,9 @@
} }
}, },
"node_modules/@esbuild/openharmony-arm64": { "node_modules/@esbuild/openharmony-arm64": {
"version": "0.28.1", "version": "0.28.0",
"resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.28.1.tgz", "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.28.0.tgz",
"integrity": "sha512-ge+Z7EXFNt2BO1oAMsVpiQ8EwndV9i1xXerAeTIK7AtPs3bKFXQM7nlRxDSIUIMeueR1CNXxqztLzdNeReKBJg==", "integrity": "sha512-FLGfyizszcef5C3YtoyQDACyg95+dndv79i2EekILBofh5wpCa1KuBqOWKrEHZg3zrL3t5ouE5jgr94vA+Wb2w==",
"cpu": [ "cpu": [
"arm64" "arm64"
], ],
@@ -1037,9 +1037,9 @@
} }
}, },
"node_modules/@esbuild/sunos-x64": { "node_modules/@esbuild/sunos-x64": {
"version": "0.28.1", "version": "0.28.0",
"resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.28.1.tgz", "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.28.0.tgz",
"integrity": "sha512-BEjgtECkL3vY+SaSQ6nzVfiALUeFxpawyp8Jmf5PtYhf1Ug40N1h/hxlhts+f1FvSvarEigdxS3BlSMI2PJLcQ==", "integrity": "sha512-1ZgjUoEdHZZl/YlV76TSCz9Hqj9h9YmMGAgAPYd+q4SicWNX3G5GCyx9uhQWSLcbvPW8Ni7lj4gDa1T40akdlw==",
"cpu": [ "cpu": [
"x64" "x64"
], ],
@@ -1054,9 +1054,9 @@
} }
}, },
"node_modules/@esbuild/win32-arm64": { "node_modules/@esbuild/win32-arm64": {
"version": "0.28.1", "version": "0.28.0",
"resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.28.1.tgz", "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.28.0.tgz",
"integrity": "sha512-lCv9eK/H6ZJWbE7bh2nw54CZ9M2nupBxJcTsdk/QQnWkdSjKGuxmmH8/GWrlT1eMmZfn4dGcCjRte397WqfQXA==", "integrity": "sha512-Q9StnDmQ/enxnpxCCLSg0oo4+34B9TdXpuyPeTedN/6+iXBJ4J+zwfQI28u/Jl40nOYAxGoNi7mFP40RUtkmUA==",
"cpu": [ "cpu": [
"arm64" "arm64"
], ],
@@ -1071,9 +1071,9 @@
} }
}, },
"node_modules/@esbuild/win32-ia32": { "node_modules/@esbuild/win32-ia32": {
"version": "0.28.1", "version": "0.28.0",
"resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.28.1.tgz", "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.28.0.tgz",
"integrity": "sha512-zvb/mB2bSCoJOpoCBgYKKpX6YM6mJBlBUVUtVj41DlZJVEB6/0CKlRYxP5wWl1C1ILiCoAU5wZZ4q1P3qeS6Eg==", "integrity": "sha512-zF3ag/gfiCe6U2iczcRzSYJKH1DCI+ByzSENHlM2FcDbEeo5Zd2C86Aq0tKUYAJJ1obRP84ymxIAksZUcdztHA==",
"cpu": [ "cpu": [
"ia32" "ia32"
], ],
@@ -1088,9 +1088,9 @@
} }
}, },
"node_modules/@esbuild/win32-x64": { "node_modules/@esbuild/win32-x64": {
"version": "0.28.1", "version": "0.28.0",
"resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.28.1.tgz", "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.28.0.tgz",
"integrity": "sha512-bm4Mowrv+GXMlpWX++EcXw/iLyd1o3+bJkC2DkWXYVvgZCqD/bSj9ctZeAMC3cIxgjRVR2Dufaiu4YPxr5gW1A==", "integrity": "sha512-pEl1bO9mfAmIC+tW5btTmrKaujg3zGtUmWNdCw/xs70FBjwAL3o9OEKNHvNmnyylD6ubxUERiEhdsL0xBQ9efw==",
"cpu": [ "cpu": [
"x64" "x64"
], ],
@@ -3869,9 +3869,9 @@
"license": "MIT" "license": "MIT"
}, },
"node_modules/esbuild": { "node_modules/esbuild": {
"version": "0.28.1", "version": "0.28.0",
"resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.28.1.tgz", "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.28.0.tgz",
"integrity": "sha512-HrJrvZv5ayxBzPfwphOoNzkzOIIlifzk0KJrGK2c8R4+LKpMtpYLQeUdjnwjWv/LZlkH2laZk+4w78pi99D4Vw==", "integrity": "sha512-sNR9MHpXSUV/XB4zmsFKN+QgVG82Cc7+/aaxJ8Adi8hyOac+EXptIp45QBPaVyX3N70664wRbTcLTOemCAnyqw==",
"dev": true, "dev": true,
"hasInstallScript": true, "hasInstallScript": true,
"license": "MIT", "license": "MIT",
@@ -3882,32 +3882,32 @@
"node": ">=18" "node": ">=18"
}, },
"optionalDependencies": { "optionalDependencies": {
"@esbuild/aix-ppc64": "0.28.1", "@esbuild/aix-ppc64": "0.28.0",
"@esbuild/android-arm": "0.28.1", "@esbuild/android-arm": "0.28.0",
"@esbuild/android-arm64": "0.28.1", "@esbuild/android-arm64": "0.28.0",
"@esbuild/android-x64": "0.28.1", "@esbuild/android-x64": "0.28.0",
"@esbuild/darwin-arm64": "0.28.1", "@esbuild/darwin-arm64": "0.28.0",
"@esbuild/darwin-x64": "0.28.1", "@esbuild/darwin-x64": "0.28.0",
"@esbuild/freebsd-arm64": "0.28.1", "@esbuild/freebsd-arm64": "0.28.0",
"@esbuild/freebsd-x64": "0.28.1", "@esbuild/freebsd-x64": "0.28.0",
"@esbuild/linux-arm": "0.28.1", "@esbuild/linux-arm": "0.28.0",
"@esbuild/linux-arm64": "0.28.1", "@esbuild/linux-arm64": "0.28.0",
"@esbuild/linux-ia32": "0.28.1", "@esbuild/linux-ia32": "0.28.0",
"@esbuild/linux-loong64": "0.28.1", "@esbuild/linux-loong64": "0.28.0",
"@esbuild/linux-mips64el": "0.28.1", "@esbuild/linux-mips64el": "0.28.0",
"@esbuild/linux-ppc64": "0.28.1", "@esbuild/linux-ppc64": "0.28.0",
"@esbuild/linux-riscv64": "0.28.1", "@esbuild/linux-riscv64": "0.28.0",
"@esbuild/linux-s390x": "0.28.1", "@esbuild/linux-s390x": "0.28.0",
"@esbuild/linux-x64": "0.28.1", "@esbuild/linux-x64": "0.28.0",
"@esbuild/netbsd-arm64": "0.28.1", "@esbuild/netbsd-arm64": "0.28.0",
"@esbuild/netbsd-x64": "0.28.1", "@esbuild/netbsd-x64": "0.28.0",
"@esbuild/openbsd-arm64": "0.28.1", "@esbuild/openbsd-arm64": "0.28.0",
"@esbuild/openbsd-x64": "0.28.1", "@esbuild/openbsd-x64": "0.28.0",
"@esbuild/openharmony-arm64": "0.28.1", "@esbuild/openharmony-arm64": "0.28.0",
"@esbuild/sunos-x64": "0.28.1", "@esbuild/sunos-x64": "0.28.0",
"@esbuild/win32-arm64": "0.28.1", "@esbuild/win32-arm64": "0.28.0",
"@esbuild/win32-ia32": "0.28.1", "@esbuild/win32-ia32": "0.28.0",
"@esbuild/win32-x64": "0.28.1" "@esbuild/win32-x64": "0.28.0"
} }
}, },
"node_modules/escalade": { "node_modules/escalade": {
+4
View File
@@ -1072,6 +1072,10 @@ export class CoreBrowserTerminal extends CoreTerminal implements ITerminal {
* Clear the entire buffer, making the prompt line the new first line. * Clear the entire buffer, making the prompt line the new first line.
*/ */
public clear(): void { public clear(): void {
if (this.buffer.ybase === 0 && this.buffer.y === 0) {
// Don't clear if it's already clear
return;
}
this.buffer.clearAllMarkers(); this.buffer.clearAllMarkers();
this.buffer.lines.set(0, this.buffer.lines.get(this.buffer.ybase + this.buffer.y)!); this.buffer.lines.set(0, this.buffer.lines.get(this.buffer.ybase + this.buffer.y)!);
this.buffer.lines.length = 1; this.buffer.lines.length = 1;
+4 -14
View File
@@ -536,14 +536,9 @@ export class DomRenderer extends Disposable implements IRenderer {
for (let y = start; y <= end; y++) { for (let y = start; y <= end; y++) {
const row = y + buffer.ydisp; const row = y + buffer.ydisp;
const rowElement = this._rowElements[y]; const rowElement = this._rowElements[y];
if (!rowElement) {
continue;
}
const lineData = buffer.lines.get(row); const lineData = buffer.lines.get(row);
if (!lineData) { if (!rowElement || !lineData) {
rowElement.replaceChildren(); break;
this._setRowBlinkState(y, false);
continue;
} }
rowElement.replaceChildren( rowElement.replaceChildren(
...this._rowFactory.createRow( ...this._rowFactory.createRow(
@@ -615,14 +610,9 @@ export class DomRenderer extends Disposable implements IRenderer {
for (let i = y; i <= y2; ++i) { for (let i = y; i <= y2; ++i) {
const row = i + buffer.ydisp; const row = i + buffer.ydisp;
const rowElement = this._rowElements[i]; const rowElement = this._rowElements[i];
if (!rowElement) {
continue;
}
const bufferline = buffer.lines.get(row); const bufferline = buffer.lines.get(row);
if (!bufferline) { if (!rowElement || !bufferline) {
rowElement.replaceChildren(); break;
this._setRowBlinkState(i, false);
continue;
} }
rowElement.replaceChildren( rowElement.replaceChildren(
...this._rowFactory.createRow( ...this._rowFactory.createRow(
@@ -64,13 +64,6 @@ describe('SelectionModel', () => {
assert.deepEqual(model.finalSelectionStart, undefined); assert.deepEqual(model.finalSelectionStart, undefined);
assert.deepEqual(model.finalSelectionEnd, undefined); assert.deepEqual(model.finalSelectionEnd, undefined);
}); });
it('should reset selection start to origin when start row is trimmed', () => {
model.selectionStart = [50, 0];
model.selectionEnd = [10, 2];
assert.equal(model.handleTrim(1), true);
assert.deepEqual(model.finalSelectionStart, [0, 0]);
assert.deepEqual(model.finalSelectionEnd, [10, 1]);
});
}); });
describe('finalSelectionStart', () => { describe('finalSelectionStart', () => {
+2 -3
View File
@@ -135,10 +135,9 @@ export class SelectionModel {
return true; return true;
} }
// If the selection start row is trimmed away, reset to the buffer origin. // If the selection start is trimmed, ensure the start column is 0.
if (this.selectionStart && this.selectionStart[1] < 0) { if (this.selectionStart && this.selectionStart[1] < 0) {
this.selectionStart = [0, 0]; this.selectionStart[1] = 0;
return true;
} }
return false; return false;
} }
@@ -100,7 +100,6 @@ export class CharacterJoinerService implements ICharacterJoinerService {
const ranges: [number, number][] = []; const ranges: [number, number][] = [];
const lineStr = line.translateToString(true); const lineStr = line.translateToString(true);
const trimmedLength = line.getTrimmedLength();
// Because some cells can be represented by multiple javascript characters, // Because some cells can be represented by multiple javascript characters,
// we track the cell and the string indexes separately. This allows us to // we track the cell and the string indexes separately. This allows us to
@@ -112,7 +111,7 @@ export class CharacterJoinerService implements ICharacterJoinerService {
let rangeAttrFG = line.getFg(0); let rangeAttrFG = line.getFg(0);
let rangeAttrBG = line.getBg(0); let rangeAttrBG = line.getBg(0);
for (let x = 0; x < trimmedLength; x++) { for (let x = 0; x < line.getTrimmedLength(); x++) {
line.loadCell(x, this._workCell); line.loadCell(x, this._workCell);
if (this._workCell.getWidth() === 0) { if (this._workCell.getWidth() === 0) {
@@ -148,7 +147,7 @@ export class CharacterJoinerService implements ICharacterJoinerService {
} }
// Process any trailing ranges. // Process any trailing ranges.
if (trimmedLength - rangeStartColumn > 1) { if (this._bufferService.cols - rangeStartColumn > 1) {
const joinedRanges = this._getJoinedRanges( const joinedRanges = this._getJoinedRanges(
lineStr, lineStr,
rangeStartStringIndex, rangeStartStringIndex,
@@ -217,8 +216,7 @@ export class CharacterJoinerService implements ICharacterJoinerService {
return; return;
} }
const trimmedLength = line.getTrimmedLength(); for (let x = startCol; x < this._bufferService.cols; x++) {
for (let x = startCol; x < trimmedLength; x++) {
const width = line.getWidth(x); const width = line.getWidth(x);
const length = line.getString(x).length || WHITESPACE_CELL_CHAR.length; const length = line.getString(x).length || WHITESPACE_CELL_CHAR.length;
@@ -266,7 +264,7 @@ export class CharacterJoinerService implements ICharacterJoinerService {
// If there is still a range left at the end, it must extend all the way to // If there is still a range left at the end, it must extend all the way to
// the end of the line. // the end of the line.
if (currentRange) { if (currentRange) {
currentRange[1] = trimmedLength; currentRange[1] = this._bufferService.cols;
} }
} }
-93
View File
@@ -351,96 +351,3 @@ describe('MouseService mouseEventsRequireAlt', () => {
assert.isFalse(element.classList.contains(MouseEventCssClasses.ENABLE_MOUSE_EVENTS)); assert.isFalse(element.classList.contains(MouseEventCssClasses.ENABLE_MOUSE_EVENTS));
}); });
}); });
describe('MouseService multi-window', () => {
interface IRecordingDocument {
doc: Document;
added: string[];
removed: string[];
listeners: { [type: string]: EventListener[] };
}
function createRecordingDocument(): IRecordingDocument {
const added: string[] = [];
const removed: string[] = [];
const listeners: { [type: string]: EventListener[] } = {};
const doc = {
addEventListener: (type: string, listener: EventListener) => {
added.push(type);
(listeners[type] ??= []).push(listener);
},
removeEventListener: (type: string) => removed.push(type)
} as any;
return { doc, added, removed, listeners };
}
it('should manage protocol mouseup/mousemove listeners on the element\'s current document, not the document the terminal was opened with', () => {
const mouseStateService = new MouseStateService();
const optionsService = new OptionsService({});
const mouseService = new MouseService(
new MockRenderService(),
{
getMouseReportCoords: () => ({ col: 0, row: 0, x: 0, y: 0 })
} as any,
mouseStateService,
{
triggerDataEvent: () => {},
triggerBinaryEvent: () => {},
decPrivateModes: { applicationCursorKeys: false }
} as any,
bufferService,
optionsService,
new TestSelectionService(),
logService,
new MockCoreBrowserService()
);
// Simulate a terminal that was opened with one document but whose element now lives in
// another document (eg. it was moved to a child window)
const openDocument = createRecordingDocument();
const childDocument = createRecordingDocument();
const elementListeners: { [type: string]: EventListener[] } = {};
const element = {
classList: {
add: () => {},
remove: () => {},
contains: () => false
},
addEventListener: (type: string, listener: EventListener) => {
(elementListeners[type] ??= []).push(listener);
},
removeEventListener: () => {},
ownerDocument: childDocument.doc
} as any;
mouseService.bindMouse({
element,
screenElement: createTestMouseTargetElement(),
document: openDocument.doc
}, disposable => disposable, () => {});
mouseStateService.activeProtocol = 'ANY';
elementListeners['mousedown'][0]({
type: 'mousedown',
button: 0,
buttons: 1,
altKey: false,
ctrlKey: false,
shiftKey: false,
preventDefault: () => {}
} as any);
assert.deepEqual(childDocument.added, ['mouseup', 'mousemove']);
assert.deepEqual(openDocument.added, []);
childDocument.listeners['mouseup'][0]({
type: 'mouseup',
button: 0,
buttons: 0,
altKey: false,
ctrlKey: false,
shiftKey: false
} as any);
assert.deepEqual(childDocument.removed, ['mouseup', 'mousemove']);
assert.deepEqual(openDocument.removed, []);
});
});
+10 -18
View File
@@ -21,7 +21,6 @@ interface IMouseBindContext {
readonly target: IMouseServiceTarget; readonly target: IMouseServiceTarget;
readonly focus: () => void; readonly focus: () => void;
readonly requestedEvents: RequestedMouseEvents; readonly requestedEvents: RequestedMouseEvents;
listenerDocument: Document | null;
} }
export class MouseService implements IMouseService { export class MouseService implements IMouseService {
@@ -62,7 +61,7 @@ export class MouseService implements IMouseService {
mousedrag: null, mousedrag: null,
mousemove: null mousemove: null
}; };
const ctx: IMouseBindContext = { target, focus, requestedEvents, listenerDocument: null }; const ctx: IMouseBindContext = { target, focus, requestedEvents };
const eventListeners: Record<'mouseup' | 'wheel' | 'mousedrag' | 'mousemove', EventListener> = { const eventListeners: Record<'mouseup' | 'wheel' | 'mousedrag' | 'mousemove', EventListener> = {
mouseup: (ev: Event) => this._handleMouseUp(ctx, ev as MouseEvent), mouseup: (ev: Event) => this._handleMouseUp(ctx, ev as MouseEvent),
wheel: (ev: Event) => this._handleWheel(ctx, ev as WheelEvent), wheel: (ev: Event) => this._handleWheel(ctx, ev as WheelEvent),
@@ -88,12 +87,11 @@ export class MouseService implements IMouseService {
// Ensure document-level listeners are removed on dispose // Ensure document-level listeners are removed on dispose
register(toDisposable(() => { register(toDisposable(() => {
const listenerDocument = ctx.listenerDocument ?? document;
if (requestedEvents.mouseup) { if (requestedEvents.mouseup) {
listenerDocument.removeEventListener('mouseup', requestedEvents.mouseup); document.removeEventListener('mouseup', requestedEvents.mouseup);
} }
if (requestedEvents.mousedrag) { if (requestedEvents.mousedrag) {
listenerDocument.removeEventListener('mousemove', requestedEvents.mousedrag); document.removeEventListener('mousemove', requestedEvents.mousedrag);
} }
})); }));
@@ -201,14 +199,12 @@ export class MouseService implements IMouseService {
this._sendEvent(ctx, ev); this._sendEvent(ctx, ev);
if (!ev.buttons) { if (!ev.buttons) {
// if no other button is held remove global handlers // if no other button is held remove global handlers
const listenerDocument = ctx.listenerDocument ?? ctx.target.document;
if (ctx.requestedEvents.mouseup) { if (ctx.requestedEvents.mouseup) {
listenerDocument.removeEventListener('mouseup', ctx.requestedEvents.mouseup); ctx.target.document.removeEventListener('mouseup', ctx.requestedEvents.mouseup);
} }
if (ctx.requestedEvents.mousedrag) { if (ctx.requestedEvents.mousedrag) {
listenerDocument.removeEventListener('mousemove', ctx.requestedEvents.mousedrag); ctx.target.document.removeEventListener('mousemove', ctx.requestedEvents.mousedrag);
} }
ctx.listenerDocument = null;
} }
} }
@@ -250,15 +246,12 @@ export class MouseService implements IMouseService {
// of the terminal element. // of the terminal element.
// Note: Other emulators also do this for 'mousedown' while a button // Note: Other emulators also do this for 'mousedown' while a button
// is held, we currently limit 'mousedown' to the terminal only. // is held, we currently limit 'mousedown' to the terminal only.
// Use the element's current document in case it moved to another window after open.
const listenerDocument = ctx.target.element.ownerDocument ?? ctx.target.document;
if (ctx.requestedEvents.mouseup) { if (ctx.requestedEvents.mouseup) {
listenerDocument.addEventListener('mouseup', ctx.requestedEvents.mouseup); ctx.target.document.addEventListener('mouseup', ctx.requestedEvents.mouseup);
} }
if (ctx.requestedEvents.mousedrag) { if (ctx.requestedEvents.mousedrag) {
listenerDocument.addEventListener('mousemove', ctx.requestedEvents.mousedrag); ctx.target.document.addEventListener('mousemove', ctx.requestedEvents.mousedrag);
} }
ctx.listenerDocument = listenerDocument;
} }
private _handlePassiveWheel(ctx: IMouseBindContext, ev: WheelEvent): false | void { private _handlePassiveWheel(ctx: IMouseBindContext, ev: WheelEvent): false | void {
@@ -405,9 +398,8 @@ export class MouseService implements IMouseService {
} }
private _handleProtocolChange(ctx: IMouseBindContext, eventListeners: Record<'mouseup' | 'wheel' | 'mousedrag' | 'mousemove', EventListener>, events: CoreMouseEventType): void { private _handleProtocolChange(ctx: IMouseBindContext, eventListeners: Record<'mouseup' | 'wheel' | 'mousedrag' | 'mousemove', EventListener>, events: CoreMouseEventType): void {
const { element } = ctx.target; const { element, document } = ctx.target;
const { requestedEvents } = ctx; const { requestedEvents } = ctx;
const listenerDocument = ctx.listenerDocument ?? ctx.target.document;
// apply global changes on events // apply global changes on events
if (events) { if (events) {
if (this._optionsService.rawOptions.logLevel === 'debug') { if (this._optionsService.rawOptions.logLevel === 'debug') {
@@ -442,7 +434,7 @@ export class MouseService implements IMouseService {
if (!(events & CoreMouseEventType.UP)) { if (!(events & CoreMouseEventType.UP)) {
if (requestedEvents.mouseup) { if (requestedEvents.mouseup) {
listenerDocument.removeEventListener('mouseup', requestedEvents.mouseup); document.removeEventListener('mouseup', requestedEvents.mouseup);
} }
requestedEvents.mouseup = null; requestedEvents.mouseup = null;
} else { } else {
@@ -451,7 +443,7 @@ export class MouseService implements IMouseService {
if (!(events & CoreMouseEventType.DRAG)) { if (!(events & CoreMouseEventType.DRAG)) {
if (requestedEvents.mousedrag) { if (requestedEvents.mousedrag) {
listenerDocument.removeEventListener('mousemove', requestedEvents.mousedrag); document.removeEventListener('mousemove', requestedEvents.mousedrag);
} }
requestedEvents.mousedrag = null; requestedEvents.mousedrag = null;
} else { } else {
+1 -1
View File
@@ -704,7 +704,7 @@ export class SelectionService extends Disposable implements ISelectionService {
if (this._activeSelectionMode !== SelectionMode.COLUMN) { if (this._activeSelectionMode !== SelectionMode.COLUMN) {
this._model.selectionEnd[0] = this._bufferService.cols; this._model.selectionEnd[0] = this._bufferService.cols;
} }
this._model.selectionEnd[1] = Math.min(buffer.ydisp + this._bufferService.rows - 1, buffer.lines.length - 1); this._model.selectionEnd[1] = Math.min(buffer.ydisp + this._bufferService.rows, buffer.lines.length - 1);
} else { } else {
if (this._activeSelectionMode !== SelectionMode.COLUMN) { if (this._activeSelectionMode !== SelectionMode.COLUMN) {
this._model.selectionEnd[0] = 0; this._model.selectionEnd[0] = 0;
+19 -45
View File
@@ -79,7 +79,7 @@ function assertDecodedRange(
} }
const target = new Uint32Array(count); const target = new Uint32Array(count);
const length = decode(input, target); const length = decode(input, target);
assert.strictEqual(length, count); assert.equal(length, count);
let mismatchIndex = -1; let mismatchIndex = -1;
let index = 0; let index = 0;
for (let i = min; i < max; ++i) { for (let i = min; i < max; ++i) {
@@ -92,8 +92,8 @@ function assertDecodedRange(
} }
index++; index++;
} }
assert.strictEqual(mismatchIndex, -1); assert.equal(mismatchIndex, -1);
assert.strictEqual(outputToString(target, length), input); assert.equal(outputToString(target, length), input);
} }
const BATCH_SIZE = 8192; const BATCH_SIZE = 8192;
@@ -116,9 +116,9 @@ describe('text encodings', () => {
const data = new Uint32Array(s.length); const data = new Uint32Array(s.length);
for (let i = 0; i < s.length; ++i) { for (let i = 0; i < s.length; ++i) {
data[i] = s.charCodeAt(i); data[i] = s.charCodeAt(i);
assert.strictEqual(stringFromCodePoint(data[i]), s[i]); assert.equal(stringFromCodePoint(data[i]), s[i]);
} }
assert.strictEqual(utf32ToString(data), s); assert.equal(utf32ToString(data), s);
}); });
describe('StringToUtf32 decoder', () => { describe('StringToUtf32 decoder', () => {
@@ -159,7 +159,7 @@ describe('text encodings', () => {
const decoder = new StringToUtf32(); const decoder = new StringToUtf32();
const target = new Uint32Array(5); const target = new Uint32Array(5);
const length = decoder.decode(String.fromCharCode(0xFEFF), target); const length = decoder.decode(String.fromCharCode(0xFEFF), target);
assert.strictEqual(length, 0); assert.equal(length, 0);
decoder.clear(); decoder.clear();
}); });
}); });
@@ -169,7 +169,7 @@ describe('text encodings', () => {
const target = new Uint32Array(500); const target = new Uint32Array(500);
for (let i = 0; i < TEST_STRINGS.length; ++i) { for (let i = 0; i < TEST_STRINGS.length; ++i) {
const length = decoder.decode(TEST_STRINGS[i], target); const length = decoder.decode(TEST_STRINGS[i], target);
assert.strictEqual(toString(target, length), TEST_STRINGS[i]); assert.equal(toString(target, length), TEST_STRINGS[i]);
decoder.clear(); decoder.clear();
} }
}); });
@@ -184,7 +184,7 @@ describe('text encodings', () => {
const written = decoder.decode(input[i], target); const written = decoder.decode(input[i], target);
decoded += toString(target, written); decoded += toString(target, written);
} }
assert.strictEqual(decoded, 'Ä€𝄞Ö𝄞€Ü𝄞€'); assert(decoded, 'Ä€𝄞Ö𝄞€Ü𝄞€');
}); });
}); });
}); });
@@ -226,7 +226,7 @@ describe('text encodings', () => {
const target = new Uint32Array(5); const target = new Uint32Array(5);
const utf8Data = stringToUtf8Bytes(String.fromCharCode(0xFEFF)); const utf8Data = stringToUtf8Bytes(String.fromCharCode(0xFEFF));
const length = decoder.decode(utf8Data, target); const length = decoder.decode(utf8Data, target);
assert.strictEqual(length, 0); assert.equal(length, 0);
decoder.clear(); decoder.clear();
}); });
}); });
@@ -237,7 +237,7 @@ describe('text encodings', () => {
for (let i = 0; i < TEST_STRINGS.length; ++i) { for (let i = 0; i < TEST_STRINGS.length; ++i) {
const utf8Data = stringToUtf8Bytes(TEST_STRINGS[i]); const utf8Data = stringToUtf8Bytes(TEST_STRINGS[i]);
const length = decoder.decode(utf8Data, target); const length = decoder.decode(utf8Data, target);
assert.strictEqual(toString(target, length), TEST_STRINGS[i]); assert.equal(toString(target, length), TEST_STRINGS[i]);
decoder.clear(); decoder.clear();
} }
}); });
@@ -252,7 +252,7 @@ describe('text encodings', () => {
const written = decoder.decode(utf8Data.slice(i, i + 1), target); const written = decoder.decode(utf8Data.slice(i, i + 1), target);
decoded += toString(target, written); decoded += toString(target, written);
} }
assert.strictEqual(decoded, 'ÄÖÜßöäü'); assert(decoded, 'ÄÖÜßöäü');
}); });
it('2/3 byte sequences - advance by 1', () => { it('2/3 byte sequences - advance by 1', () => {
@@ -264,7 +264,7 @@ describe('text encodings', () => {
const written = decoder.decode(utf8Data.slice(i, i + 1), target); const written = decoder.decode(utf8Data.slice(i, i + 1), target);
decoded += toString(target, written); decoded += toString(target, written);
} }
assert.strictEqual(decoded, 'Āր܀߀ö€ä€ü'); assert(decoded, 'Āր܀߀ö€ä€ü');
}); });
it('2/3/4 byte sequences - advance by 1', () => { it('2/3/4 byte sequences - advance by 1', () => {
@@ -276,7 +276,7 @@ describe('text encodings', () => {
const written = decoder.decode(utf8Data.slice(i, i + 1), target); const written = decoder.decode(utf8Data.slice(i, i + 1), target);
decoded += toString(target, written); decoded += toString(target, written);
} }
assert.strictEqual(decoded, 'Ä€𝄞Ö𝄞€Ü𝄞€'); assert(decoded, 'Ä€𝄞Ö𝄞€Ü𝄞€');
}); });
it('2/3/4 byte sequences - advance by 2', () => { it('2/3/4 byte sequences - advance by 2', () => {
@@ -288,7 +288,7 @@ describe('text encodings', () => {
const written = decoder.decode(utf8Data.slice(i, i + 2), target); const written = decoder.decode(utf8Data.slice(i, i + 2), target);
decoded += toString(target, written); decoded += toString(target, written);
} }
assert.strictEqual(decoded, 'Ä€𝄞Ö𝄞€Ü𝄞€'); assert(decoded, 'Ä€𝄞Ö𝄞€Ü𝄞€');
}); });
it('2/3/4 byte sequences - advance by 3', () => { it('2/3/4 byte sequences - advance by 3', () => {
@@ -300,7 +300,7 @@ describe('text encodings', () => {
const written = decoder.decode(utf8Data.slice(i, i + 3), target); const written = decoder.decode(utf8Data.slice(i, i + 3), target);
decoded += toString(target, written); decoded += toString(target, written);
} }
assert.strictEqual(decoded, 'Ä€𝄞Ö𝄞€Ü𝄞€'); assert(decoded, 'Ä€𝄞Ö𝄞€Ü𝄞€');
}); });
it('BOMs (3 byte sequences) - advance by 2', () => { it('BOMs (3 byte sequences) - advance by 2', () => {
@@ -312,7 +312,7 @@ describe('text encodings', () => {
const written = decoder.decode(utf8Data.slice(i, i + 2), target); const written = decoder.decode(utf8Data.slice(i, i + 2), target);
decoded += toString(target, written); decoded += toString(target, written);
} }
assert.strictEqual(decoded, ''); assert.equal(decoded, '');
}); });
it('test break after 3 bytes - issue #2495', () => { it('test break after 3 bytes - issue #2495', () => {
@@ -320,37 +320,11 @@ describe('text encodings', () => {
const target = new Uint32Array(5); const target = new Uint32Array(5);
const utf8Data = fromByteString('\xf0\xa0\x9c\x8e'); const utf8Data = fromByteString('\xf0\xa0\x9c\x8e');
let written = decoder.decode(utf8Data.slice(0, 3), target); let written = decoder.decode(utf8Data.slice(0, 3), target);
assert.strictEqual(written, 0); assert.equal(written, 0);
written = decoder.decode(utf8Data.slice(3), target); written = decoder.decode(utf8Data.slice(3), target);
assert.strictEqual(written, 1); assert.equal(written, 1);
assert.strictEqual(toString(target, written), '𠜎'); assert(toString(target, written), '𠜎');
}); });
describe('0x80 not swallowed in continuation', () => {
it('A—B', () => {
const decoder = new Utf8ToUtf32();
const target = new Uint32Array(5);
const utf8Data = new TextEncoder().encode('A—BA—BA—BA—BA—B');
let decoded = '';
for (let i = 0; i < utf8Data.length; i += 2) {
const written = decoder.decode(utf8Data.slice(i, i + 2), target);
decoded += toString(target, written);
}
assert.strictEqual(decoded, 'A—BA—BA—BA—BA—B');
});
it('A𐀀B', () => {
const decoder = new Utf8ToUtf32();
const target = new Uint32Array(5);
const utf8Data = new TextEncoder().encode('A𐀀BA𐀀BA𐀀BA𐀀BA𐀀B');
let decoded = '';
for (let i = 0; i < utf8Data.length; i += 2) {
const written = decoder.decode(utf8Data.slice(i, i + 2), target);
decoded += toString(target, written);
}
assert.strictEqual(decoded, 'A𐀀BA𐀀BA𐀀BA𐀀BA𐀀B');
});
});
}); });
}); });
}); });
+2 -2
View File
@@ -158,9 +158,9 @@ export class Utf8ToUtf32 {
cp &= ((((cp & 0xE0) === 0xC0)) ? 0x1F : (((cp & 0xF0) === 0xE0)) ? 0x0F : 0x07); cp &= ((((cp & 0xE0) === 0xC0)) ? 0x1F : (((cp & 0xF0) === 0xE0)) ? 0x0F : 0x07);
let pos = 0; let pos = 0;
let tmp: number; let tmp: number;
while ((tmp = this.interim[++pos]) && pos < 4) { while ((tmp = this.interim[++pos] & 0x3F) && pos < 4) {
cp <<= 6; cp <<= 6;
cp |= tmp & 0x3F; cp |= tmp;
} }
// missing bytes - read ahead from input // missing bytes - read ahead from input
const type = (((this.interim[0] & 0xE0) === 0xC0)) ? 2 : (((this.interim[0] & 0xF0) === 0xE0)) ? 3 : 4; const type = (((this.interim[0] & 0xE0) === 0xC0)) ? 2 : (((this.interim[0] & 0xF0) === 0xE0)) ? 3 : 4;
+4
View File
@@ -99,6 +99,10 @@ export class Terminal extends CoreTerminal {
* Clear the entire buffer, making the prompt line the new first line. * Clear the entire buffer, making the prompt line the new first line.
*/ */
public clear(): void { public clear(): void {
if (this.buffer.ybase === 0 && this.buffer.y === 0) {
// Don't clear if it's already clear
return;
}
this.buffer.clearAllMarkers(); this.buffer.clearAllMarkers();
this.buffer.lines.set(0, this.buffer.lines.get(this.buffer.ybase + this.buffer.y)!); this.buffer.lines.set(0, this.buffer.lines.get(this.buffer.ybase + this.buffer.y)!);
this.buffer.lines.length = 1; this.buffer.lines.length = 1;