Compare commits

..
Author SHA1 Message Date
Cursor AgentandDaniel Imms 01ee69dcc0 fix(deps): override uuid to ^11.1.1
nyc 18.0.0 pulls in uuid 8.3.2 via istanbul-lib-processinfo, which is
affected by silent partial buffer writes in v3()/v5()/v6(). Force the
patched 11.1.1 release via npm overrides.

Co-authored-by: Daniel Imms <Tyriar@users.noreply.github.com>
2026-06-02 13:51:40 +00:00
27 changed files with 107 additions and 330 deletions
+27 -19
View File
@@ -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
+4 -4
View File
@@ -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
+2 -2
View File
@@ -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'
+2 -2
View File
@@ -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'
+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 {
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`]);
});
});
});
+6 -8
View File
@@ -142,14 +142,12 @@ export class SearchAddon extends Disposable implements ITerminalAddon, ISearchAp
}
prevResult = result;
results.push(prevResult);
const cols = this._terminal.cols;
let nextCol = prevResult.col + prevResult.size;
let nextRow = prevResult.row;
if (nextCol >= cols) {
nextRow += Math.floor(nextCol / cols);
nextCol = nextCol % cols;
}
result = this._engine.find(term, nextRow, nextCol, searchOptions);
result = this._engine.find(
term,
prevResult.col + prevResult.term.length >= this._terminal.cols ? prevResult.row + 1 : prevResult.row,
prevResult.col + prevResult.term.length >= this._terminal.cols ? 0 : prevResult.col + 1,
searchOptions
);
}
this._resultTracker.updateResults(results, this._highlightLimit);
@@ -414,20 +414,6 @@ test.describe('Search 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', () => {
let fixture: string;
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.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;
}
+6 -22
View File
@@ -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 {
+6 -21
View File
@@ -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 = [];
}
}
}
}
}
}
+8 -5
View File
@@ -8043,14 +8043,17 @@
}
},
"node_modules/uuid": {
"version": "8.3.2",
"resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz",
"integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==",
"deprecated": "uuid@10 and below is no longer supported. For ESM codebases, update to uuid@latest. For CommonJS codebases, use uuid@11 (but be aware this version will likely be deprecated in 2028).",
"version": "11.1.1",
"resolved": "https://registry.npmjs.org/uuid/-/uuid-11.1.1.tgz",
"integrity": "sha512-vIYxrBCC/N/K+Js3qSN88go7kIfNPssr/hHCesKCQNAjmgvYS2oqr69kIufEG+O4+PfezOH4EbIeHCfFov8ZgQ==",
"dev": true,
"funding": [
"https://github.com/sponsors/broofa",
"https://github.com/sponsors/ctavan"
],
"license": "MIT",
"bin": {
"uuid": "dist/bin/uuid"
"uuid": "dist/esm/bin/uuid"
}
},
"node_modules/vary": {
+2 -1
View File
@@ -12,7 +12,8 @@
"addons/*"
],
"overrides": {
"serialize-javascript": "^7.0.3"
"serialize-javascript": "^7.0.3",
"uuid": "^11.1.1"
},
"keywords": [
"cli",
+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.
*/
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;
+4 -14
View File
@@ -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', () => {
+2 -3
View File
@@ -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;
}
}
+1 -1
View File
@@ -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;
-15
View File
@@ -495,21 +495,6 @@ describe('BufferLine', function(): void {
assert.deepEqual(columns, [0]);
});
});
describe('copyCellsFrom', () => {
it('should only move combined data within the copied range', () => {
const src = new TestBufferLine(10, NULL_CELL_DATA, false);
src.setCell(1, createCellData(1, 'a', 1));
src.addCodepointToCell(1, '\u0301'.charCodeAt(0), 0);
src.setCell(5, createCellData(1, 'z', 1));
src.addCodepointToCell(5, '\u0301'.charCodeAt(0), 0);
const dest = new TestBufferLine(10, NULL_CELL_DATA, false);
dest.copyCellsFrom(src, 0, 0, 2, false);
assert.property(dest.combined, '1');
assert.notProperty(dest.combined, '5');
});
});
describe('addCharToCell', () => {
it('should set width to 1 for empty cell', () => {
const line = new TestBufferLine(3, NULL_CELL_DATA, false);
-4
View File
@@ -609,13 +609,9 @@ export class BufferLine implements IBufferLine {
const srcStart = srcCol * Constants.CELL_INDICIES;
if (src._data[srcStart + Cell.CONTENT] & Content.IS_COMBINED_MASK) {
this._combined[destCol] = src._combined[srcCol];
} else {
delete this._combined[destCol];
}
if (src._data[srcStart + Cell.BG] & BgFlags.HAS_EXTENDED) {
this._extendedAttrs[destCol] = src._extendedAttrs[srcCol];
} else {
delete this._extendedAttrs[destCol];
}
}
-23
View File
@@ -10,7 +10,6 @@ import { CellData } from './CellData';
import { NULL_CELL_CHAR, NULL_CELL_WIDTH, NULL_CELL_CODE } from './Constants';
import { reflowLargerGetLinesToRemove, reflowSmallerGetNewLineLengths } from './BufferReflow';
import { IBufferLine } from './Types';
import { createCellData, NULL_CELL_DATA } from '../TestUtils.test';
const TEST_STRING_CACHE = new BufferLineStringCache();
@@ -123,27 +122,5 @@ describe('BufferReflow', () => {
const lines = createWrappedLines('abcde');
assert.notDeepEqual(reflowLargerGetLinesToRemove(lines, 1, 5, 10, nullCell, false), []);
});
it('should clear isWrapped on the last retained row after unwrap', () => {
const lines = new CircularList<BufferLine>(10);
const set2 = (line: BufferLine, s: string) => {
for (let i = 0; i < s.length; i++) {
line.setCell(i, createCellData(0, s[i], 1));
}
};
const l0 = new BufferLine(TEST_STRING_CACHE, 2);
set2(l0, 'ab');
const l1 = new BufferLine(TEST_STRING_CACHE, 2, undefined, true);
set2(l1, 'cd');
const l2 = new BufferLine(TEST_STRING_CACHE, 2, undefined, true);
set2(l2, 'ef');
lines.push(l0);
lines.push(l1);
lines.push(l2);
reflowLargerGetLinesToRemove(lines, 2, 6, 99, NULL_CELL_DATA, false);
assert.equal(l1.isWrapped, false);
});
});
});
-5
View File
@@ -99,11 +99,6 @@ export function reflowLargerGetLinesToRemove(lines: CircularList<IBufferLine>, o
}
if (countToRemove > 0) {
wrappedLines[destLineIndex].isWrapped = false;
const lineAfterGroup = lines.get(y + wrappedLines.length);
if (lineAfterGroup) {
lineAfterGroup.isWrapped = false;
}
toRemove.push(y + wrappedLines.length - countToRemove); // index
toRemove.push(countToRemove);
}
+19 -45
View File
@@ -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');
});
});
});
});
});
+2 -2
View File
@@ -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;
+4
View File
@@ -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;