Support caching of trimRight

This commit is contained in:
Daniel Imms
2026-05-02 04:35:18 -07:00
parent 7fa8b7a069
commit 4d4b994c35
3 changed files with 69 additions and 20 deletions
+48 -10
View File
@@ -16,6 +16,22 @@ class TestBufferLine extends BufferLine {
return this._combined;
}
public get cachedCanonicalString(): string | undefined {
return this._cachedCanonicalString;
}
public set cachedCanonicalString(value: string | undefined) {
this._cachedCanonicalString = value;
}
public get cachedCanonicalTrimmedString(): string | undefined {
return this._cachedCanonicalTrimmedString;
}
public set cachedCanonicalTrimmedString(value: string | undefined) {
this._cachedCanonicalTrimmedString = value;
}
public toArray(): CharData[] {
const result = [];
for (let i = 0; i < this.length; ++i) {
@@ -808,30 +824,52 @@ describe('BufferLine', function(): void {
assert.equal(extendedAttributes(line, 4), extendedAttributes(initial, 4));
});
it('should cache canonical non-trimmed string translations', () => {
const line = new TestBufferLine(3);
it('should cache canonical string translations', () => {
const line = new TestBufferLine(5);
line.setCell(0, createCellData(1, 'a', 1));
line.setCell(1, createCellData(1, 'b', 1));
line.setCell(2, createCellData(1, 'c', 1));
// Trimmed-only canonical request should cache the trimmed value.
const trimmed = line.translateToString(true, undefined, undefined, undefined);
assert.equal(trimmed, 'abc');
assert.equal(line.cachedCanonicalTrimmedString, 'abc');
// Non-trimmed canonical request should cache the full value and free trimmed cache.
const translated = line.translateToString(false, undefined, undefined, undefined);
assert.equal(translated, 'abc');
assert.equal((line as any)._cachedTrimmedString, 'abc');
(line as any)._cachedTrimmedString = 'cached';
assert.equal(line.translateToString(false, undefined, undefined, undefined), 'cached');
assert.equal(translated, 'abc ');
assert.equal(line.cachedCanonicalString, 'abc ');
assert.equal(line.cachedCanonicalTrimmedString, undefined);
// Once non-trimmed is cached, trimmed should be derived from trimEnd() (not re-cached).
assert.equal(line.translateToString(true, undefined, undefined, undefined), 'abc');
assert.equal(line.cachedCanonicalTrimmedString, undefined);
line.cachedCanonicalString = 'cached-non-trimmed ';
line.cachedCanonicalTrimmedString = 'cached-trimmed';
assert.equal(line.translateToString(false, undefined, undefined, undefined), 'cached-non-trimmed ');
assert.equal(line.translateToString(true, undefined, undefined, undefined), 'cached-trimmed');
line.cachedCanonicalTrimmedString = undefined;
assert.equal(line.translateToString(true, undefined, undefined, undefined), 'cached-non-trimmed');
// Any optional translation argument should bypass cache.
assert.equal(line.translateToString(false, 0, 2, undefined), 'ab');
assert.equal(line.translateToString(true, undefined, undefined, undefined), 'abc');
assert.equal(line.translateToString(true, 0, 2, undefined), 'ab');
});
it('should invalidate cached trimmed strings on line mutations', () => {
it('should invalidate cached canonical strings on line mutations', () => {
const assertCacheInvalidated = (mutate: (line: TestBufferLine) => void): void => {
const line = new TestBufferLine(5);
line.fill(createCellData(1, 'a', 1));
line.translateToString(true, undefined, undefined, undefined);
assert.equal(line.cachedCanonicalTrimmedString, 'aaaaa');
line.translateToString(false, undefined, undefined, undefined);
assert.equal((line as any)._cachedTrimmedString, 'aaaaa');
assert.equal(line.cachedCanonicalString, 'aaaaa');
assert.equal(line.cachedCanonicalTrimmedString, undefined);
mutate(line);
assert.equal((line as any)._cachedTrimmedString, undefined);
assert.equal(line.cachedCanonicalString, undefined);
assert.equal(line.cachedCanonicalTrimmedString, undefined);
};
assertCacheInvalidated(line => line.set(0, [0, 'b', 1, 'b'.charCodeAt(0)]));
+19 -9
View File
@@ -63,7 +63,9 @@ export class BufferLine implements IBufferLine {
protected _data: Uint32Array;
protected _combined: {[index: number]: string} = {};
protected _extendedAttrs: {[index: number]: IExtendedAttrs | undefined} = {};
private _cachedTrimmedString: string | undefined;
// Cache for canonical full-line translations.
protected _cachedCanonicalString: string | undefined;
protected _cachedCanonicalTrimmedString: string | undefined;
public length: number;
constructor(cols: number, fillCellData?: ICellData, public isWrapped: boolean = false) {
@@ -520,8 +522,8 @@ export class BufferLine implements IBufferLine {
}
/**
* Translates the buffer line to a string. Caching only applies to canonical non-trimmed
* translation requests. Using `trimRight` or any optional range/mapping arguments disables cache.
* Translates the buffer line to a string. Caching only applies to canonical full-line translation
* requests (regardless of `trimRight` value).
*
* @param trimRight Whether to trim any empty cells on the right.
* @param startCol The column to start the string (0-based inclusive).
@@ -534,9 +536,10 @@ export class BufferLine implements IBufferLine {
* returned string, the corresponding entries in `outColumns` will have the same column number.
*/
public translateToString(trimRight?: boolean, startCol?: number, endCol?: number, outColumns?: number[]): string {
const isCanonicalTrimmedRequest = !trimRight && (startCol === undefined || startCol === 0) && endCol === undefined && outColumns === undefined;
if (isCanonicalTrimmedRequest && this._cachedTrimmedString !== undefined) {
return this._cachedTrimmedString;
const isCanonicalRequest = (startCol === undefined || startCol === 0) && endCol === undefined && outColumns === undefined;
const cachedResult = trimRight ? this._cachedCanonicalTrimmedString ?? this._cachedCanonicalString?.trimEnd() : this._cachedCanonicalString;
if (isCanonicalRequest && cachedResult !== undefined) {
return cachedResult;
}
startCol = startCol ?? 0;
endCol = endCol ?? this.length;
@@ -562,13 +565,20 @@ export class BufferLine implements IBufferLine {
if (outColumns) {
outColumns.push(startCol);
}
if (isCanonicalTrimmedRequest) {
this._cachedTrimmedString = result;
if (isCanonicalRequest) {
if (trimRight) {
this._cachedCanonicalTrimmedString = result;
} else {
this._cachedCanonicalString = result;
// Free the trimmed string as trimEnd() will be used to save memory
this._cachedCanonicalTrimmedString = undefined;
}
}
return result;
}
private _invalidateStringCache(): void {
this._cachedTrimmedString = undefined;
this._cachedCanonicalString = undefined;
this._cachedCanonicalTrimmedString = undefined;
}
}