Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
467eb5a227 | ||
|
|
a8632435a6 | ||
|
|
a0919b9004 | ||
|
|
b5e0cdb20c |
+27
-19
@@ -11,9 +11,9 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 10
|
||||
steps:
|
||||
- uses: actions/checkout@v5
|
||||
- uses: actions/checkout@v3
|
||||
- name: Use Node.js 22.x
|
||||
uses: actions/setup-node@v5
|
||||
uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: 22.x
|
||||
cache: 'npm'
|
||||
@@ -69,7 +69,7 @@ jobs:
|
||||
./addons/addon-webgl/out/* \
|
||||
./addons/addon-webgl/out-*st/*
|
||||
- name: Upload artifacts
|
||||
uses: actions/upload-artifact@v6
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: build-artifacts
|
||||
path: compressed-build.zip
|
||||
@@ -79,9 +79,9 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 10
|
||||
steps:
|
||||
- uses: actions/checkout@v5
|
||||
- uses: actions/checkout@v3
|
||||
- name: Use Node.js 22.x
|
||||
uses: actions/setup-node@v5
|
||||
uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: 22.x
|
||||
cache: 'npm'
|
||||
@@ -100,16 +100,16 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 10
|
||||
steps:
|
||||
- uses: actions/checkout@v5
|
||||
- uses: actions/checkout@v3
|
||||
- name: Use Node.js 22.x
|
||||
uses: actions/setup-node@v5
|
||||
uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: 22.x
|
||||
cache: 'npm'
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
npm ci
|
||||
- uses: actions/download-artifact@v7
|
||||
- uses: actions/download-artifact@v4
|
||||
with:
|
||||
name: build-artifacts
|
||||
- name: Unzip artifacts
|
||||
@@ -129,7 +129,6 @@ jobs:
|
||||
exit $EXIT_CODE
|
||||
|
||||
test-unit:
|
||||
needs: build
|
||||
timeout-minutes: 20
|
||||
strategy:
|
||||
matrix:
|
||||
@@ -137,16 +136,21 @@ jobs:
|
||||
runs-on: [ubuntu, macos, windows]
|
||||
runs-on: ${{ matrix.runs-on }}-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v5
|
||||
- uses: actions/checkout@v3
|
||||
- name: Use Node.js ${{ matrix.node-version }}.x
|
||||
uses: actions/setup-node@v5
|
||||
uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: ${{ matrix.node-version }}.x
|
||||
cache: 'npm'
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
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:
|
||||
name: build-artifacts
|
||||
- name: Unzip artifacts
|
||||
@@ -162,7 +166,6 @@ jobs:
|
||||
run: npm run test-unit --forbid-only
|
||||
|
||||
test-integration:
|
||||
needs: build
|
||||
timeout-minutes: 20
|
||||
strategy:
|
||||
matrix:
|
||||
@@ -171,9 +174,9 @@ jobs:
|
||||
browser: [chromium, firefox, webkit]
|
||||
runs-on: ${{ matrix.runs-on }}
|
||||
steps:
|
||||
- uses: actions/checkout@v5
|
||||
- uses: actions/checkout@v3
|
||||
- name: Use Node.js ${{ matrix.node-version }}.x
|
||||
uses: actions/setup-node@v5
|
||||
uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: ${{ matrix.node-version }}.x
|
||||
cache: 'npm'
|
||||
@@ -182,7 +185,12 @@ jobs:
|
||||
npm ci
|
||||
- name: Install playwright
|
||||
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:
|
||||
name: build-artifacts
|
||||
- name: Unzip artifacts
|
||||
@@ -232,9 +240,9 @@ jobs:
|
||||
matrix:
|
||||
node-version: [22]
|
||||
steps:
|
||||
- uses: actions/checkout@v5
|
||||
- uses: actions/checkout@v3
|
||||
- name: Use Node.js ${{ matrix.node-version }}.x
|
||||
uses: actions/setup-node@v5
|
||||
uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: ${{ matrix.node-version }}.x
|
||||
cache: 'npm'
|
||||
@@ -243,7 +251,7 @@ jobs:
|
||||
npm ci
|
||||
- name: Install playwright
|
||||
run: npx playwright install
|
||||
- uses: actions/download-artifact@v7
|
||||
- uses: actions/download-artifact@v4
|
||||
with:
|
||||
name: build-artifacts
|
||||
- name: Unzip artifacts
|
||||
|
||||
@@ -24,12 +24,12 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v5
|
||||
uses: actions/checkout@v3
|
||||
- name: Initialize CodeQL
|
||||
uses: github/codeql-action/init@v4
|
||||
uses: github/codeql-action/init@v2
|
||||
with:
|
||||
languages: ${{ matrix.language }}
|
||||
- name: Autobuild
|
||||
uses: github/codeql-action/autobuild@v4
|
||||
uses: github/codeql-action/autobuild@v2
|
||||
- name: Perform CodeQL Analysis
|
||||
uses: github/codeql-action/analyze@v4
|
||||
uses: github/codeql-action/analyze@v2
|
||||
|
||||
@@ -18,9 +18,9 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v5
|
||||
uses: actions/checkout@v4
|
||||
- name: Use Node.js 22.x
|
||||
uses: actions/setup-node@v5
|
||||
uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: 22.x
|
||||
cache: 'npm'
|
||||
|
||||
@@ -10,9 +10,9 @@ jobs:
|
||||
release:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v5
|
||||
- uses: actions/checkout@v3
|
||||
- name: Use Node.js 22.x
|
||||
uses: actions/setup-node@v5
|
||||
uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: 22.x
|
||||
cache: 'npm'
|
||||
|
||||
@@ -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 {
|
||||
public encodeText(data: string): string {
|
||||
const bytes = new TextEncoder().encode(data) as IUint8ArrayB64;
|
||||
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);
|
||||
return btoa(data);
|
||||
}
|
||||
public decodeText(data: string): string {
|
||||
if ((Uint8Array as IUint8ArrayB64Ctor).fromBase64 !== undefined) {
|
||||
try {
|
||||
return new TextDecoder().decode((Uint8Array as IUint8ArrayB64Ctor).fromBase64(data));
|
||||
} catch {}
|
||||
return '';
|
||||
}
|
||||
try {
|
||||
const bin = 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);
|
||||
return atob(data);
|
||||
} catch {}
|
||||
return '';
|
||||
}
|
||||
|
||||
@@ -29,7 +29,7 @@ test.describe('ClipboardAddon', () => {
|
||||
await ctx.page.context().grantPermissions(['clipboard-read', 'clipboard-write']);
|
||||
}
|
||||
await ctx.page.evaluate(`
|
||||
window.term.reset();
|
||||
window.term.reset()
|
||||
window.clipboard?.dispose();
|
||||
window.clipboard = new ClipboardAddon();
|
||||
window.term.loadAddon(window.clipboard);
|
||||
@@ -92,22 +92,4 @@ test.describe('ClipboardAddon', () => {
|
||||
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`]);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -431,14 +431,14 @@ export class ImageStorage implements IDisposable {
|
||||
// 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) as IBufferLineExt;
|
||||
if (!line) return;
|
||||
const line = buffer.lines.get(row + buffer.ydisp) as IBufferLineExt | undefined;
|
||||
if (!line) continue;
|
||||
for (let col = 0; col < cols; ++col) {
|
||||
let e: IExtendedAttrsImage;
|
||||
if (line.getBg(col) & BgFlags.HAS_EXTENDED) {
|
||||
e = line._extendedAttrs?.[col] ?? EMPTY_ATTRS;
|
||||
e = line._extendedAttrs[col] ?? EMPTY_ATTRS;
|
||||
} else {
|
||||
const maybeImg = line._extendedAttrs?.[col] as IExtendedAttrsImage | undefined;
|
||||
const maybeImg = line._extendedAttrs[col] as IExtendedAttrsImage | undefined;
|
||||
if (!maybeImg || maybeImg.imageId === undefined || maybeImg.imageId === -1) {
|
||||
continue;
|
||||
}
|
||||
@@ -461,7 +461,7 @@ export class ImageStorage implements IDisposable {
|
||||
* Also check _extendedAttrs directly for cells where text cleared HAS_EXTENDED.
|
||||
*/
|
||||
while (++col < cols) {
|
||||
const nextE = line._extendedAttrs?.[col] as IExtendedAttrsImage | undefined;
|
||||
const nextE = line._extendedAttrs[col] as IExtendedAttrsImage | undefined;
|
||||
if (!nextE || nextE.imageId !== imageId || nextE.tileId !== startTile + count) {
|
||||
break;
|
||||
}
|
||||
@@ -513,9 +513,12 @@ 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) as IBufferLineExt;
|
||||
const line = buffer.lines.get(row) as IBufferLineExt | undefined;
|
||||
if (!line) {
|
||||
continue;
|
||||
}
|
||||
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;
|
||||
if (imageId === undefined || imageId === -1) {
|
||||
continue;
|
||||
@@ -531,7 +534,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) {
|
||||
for (let rightCol = oldCol + 1; rightCol < metrics.cols; ++rightCol) {
|
||||
if (line._data[rightCol * Cell.SIZE + Cell.CONTENT] & Content.HAS_CONTENT_MASK) {
|
||||
hasData = true;
|
||||
break;
|
||||
@@ -560,7 +563,7 @@ export class ImageStorage implements IDisposable {
|
||||
const buffer = this._terminal._core.buffer;
|
||||
const line = buffer.lines.get(y) as IBufferLineExt;
|
||||
if (line && line.getBg(x) & BgFlags.HAS_EXTENDED) {
|
||||
const e: IExtendedAttrsImage = line._extendedAttrs?.[x] ?? EMPTY_ATTRS;
|
||||
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) {
|
||||
@@ -580,7 +583,7 @@ export class ImageStorage implements IDisposable {
|
||||
const buffer = this._terminal._core.buffer;
|
||||
const line = buffer.lines.get(y) as IBufferLineExt;
|
||||
if (line && line.getBg(x) & BgFlags.HAS_EXTENDED) {
|
||||
const e: IExtendedAttrsImage = line._extendedAttrs?.[x] ?? EMPTY_ATTRS;
|
||||
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) {
|
||||
@@ -611,7 +614,7 @@ export class ImageStorage implements IDisposable {
|
||||
|
||||
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];
|
||||
const old = line._extendedAttrs[x];
|
||||
if (old) {
|
||||
if (old.imageId !== undefined) {
|
||||
// found an old ExtendedAttrsImage, since we know that
|
||||
@@ -627,13 +630,13 @@ export class ImageStorage implements IDisposable {
|
||||
return;
|
||||
}
|
||||
// found a plain ExtendedAttrs instance, clone it to new entry
|
||||
(line._extendedAttrs ??= {})[x] = new ExtendedAttrsImage(old.ext, old.urlId, imageId, tileId);
|
||||
line._extendedAttrs[x] = new ExtendedAttrsImage(old.ext, old.urlId, imageId, tileId);
|
||||
return;
|
||||
}
|
||||
}
|
||||
// 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);
|
||||
line._extendedAttrs[x] = new ExtendedAttrsImage(0, 0, imageId, tileId);
|
||||
}
|
||||
|
||||
private _evictOnAlternate(): void {
|
||||
@@ -652,7 +655,7 @@ export class ImageStorage implements IDisposable {
|
||||
}
|
||||
for (let x = 0; x < this._terminal.cols; ++x) {
|
||||
if (line._data[x * Cell.SIZE + Cell.BG] & BgFlags.HAS_EXTENDED) {
|
||||
const imgId = line._extendedAttrs?.[x]?.imageId;
|
||||
const imgId = line._extendedAttrs[x]?.imageId;
|
||||
if (imgId) {
|
||||
const spec = this._images.get(imgId);
|
||||
if (spec) {
|
||||
|
||||
@@ -64,7 +64,7 @@ export interface IExtendedAttrsImage extends IExtendedAttrs {
|
||||
|
||||
/* eslint-disable */
|
||||
export interface IBufferLineExt extends IBufferLine {
|
||||
_extendedAttrs: {[index: number]: IExtendedAttrsImage | undefined} | undefined;
|
||||
_extendedAttrs: {[index: number]: IExtendedAttrsImage | undefined};
|
||||
_data: Uint32Array;
|
||||
}
|
||||
|
||||
|
||||
@@ -118,10 +118,7 @@ test.describe('ImageAddon', () => {
|
||||
}
|
||||
// bufferline privates
|
||||
strictEqual(await ctx.page.evaluate('window.term._core.buffer.lines.get(0)._data instanceof Uint32Array'), true);
|
||||
strictEqual(await ctx.page.evaluate(`(() => {
|
||||
const ea = window.term._core.buffer.lines.get(0)._extendedAttrs;
|
||||
return ea === undefined || (typeof ea === 'object' && ea !== null);
|
||||
})()`), true);
|
||||
strictEqual(await ctx.page.evaluate('window.term._core.buffer.lines.get(0)._extendedAttrs instanceof Object'), true);
|
||||
// inputhandler privates
|
||||
strictEqual(await ctx.page.evaluate('window.term._core._inputHandler._curAttrData.constructor.name'), '_AttributeData');
|
||||
strictEqual(await ctx.page.evaluate('window.term._core._inputHandler._parser.constructor.name'), 'EscapeSequenceParser');
|
||||
|
||||
@@ -389,9 +389,8 @@ export class GlyphRenderer extends Disposable {
|
||||
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_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.generateMipmap(gl.TEXTURE_2D);
|
||||
this._atlasTextures[i].version = atlas.pages[i].version;
|
||||
}
|
||||
|
||||
|
||||
@@ -1080,33 +1080,17 @@ class AtlasPage {
|
||||
size: number,
|
||||
sourcePages?: AtlasPage[]
|
||||
) {
|
||||
if (sourcePages) {
|
||||
for (const p of sourcePages) {
|
||||
this._glyphs.push(...p.glyphs);
|
||||
this._usedPixels += p._usedPixels;
|
||||
}
|
||||
}
|
||||
this.canvas = createCanvas(document, size, size);
|
||||
// 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
|
||||
// set.
|
||||
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 {
|
||||
|
||||
@@ -456,18 +456,7 @@ export class WebglRenderer extends Disposable implements IRenderer {
|
||||
|
||||
for (y = start; y <= end; y++) {
|
||||
row = y + terminal.buffer.ydisp;
|
||||
const bufferLine = 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;
|
||||
line = terminal.buffer.lines.get(row)!;
|
||||
let rowHasBlinkingCells = false;
|
||||
this._model.lineLengths[y] = 0;
|
||||
isCursorRow = cursorY === row;
|
||||
@@ -598,9 +587,13 @@ export class WebglRenderer extends Disposable implements IRenderer {
|
||||
// Null out non-first cells
|
||||
for (x++; x <= lastCharX; x++) {
|
||||
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
|
||||
// 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
|
||||
}
|
||||
@@ -614,14 +607,6 @@ export class WebglRenderer extends Disposable implements IRenderer {
|
||||
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 {
|
||||
this._rowHasBlinkingCells = new Array(this._terminal.rows).fill(false);
|
||||
this._rowHasBlinkingCellsCount = 0;
|
||||
|
||||
@@ -11,33 +11,8 @@ export class WebglWindow extends BaseWindow implements IControlWindow {
|
||||
public readonly label = 'webgl';
|
||||
|
||||
private _textureAtlasContainer!: HTMLElement;
|
||||
private _stressRunning = false;
|
||||
private _stressScrolling: HTMLInputElement | undefined;
|
||||
|
||||
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');
|
||||
zoomCheckbox.type = 'checkbox';
|
||||
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.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 = [];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1072,6 +1072,10 @@ export class CoreBrowserTerminal extends CoreTerminal implements ITerminal {
|
||||
* Clear the entire buffer, making the prompt line the new first line.
|
||||
*/
|
||||
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.lines.set(0, this.buffer.lines.get(this.buffer.ybase + this.buffer.y)!);
|
||||
this.buffer.lines.length = 1;
|
||||
|
||||
@@ -536,14 +536,9 @@ export class DomRenderer extends Disposable implements IRenderer {
|
||||
for (let y = start; y <= end; y++) {
|
||||
const row = y + buffer.ydisp;
|
||||
const rowElement = this._rowElements[y];
|
||||
if (!rowElement) {
|
||||
continue;
|
||||
}
|
||||
const lineData = buffer.lines.get(row);
|
||||
if (!lineData) {
|
||||
rowElement.replaceChildren();
|
||||
this._setRowBlinkState(y, false);
|
||||
continue;
|
||||
if (!rowElement || !lineData) {
|
||||
break;
|
||||
}
|
||||
rowElement.replaceChildren(
|
||||
...this._rowFactory.createRow(
|
||||
@@ -615,14 +610,9 @@ export class DomRenderer extends Disposable implements IRenderer {
|
||||
for (let i = y; i <= y2; ++i) {
|
||||
const row = i + buffer.ydisp;
|
||||
const rowElement = this._rowElements[i];
|
||||
if (!rowElement) {
|
||||
continue;
|
||||
}
|
||||
const bufferline = buffer.lines.get(row);
|
||||
if (!bufferline) {
|
||||
rowElement.replaceChildren();
|
||||
this._setRowBlinkState(i, false);
|
||||
continue;
|
||||
if (!rowElement || !bufferline) {
|
||||
break;
|
||||
}
|
||||
rowElement.replaceChildren(
|
||||
...this._rowFactory.createRow(
|
||||
|
||||
@@ -64,13 +64,6 @@ describe('SelectionModel', () => {
|
||||
assert.deepEqual(model.finalSelectionStart, 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', () => {
|
||||
|
||||
@@ -135,10 +135,9 @@ export class SelectionModel {
|
||||
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) {
|
||||
this.selectionStart = [0, 0];
|
||||
return true;
|
||||
this.selectionStart[1] = 0;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -100,7 +100,6 @@ export class CharacterJoinerService implements ICharacterJoinerService {
|
||||
|
||||
const ranges: [number, number][] = [];
|
||||
const lineStr = line.translateToString(true);
|
||||
const trimmedLength = line.getTrimmedLength();
|
||||
|
||||
// Because some cells can be represented by multiple javascript characters,
|
||||
// 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 rangeAttrBG = line.getBg(0);
|
||||
|
||||
for (let x = 0; x < trimmedLength; x++) {
|
||||
for (let x = 0; x < line.getTrimmedLength(); x++) {
|
||||
line.loadCell(x, this._workCell);
|
||||
|
||||
if (this._workCell.getWidth() === 0) {
|
||||
@@ -148,7 +147,7 @@ export class CharacterJoinerService implements ICharacterJoinerService {
|
||||
}
|
||||
|
||||
// Process any trailing ranges.
|
||||
if (trimmedLength - rangeStartColumn > 1) {
|
||||
if (this._bufferService.cols - rangeStartColumn > 1) {
|
||||
const joinedRanges = this._getJoinedRanges(
|
||||
lineStr,
|
||||
rangeStartStringIndex,
|
||||
@@ -217,8 +216,7 @@ export class CharacterJoinerService implements ICharacterJoinerService {
|
||||
return;
|
||||
}
|
||||
|
||||
const trimmedLength = line.getTrimmedLength();
|
||||
for (let x = startCol; x < trimmedLength; x++) {
|
||||
for (let x = startCol; x < this._bufferService.cols; x++) {
|
||||
const width = line.getWidth(x);
|
||||
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
|
||||
// the end of the line.
|
||||
if (currentRange) {
|
||||
currentRange[1] = trimmedLength;
|
||||
currentRange[1] = this._bufferService.cols;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -704,7 +704,7 @@ export class SelectionService extends Disposable implements ISelectionService {
|
||||
if (this._activeSelectionMode !== SelectionMode.COLUMN) {
|
||||
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 {
|
||||
if (this._activeSelectionMode !== SelectionMode.COLUMN) {
|
||||
this._model.selectionEnd[0] = 0;
|
||||
|
||||
@@ -71,20 +71,12 @@ export interface IBufferLineStringCache {
|
||||
* (if only one particular value is needed) or `loadCell`. For `loadCell` in a loop
|
||||
* memory allocs / GC pressure can be greatly reduced by reusing the CellData object.
|
||||
*/
|
||||
/**
|
||||
* Shared empty sparse map for lines with no combining data.
|
||||
* Uses a sentinel + copy-on-write so `copyFrom` can detect empty `_combined` with
|
||||
* reference equality. `_extendedAttrs` uses `undefined` instead (addon-image writes
|
||||
* the map directly without BufferLine APIs).
|
||||
*/
|
||||
const EMPTY_SPARSE_MAP: {[index: number]: string} = Object.create(null);
|
||||
|
||||
export class BufferLine implements IBufferLine {
|
||||
protected _data: Uint32Array;
|
||||
/** Sparse cache; only read when `IS_COMBINED_MASK` is set in `_data`. */
|
||||
protected _combined: {[index: number]: string} = EMPTY_SPARSE_MAP;
|
||||
protected _combined: {[index: number]: string} = {};
|
||||
/** Sparse cache; only read when `HAS_EXTENDED` is set in `_data`. */
|
||||
protected _extendedAttrs: {[index: number]: IExtendedAttrs | undefined} | undefined;
|
||||
protected _extendedAttrs: {[index: number]: IExtendedAttrs | undefined} = {};
|
||||
protected _stringCacheEntryRef: WeakRef<IBufferLineStringCacheEntry> | undefined;
|
||||
public length: number;
|
||||
|
||||
@@ -129,7 +121,6 @@ export class BufferLine implements IBufferLine {
|
||||
this._invalidateStringCache();
|
||||
this._data[index * Constants.CELL_INDICIES + Cell.FG] = value[CHAR_DATA_ATTR_INDEX];
|
||||
if (value[CHAR_DATA_CHAR_INDEX].length > 1) {
|
||||
this._ensureCombinedMap();
|
||||
this._combined[index] = value[1];
|
||||
this._data[index * Constants.CELL_INDICIES + Cell.CONTENT] = index | Content.IS_COMBINED_MASK | (value[CHAR_DATA_WIDTH_INDEX] << Content.WIDTH_SHIFT);
|
||||
} else {
|
||||
@@ -220,7 +211,7 @@ export class BufferLine implements IBufferLine {
|
||||
cell.combinedData = '';
|
||||
}
|
||||
if (cell.bg & BgFlags.HAS_EXTENDED) {
|
||||
cell.extended = this._extendedAttrs![index]!;
|
||||
cell.extended = this._extendedAttrs[index]!;
|
||||
} else {
|
||||
// Do not mutate cell.extended in place: it may still reference this line's map entry from a
|
||||
// prior loadCell into a reused CellData (e.g. $workCell during insert/delete).
|
||||
@@ -235,11 +226,10 @@ export class BufferLine implements IBufferLine {
|
||||
public setCell(index: number, cell: ICellData): void {
|
||||
this._invalidateStringCache();
|
||||
if (cell.content & Content.IS_COMBINED_MASK) {
|
||||
this._ensureCombinedMap();
|
||||
this._combined[index] = cell.combinedData;
|
||||
}
|
||||
if (cell.bg & BgFlags.HAS_EXTENDED) {
|
||||
this._ensureExtendedAttrsMap()[index] = cell.extended;
|
||||
this._extendedAttrs[index] = cell.extended;
|
||||
}
|
||||
this._data[index * Constants.CELL_INDICIES + Cell.CONTENT] = cell.content;
|
||||
this._data[index * Constants.CELL_INDICIES + Cell.FG] = cell.fg;
|
||||
@@ -254,7 +244,7 @@ export class BufferLine implements IBufferLine {
|
||||
public setCellFromCodepoint(index: number, codePoint: number, width: number, attrs: IAttributeData): void {
|
||||
this._invalidateStringCache();
|
||||
if (attrs.bg & BgFlags.HAS_EXTENDED) {
|
||||
this._ensureExtendedAttrsMap()[index] = attrs.extended;
|
||||
this._extendedAttrs[index] = attrs.extended;
|
||||
}
|
||||
this._data[index * Constants.CELL_INDICIES + Cell.CONTENT] = codePoint | (width << Content.WIDTH_SHIFT);
|
||||
this._data[index * Constants.CELL_INDICIES + Cell.FG] = attrs.fg;
|
||||
@@ -272,14 +262,12 @@ export class BufferLine implements IBufferLine {
|
||||
let content = this._data[index * Constants.CELL_INDICIES + Cell.CONTENT];
|
||||
if (content & Content.IS_COMBINED_MASK) {
|
||||
// we already have a combined string, simply add
|
||||
this._ensureCombinedMap();
|
||||
this._combined[index] += stringFromCodePoint(codePoint);
|
||||
} else {
|
||||
if (content & Content.CODEPOINT_MASK) {
|
||||
// normal case for combining chars:
|
||||
// - move current leading char + new one into combined string
|
||||
// - set combined flag
|
||||
this._ensureCombinedMap();
|
||||
this._combined[index] = stringFromCodePoint(content & Content.CODEPOINT_MASK) + stringFromCodePoint(codePoint);
|
||||
content &= ~Content.CODEPOINT_MASK; // set codepoint in buffer to 0
|
||||
content |= Content.IS_COMBINED_MASK;
|
||||
@@ -422,13 +410,11 @@ export class BufferLine implements IBufferLine {
|
||||
}
|
||||
}
|
||||
// remove any cut off extended attributes
|
||||
if (this._extendedAttrs) {
|
||||
const extKeys = Object.keys(this._extendedAttrs);
|
||||
for (let i = 0; i < extKeys.length; i++) {
|
||||
const key = parseInt(extKeys[i], 10);
|
||||
if (key >= cols) {
|
||||
delete this._extendedAttrs[key];
|
||||
}
|
||||
const extKeys = Object.keys(this._extendedAttrs);
|
||||
for (let i = 0; i < extKeys.length; i++) {
|
||||
const key = parseInt(extKeys[i], 10);
|
||||
if (key >= cols) {
|
||||
delete this._extendedAttrs[key];
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -464,8 +450,8 @@ export class BufferLine implements IBufferLine {
|
||||
}
|
||||
return;
|
||||
}
|
||||
this._combined = EMPTY_SPARSE_MAP;
|
||||
this._extendedAttrs = undefined;
|
||||
this._combined = {};
|
||||
this._extendedAttrs = {};
|
||||
for (let i = 0; i < this.length; ++i) {
|
||||
this.setCell(i, fillCellData);
|
||||
}
|
||||
@@ -622,57 +608,19 @@ export class BufferLine implements IBufferLine {
|
||||
private _copyCellMapsFrom(src: BufferLine, srcCol: number, destCol: number): void {
|
||||
const srcStart = srcCol * Constants.CELL_INDICIES;
|
||||
if (src._data[srcStart + Cell.CONTENT] & Content.IS_COMBINED_MASK) {
|
||||
this._ensureCombinedMap();
|
||||
this._combined[destCol] = src._combined[srcCol];
|
||||
}
|
||||
if (src._data[srcStart + Cell.BG] & BgFlags.HAS_EXTENDED) {
|
||||
this._ensureExtendedAttrsMap()[destCol] = src._extendedAttrs![srcCol]!;
|
||||
this._extendedAttrs[destCol] = src._extendedAttrs[srcCol];
|
||||
}
|
||||
}
|
||||
|
||||
/** Rebuild sparse maps from another line, keyed only by `_data` flags. */
|
||||
private _copySparseMapsFrom(line: BufferLine): void {
|
||||
const srcCombined = line._combined;
|
||||
const srcExtended = line._extendedAttrs;
|
||||
|
||||
// Fast path: blank / scroll-recycle source (empty `_combined` sentinel, no extended map).
|
||||
if (srcCombined === EMPTY_SPARSE_MAP && !srcExtended) {
|
||||
this._combined = EMPTY_SPARSE_MAP;
|
||||
this._extendedAttrs = undefined;
|
||||
return;
|
||||
}
|
||||
this._copySparseMapsFromDense(line);
|
||||
}
|
||||
|
||||
/** Copy sparse maps by scanning `_data` flags (inlined, no per-cell function calls). */
|
||||
private _copySparseMapsFromDense(line: BufferLine): void {
|
||||
this._combined = {};
|
||||
this._extendedAttrs = {};
|
||||
const srcData = line._data;
|
||||
const srcCombined = line._combined;
|
||||
const srcExtended = line._extendedAttrs;
|
||||
const len = line.length;
|
||||
const indices = Constants.CELL_INDICIES;
|
||||
const combined = this._combined;
|
||||
const extendedAttrs = this._extendedAttrs;
|
||||
for (let i = 0; i < len; i++) {
|
||||
const start = i * indices;
|
||||
if (srcData[start + Cell.CONTENT] & Content.IS_COMBINED_MASK) {
|
||||
combined[i] = srcCombined[i];
|
||||
}
|
||||
if (srcData[start + Cell.BG] & BgFlags.HAS_EXTENDED) {
|
||||
extendedAttrs[i] = srcExtended![i];
|
||||
}
|
||||
for (let i = 0; i < line.length; i++) {
|
||||
this._copyCellMapsFrom(line, i, i);
|
||||
}
|
||||
}
|
||||
|
||||
private _ensureCombinedMap(): void {
|
||||
if (this._combined === EMPTY_SPARSE_MAP) {
|
||||
this._combined = {};
|
||||
}
|
||||
}
|
||||
|
||||
private _ensureExtendedAttrsMap(): {[index: number]: IExtendedAttrs | undefined} {
|
||||
return this._extendedAttrs ??= {};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -79,7 +79,7 @@ function assertDecodedRange(
|
||||
}
|
||||
const target = new Uint32Array(count);
|
||||
const length = decode(input, target);
|
||||
assert.strictEqual(length, count);
|
||||
assert.equal(length, count);
|
||||
let mismatchIndex = -1;
|
||||
let index = 0;
|
||||
for (let i = min; i < max; ++i) {
|
||||
@@ -92,8 +92,8 @@ function assertDecodedRange(
|
||||
}
|
||||
index++;
|
||||
}
|
||||
assert.strictEqual(mismatchIndex, -1);
|
||||
assert.strictEqual(outputToString(target, length), input);
|
||||
assert.equal(mismatchIndex, -1);
|
||||
assert.equal(outputToString(target, length), input);
|
||||
}
|
||||
|
||||
const BATCH_SIZE = 8192;
|
||||
@@ -116,9 +116,9 @@ describe('text encodings', () => {
|
||||
const data = new Uint32Array(s.length);
|
||||
for (let i = 0; i < s.length; ++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', () => {
|
||||
@@ -159,7 +159,7 @@ describe('text encodings', () => {
|
||||
const decoder = new StringToUtf32();
|
||||
const target = new Uint32Array(5);
|
||||
const length = decoder.decode(String.fromCharCode(0xFEFF), target);
|
||||
assert.strictEqual(length, 0);
|
||||
assert.equal(length, 0);
|
||||
decoder.clear();
|
||||
});
|
||||
});
|
||||
@@ -169,7 +169,7 @@ describe('text encodings', () => {
|
||||
const target = new Uint32Array(500);
|
||||
for (let i = 0; i < TEST_STRINGS.length; ++i) {
|
||||
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();
|
||||
}
|
||||
});
|
||||
@@ -184,7 +184,7 @@ describe('text encodings', () => {
|
||||
const written = decoder.decode(input[i], target);
|
||||
decoded += toString(target, written);
|
||||
}
|
||||
assert.strictEqual(decoded, 'Ä€𝄞Ö𝄞€Ü𝄞€');
|
||||
assert(decoded, 'Ä€𝄞Ö𝄞€Ü𝄞€');
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -226,7 +226,7 @@ describe('text encodings', () => {
|
||||
const target = new Uint32Array(5);
|
||||
const utf8Data = stringToUtf8Bytes(String.fromCharCode(0xFEFF));
|
||||
const length = decoder.decode(utf8Data, target);
|
||||
assert.strictEqual(length, 0);
|
||||
assert.equal(length, 0);
|
||||
decoder.clear();
|
||||
});
|
||||
});
|
||||
@@ -237,7 +237,7 @@ describe('text encodings', () => {
|
||||
for (let i = 0; i < TEST_STRINGS.length; ++i) {
|
||||
const utf8Data = stringToUtf8Bytes(TEST_STRINGS[i]);
|
||||
const length = decoder.decode(utf8Data, target);
|
||||
assert.strictEqual(toString(target, length), TEST_STRINGS[i]);
|
||||
assert.equal(toString(target, length), TEST_STRINGS[i]);
|
||||
decoder.clear();
|
||||
}
|
||||
});
|
||||
@@ -252,7 +252,7 @@ describe('text encodings', () => {
|
||||
const written = decoder.decode(utf8Data.slice(i, i + 1), target);
|
||||
decoded += toString(target, written);
|
||||
}
|
||||
assert.strictEqual(decoded, 'ÄÖÜßöäü');
|
||||
assert(decoded, 'ÄÖÜßöäü');
|
||||
});
|
||||
|
||||
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);
|
||||
decoded += toString(target, written);
|
||||
}
|
||||
assert.strictEqual(decoded, 'Āր܀߀ö€ä€ü');
|
||||
assert(decoded, 'Āր܀߀ö€ä€ü');
|
||||
});
|
||||
|
||||
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);
|
||||
decoded += toString(target, written);
|
||||
}
|
||||
assert.strictEqual(decoded, 'Ä€𝄞Ö𝄞€Ü𝄞€');
|
||||
assert(decoded, 'Ä€𝄞Ö𝄞€Ü𝄞€');
|
||||
});
|
||||
|
||||
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);
|
||||
decoded += toString(target, written);
|
||||
}
|
||||
assert.strictEqual(decoded, 'Ä€𝄞Ö𝄞€Ü𝄞€');
|
||||
assert(decoded, 'Ä€𝄞Ö𝄞€Ü𝄞€');
|
||||
});
|
||||
|
||||
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);
|
||||
decoded += toString(target, written);
|
||||
}
|
||||
assert.strictEqual(decoded, 'Ä€𝄞Ö𝄞€Ü𝄞€');
|
||||
assert(decoded, 'Ä€𝄞Ö𝄞€Ü𝄞€');
|
||||
});
|
||||
|
||||
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);
|
||||
decoded += toString(target, written);
|
||||
}
|
||||
assert.strictEqual(decoded, '');
|
||||
assert.equal(decoded, '');
|
||||
});
|
||||
|
||||
it('test break after 3 bytes - issue #2495', () => {
|
||||
@@ -320,37 +320,11 @@ describe('text encodings', () => {
|
||||
const target = new Uint32Array(5);
|
||||
const utf8Data = fromByteString('\xf0\xa0\x9c\x8e');
|
||||
let written = decoder.decode(utf8Data.slice(0, 3), target);
|
||||
assert.strictEqual(written, 0);
|
||||
assert.equal(written, 0);
|
||||
written = decoder.decode(utf8Data.slice(3), target);
|
||||
assert.strictEqual(written, 1);
|
||||
assert.strictEqual(toString(target, written), '𠜎');
|
||||
assert.equal(written, 1);
|
||||
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');
|
||||
});
|
||||
});
|
||||
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -158,9 +158,9 @@ export class Utf8ToUtf32 {
|
||||
cp &= ((((cp & 0xE0) === 0xC0)) ? 0x1F : (((cp & 0xF0) === 0xE0)) ? 0x0F : 0x07);
|
||||
let pos = 0;
|
||||
let tmp: number;
|
||||
while ((tmp = this.interim[++pos]) && pos < 4) {
|
||||
while ((tmp = this.interim[++pos] & 0x3F) && pos < 4) {
|
||||
cp <<= 6;
|
||||
cp |= tmp & 0x3F;
|
||||
cp |= tmp;
|
||||
}
|
||||
// missing bytes - read ahead from input
|
||||
const type = (((this.interim[0] & 0xE0) === 0xC0)) ? 2 : (((this.interim[0] & 0xF0) === 0xE0)) ? 3 : 4;
|
||||
|
||||
@@ -99,6 +99,10 @@ export class Terminal extends CoreTerminal {
|
||||
* Clear the entire buffer, making the prompt line the new first line.
|
||||
*/
|
||||
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.lines.set(0, this.buffer.lines.get(this.buffer.ybase + this.buffer.y)!);
|
||||
this.buffer.lines.length = 1;
|
||||
|
||||
@@ -1,113 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2026 The xterm.js authors. All rights reserved.
|
||||
* @license MIT
|
||||
*/
|
||||
|
||||
import { perfContext, before, RuntimeCase } from 'xterm-benchmark';
|
||||
import { BufferLine, DEFAULT_ATTR_DATA } from 'common/buffer/BufferLine';
|
||||
import { BufferLineStringCache } from 'common/buffer/BufferLineStringCache';
|
||||
import { CellData } from 'common/buffer/CellData';
|
||||
import { BgFlags, NULL_CELL_CHAR, NULL_CELL_WIDTH, NULL_CELL_CODE, UnderlineStyle } from 'common/buffer/Constants';
|
||||
|
||||
const ITERATIONS = 500_000;
|
||||
const stringCache = new BufferLineStringCache();
|
||||
const nullCell = CellData.fromCharData([0, NULL_CELL_CHAR, NULL_CELL_WIDTH, NULL_CELL_CODE]);
|
||||
const yCell = CellData.fromCharData([0, 'y', 1, 'y'.charCodeAt(0)]);
|
||||
|
||||
function fillPlainY(line: BufferLine, cols: number): void {
|
||||
for (let i = 0; i < cols; i++) {
|
||||
line.setCellFromCodepoint(i, 'y'.charCodeAt(0), 1, DEFAULT_ATTR_DATA);
|
||||
}
|
||||
}
|
||||
|
||||
function setupScrollRecyclePair(cols: number): { recycled: BufferLine, blank: BufferLine } {
|
||||
const blank = new BufferLine(stringCache, cols, nullCell, false);
|
||||
const recycled = new BufferLine(stringCache, cols, yCell, false);
|
||||
fillPlainY(recycled, cols);
|
||||
return { recycled, blank };
|
||||
}
|
||||
|
||||
/** Source line with combining chars and extended attrs spread across columns. */
|
||||
function buildSparseSourceLine(cols: number): BufferLine {
|
||||
const line = new BufferLine(stringCache, cols, nullCell, false);
|
||||
for (let i = 0; i < cols; i++) {
|
||||
if (i % 10 === 0) {
|
||||
const cell = CellData.fromCharData([0, 'a', 1, 'a'.charCodeAt(0)]);
|
||||
cell.extended.underlineStyle = UnderlineStyle.CURLY;
|
||||
cell.bg |= BgFlags.HAS_EXTENDED;
|
||||
line.setCell(i, cell);
|
||||
} else if (i % 17 === 0) {
|
||||
line.setCell(i, CellData.fromCharData([1, 'e\u0301', 1, '\u0301'.charCodeAt(0)]));
|
||||
} else {
|
||||
line.setCellFromCodepoint(i, 'y'.charCodeAt(0), 1, DEFAULT_ATTR_DATA);
|
||||
}
|
||||
}
|
||||
return line;
|
||||
}
|
||||
|
||||
function setupCopyFromSparsePair(cols: number): { dest: BufferLine, src: BufferLine } {
|
||||
const src = buildSparseSourceLine(cols);
|
||||
const dest = new BufferLine(stringCache, cols, yCell, false);
|
||||
fillPlainY(dest, cols);
|
||||
return { dest, src };
|
||||
}
|
||||
|
||||
perfContext('BufferLine.copyFrom (scroll recycle)', () => {
|
||||
perfContext('cols=80', () => {
|
||||
let recycled: BufferLine;
|
||||
let blank: BufferLine;
|
||||
before(() => {
|
||||
({ recycled, blank } = setupScrollRecyclePair(80));
|
||||
});
|
||||
new RuntimeCase('', () => {
|
||||
for (let i = 0; i < ITERATIONS; i++) {
|
||||
recycled.copyFrom(blank);
|
||||
}
|
||||
return { payloadSize: ITERATIONS };
|
||||
}, { fork: false }).showAverageRuntime();
|
||||
});
|
||||
|
||||
perfContext('cols=279', () => {
|
||||
let recycled: BufferLine;
|
||||
let blank: BufferLine;
|
||||
before(() => {
|
||||
({ recycled, blank } = setupScrollRecyclePair(279));
|
||||
});
|
||||
new RuntimeCase('', () => {
|
||||
for (let i = 0; i < ITERATIONS; i++) {
|
||||
recycled.copyFrom(blank);
|
||||
}
|
||||
return { payloadSize: ITERATIONS };
|
||||
}, { fork: false }).showAverageRuntime();
|
||||
});
|
||||
});
|
||||
|
||||
perfContext('BufferLine.copyFrom (sparse source: combined + extended attrs)', () => {
|
||||
perfContext('cols=80', () => {
|
||||
let dest: BufferLine;
|
||||
let src: BufferLine;
|
||||
before(() => {
|
||||
({ dest, src } = setupCopyFromSparsePair(80));
|
||||
});
|
||||
new RuntimeCase('', () => {
|
||||
for (let i = 0; i < ITERATIONS; i++) {
|
||||
dest.copyFrom(src);
|
||||
}
|
||||
return { payloadSize: ITERATIONS };
|
||||
}, { fork: false }).showAverageRuntime();
|
||||
});
|
||||
|
||||
perfContext('cols=279', () => {
|
||||
let dest: BufferLine;
|
||||
let src: BufferLine;
|
||||
before(() => {
|
||||
({ dest, src } = setupCopyFromSparsePair(279));
|
||||
});
|
||||
new RuntimeCase('', () => {
|
||||
for (let i = 0; i < ITERATIONS; i++) {
|
||||
dest.copyFrom(src);
|
||||
}
|
||||
return { payloadSize: ITERATIONS };
|
||||
}, { fork: false }).showAverageRuntime();
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user