Skip to content

Commit 1e18962

Browse files
committed
Merge remote-tracking branch 'upstream/master' into inst_const_enuym
2 parents 80debaf + 1023e40 commit 1e18962

16 files changed

Lines changed: 465 additions & 48 deletions

addons/addon-image/README.md

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -25,14 +25,14 @@ const customSettings: IImageAddonOptions = {
2525
pixelLimit: 16777216, // max. pixel size of a single image
2626
sixelSupport: true, // enable sixel support
2727
sixelScrolling: true, // whether to scroll on image output
28-
sixelPaletteLimit: 256, // initial sixel palette size
29-
sixelSizeLimit: 25000000, // size limit of a single sixel sequence
28+
sixelPaletteLimit: 4096, // initial sixel palette size
29+
sixelSizeLimit: 33554432, // size limit of a single sixel sequence
3030
storageLimit: 128, // FIFO storage limit in MB
3131
showPlaceholder: true, // whether to show a placeholder for evicted images
3232
iipSupport: true, // enable iTerm IIP support
33-
iipSizeLimit: 20000000, // size limit of a single IIP sequence
33+
iipSizeLimit: 33554432, // size limit of a single IIP sequence
3434
kittySupport: true, // enable Kitty graphics support
35-
kittySizeLimit: 20000000 // size limit of a single Kitty sequence
35+
kittySizeLimit: 33554432 // size limit of a single Kitty sequence
3636
}
3737

3838
// initialization

addons/addon-image/test/ImageAddon.test.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -132,13 +132,13 @@ test.describe('ImageAddon', () => {
132132
sixelSupport: true,
133133
sixelScrolling: true,
134134
sixelPaletteLimit: 512, // set to 512 to get example image working
135-
sixelSizeLimit: 25000000,
135+
sixelSizeLimit: 33554432,
136136
storageLimit: 128,
137137
showPlaceholder: true,
138138
iipSupport: true,
139-
iipSizeLimit: 20000000,
139+
iipSizeLimit: 33554432,
140140
kittySupport: true,
141-
kittySizeLimit: 20000000
141+
kittySizeLimit: 33554432
142142
};
143143
deepStrictEqual(await ctx.page.evaluate(`window.imageAddon._opts`), DEFAULT_OPTIONS);
144144
});

addons/addon-serialize/test/SerializeAddon.test.ts

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,14 @@ test.describe('SerializeAddon', () => {
4949
for (let i = 0; i < buffer.length; i++) {
5050
// Do this intentionally to get content of underlining source
5151
const bufferLine = buffer.getLine(i)._line;
52-
lines.push(JSON.stringify(bufferLine));
52+
lines.push(JSON.stringify(bufferLine, (key, value) => {
53+
// BufferLine caches are internal/transient and can legitimately differ
54+
// across equivalent terminal states.
55+
if (key === '_stringCache' || key === '_stringCacheEntryRef') {
56+
return undefined;
57+
}
58+
return value;
59+
}));
5360
}
5461
return {
5562
x: buffer.cursorX,

src/browser/renderer/dom/DomRendererRowFactory.test.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,14 +8,14 @@ import { assert } from 'chai';
88
import { DomRendererRowFactory } from 'browser/renderer/dom/DomRendererRowFactory';
99
import { NULL_CELL_CODE, NULL_CELL_WIDTH, NULL_CELL_CHAR, DEFAULT_ATTR, FgFlags, BgFlags, Attributes, UnderlineStyle } from 'common/buffer/Constants';
1010
import { BufferLine, DEFAULT_ATTR_DATA } from 'common/buffer/BufferLine';
11+
import { BufferLineStringCache } from 'common/buffer/BufferLineStringCache';
1112
import { IBufferLine } from 'common/Types';
1213
import { CellData } from 'common/buffer/CellData';
1314
import { MockCoreService, MockDecorationService, MockOptionsService, createCellData, NULL_CELL_DATA } from 'common/TestUtils.test';
1415
import { MockCharacterJoinerService, MockCoreBrowserService, MockThemeService } from 'browser/TestUtils.test';
1516
import { TestWidthCache } from 'browser/renderer/dom/WidthCache.test';
1617

17-
const dom = new jsdom.JSDOM('');
18-
18+
const TEST_STRING_CACHE = new BufferLineStringCache();
1919

2020
describe('DomRendererRowFactory', () => {
2121
let dom: jsdom.JSDOM;
@@ -517,7 +517,7 @@ describe('DomRendererRowFactory', () => {
517517
}
518518

519519
function createEmptyLineData(cols: number): IBufferLine {
520-
const lineData = new BufferLine(cols);
520+
const lineData = new BufferLine(TEST_STRING_CACHE, cols);
521521
for (let i = 0; i < cols; i++) {
522522
lineData.setCell(i, NULL_CELL_DATA);
523523
}

src/browser/services/CharacterJoinerService.test.ts

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,10 +7,13 @@ import { assert } from 'chai';
77
import { ICharacterJoinerService } from 'browser/services/Services';
88
import { CharacterJoinerService } from 'browser/services/CharacterJoinerService';
99
import { BufferLine } from 'common/buffer/BufferLine';
10+
import { BufferLineStringCache } from 'common/buffer/BufferLineStringCache';
1011
import { IBufferLine } from 'common/Types';
1112
import { CellData } from 'common/buffer/CellData';
1213
import { MockBufferService, createCellData } from 'common/TestUtils.test';
1314

15+
const TEST_STRING_CACHE = new BufferLineStringCache();
16+
1417
describe('CharacterJoinerService', () => {
1518
let service: ICharacterJoinerService;
1619

@@ -22,7 +25,7 @@ describe('CharacterJoinerService', () => {
2225
lines.set(2, lineData([['a -> b -', 0xFFFFFFFF], ['> c -> d', 0]]));
2326

2427
lines.set(3, lineData([['no joined ranges']]));
25-
lines.set(4, new BufferLine(0));
28+
lines.set(4, new BufferLine(TEST_STRING_CACHE, 0));
2629
lines.set(5, lineData([['a', 0x11111111], [' -> b -> c -> '], ['d', 0x22222222]]));
2730
const line6 = lineData([['wi']]);
2831
line6.resize(line6.length + 1, createCellData(0, '¥', 2));
@@ -267,7 +270,7 @@ describe('CharacterJoinerService', () => {
267270
type IPartialLineData = ([string] | [string, number]);
268271

269272
function lineData(data: IPartialLineData[]): IBufferLine {
270-
const tline = new BufferLine(0);
273+
const tline = new BufferLine(TEST_STRING_CACHE, 0);
271274
for (let i = 0; i < data.length; ++i) {
272275
const line = data[i][0];
273276
const attr = (data[i][1] || 0) as number;

src/browser/services/SelectionService.test.ts

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,12 +9,15 @@ import { SelectionModel } from 'browser/selection/SelectionModel';
99
import { IBufferLine } from 'common/Types';
1010
import { MockBufferService, MockOptionsService, MockCoreService, createCellData } from 'common/TestUtils.test';
1111
import { BufferLine } from 'common/buffer/BufferLine';
12+
import { BufferLineStringCache } from 'common/buffer/BufferLineStringCache';
1213
import { IBufferService, IOptionsService } from 'common/services/Services';
1314
import { MockCoreBrowserService, MockMouseService, MockRenderService } from 'browser/TestUtils.test';
1415
import { CellData } from 'common/buffer/CellData';
1516
import { IBuffer } from 'common/buffer/Types';
1617
import { IRenderService } from 'browser/services/Services';
1718

19+
const TEST_STRING_CACHE = new BufferLineStringCache();
20+
1821
class TestSelectionService extends SelectionService {
1922
constructor(
2023
bufferService: IBufferService,
@@ -55,15 +58,15 @@ describe('SelectionService', () => {
5558
});
5659

5760
function stringToRow(text: string): IBufferLine {
58-
const result = new BufferLine(text.length);
61+
const result = new BufferLine(TEST_STRING_CACHE, text.length);
5962
for (let i = 0; i < text.length; i++) {
6063
result.setCell(i, createCellData(0, text.charAt(i), 1));
6164
}
6265
return result;
6366
}
6467

6568
function stringArrayToRow(chars: string[]): IBufferLine {
66-
const line = new BufferLine(chars.length);
69+
const line = new BufferLine(TEST_STRING_CACHE, chars.length);
6770
chars.map((c, idx) => line.setCell(idx, createCellData(0, c, 1)));
6871
return line;
6972
}
@@ -118,7 +121,7 @@ describe('SelectionService', () => {
118121
[0, 'o', 1, 'o'.charCodeAt(0)],
119122
[0, 'o', 1, 'o'.charCodeAt(0)]
120123
];
121-
const line = new BufferLine(data.length);
124+
const line = new BufferLine(TEST_STRING_CACHE, data.length);
122125
for (let i = 0; i < data.length; ++i) line.setCell(i, CellData.fromCharData(data[i]));
123126
buffer.lines.set(0, line);
124127
// Ensure wide characters take up 2 columns

src/common/buffer/Buffer.test.ts

Lines changed: 118 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -8,22 +8,34 @@ import { Buffer } from 'common/buffer/Buffer';
88
import { CircularList } from 'common/CircularList';
99
import { MockOptionsService, MockBufferService, MockLogService, createCellData } from 'common/TestUtils.test';
1010
import { BufferLine, DEFAULT_ATTR_DATA } from 'common/buffer/BufferLine';
11+
import { BufferLineStringCache } from 'common/buffer/BufferLineStringCache';
1112
import { CellData } from 'common/buffer/CellData';
1213
import { ExtendedAttrs } from 'common/buffer/AttributeData';
1314

1415
const INIT_COLS = 80;
1516
const INIT_ROWS = 24;
1617
const INIT_SCROLLBACK = 1000;
18+
const TEST_STRING_CACHE = new BufferLineStringCache();
19+
20+
class TestBuffer extends Buffer {
21+
public getStringCache(): BufferLineStringCache {
22+
return (this as unknown as { _stringCache: BufferLineStringCache })._stringCache;
23+
}
24+
25+
public getStringCacheClearTimeout(): unknown {
26+
return (this.getStringCache() as unknown as { _clearTimeout: { value: unknown } })._clearTimeout.value;
27+
}
28+
}
1729

1830
describe('Buffer', () => {
1931
let optionsService: MockOptionsService;
2032
let bufferService: MockBufferService;
21-
let buffer: Buffer;
33+
let buffer: TestBuffer;
2234

2335
beforeEach(() => {
2436
optionsService = new MockOptionsService({ scrollback: INIT_SCROLLBACK });
2537
bufferService = new MockBufferService(INIT_COLS, INIT_ROWS);
26-
buffer = new Buffer(true, optionsService, bufferService, new MockLogService());
38+
buffer = new TestBuffer(true, optionsService, bufferService, new MockLogService());
2739
});
2840

2941
describe('constructor', () => {
@@ -151,7 +163,7 @@ describe('Buffer', () => {
151163

152164
describe('no scrollback', () => {
153165
it('should trim from the top of the buffer when the cursor reaches the bottom', () => {
154-
buffer = new Buffer(true, new MockOptionsService({ scrollback: 0 }), bufferService, new MockLogService());
166+
buffer = new TestBuffer(true, new MockOptionsService({ scrollback: 0 }), bufferService, new MockLogService());
155167
assert.equal(buffer.lines.maxLength, INIT_ROWS);
156168
buffer.y = INIT_ROWS - 1;
157169
buffer.fillViewportRows();
@@ -1054,7 +1066,7 @@ describe('Buffer', () => {
10541066
describe('buffer marked to have no scrollback', () => {
10551067
it('should always have a scrollback of 0', () => {
10561068
// Test size on initialization
1057-
buffer = new Buffer(false, new MockOptionsService({ scrollback: 1000 }), bufferService, new MockLogService());
1069+
buffer = new TestBuffer(false, new MockOptionsService({ scrollback: 1000 }), bufferService, new MockLogService());
10581070
buffer.fillViewportRows();
10591071
assert.equal(buffer.lines.maxLength, INIT_ROWS);
10601072
// Test size on buffer increase
@@ -1068,15 +1080,15 @@ describe('Buffer', () => {
10681080

10691081
describe('addMarker', () => {
10701082
it('should adjust a marker line when the buffer is trimmed', () => {
1071-
buffer = new Buffer(true, new MockOptionsService({ scrollback: 0 }), bufferService, new MockLogService());
1083+
buffer = new TestBuffer(true, new MockOptionsService({ scrollback: 0 }), bufferService, new MockLogService());
10721084
buffer.fillViewportRows();
10731085
const marker = buffer.addMarker(buffer.lines.length - 1);
10741086
assert.equal(marker.line, buffer.lines.length - 1);
10751087
buffer.lines.onTrimEmitter.fire(1);
10761088
assert.equal(marker.line, buffer.lines.length - 2);
10771089
});
10781090
it('should dispose of a marker if it is trimmed off the buffer', () => {
1079-
buffer = new Buffer(true, new MockOptionsService({ scrollback: 0 }), bufferService, new MockLogService());
1091+
buffer = new TestBuffer(true, new MockOptionsService({ scrollback: 0 }), bufferService, new MockLogService());
10801092
buffer.fillViewportRows();
10811093
assert.equal(buffer.markers.length, 0);
10821094
const marker = buffer.addMarker(0);
@@ -1088,7 +1100,7 @@ describe('Buffer', () => {
10881100
});
10891101
it('should call onDispose', () => {
10901102
const eventStack: string[] = [];
1091-
buffer = new Buffer(true, new MockOptionsService({ scrollback: 0 }), bufferService, new MockLogService());
1103+
buffer = new TestBuffer(true, new MockOptionsService({ scrollback: 0 }), bufferService, new MockLogService());
10921104
buffer.fillViewportRows();
10931105
assert.equal(buffer.markers.length, 0);
10941106
const marker = buffer.addMarker(0);
@@ -1104,7 +1116,7 @@ describe('Buffer', () => {
11041116

11051117
describe ('translateBufferLineToString', () => {
11061118
it('should handle selecting a section of ascii text', () => {
1107-
const line = new BufferLine(4);
1119+
const line = new BufferLine(TEST_STRING_CACHE, 4);
11081120
line.setCell(0, createCellData(0, 'a', 1));
11091121
line.setCell(1, createCellData(0, 'b', 1));
11101122
line.setCell(2, createCellData(0, 'c', 1));
@@ -1116,7 +1128,7 @@ describe('Buffer', () => {
11161128
});
11171129

11181130
it('should handle a cut-off double width character by including it', () => {
1119-
const line = new BufferLine(3);
1131+
const line = new BufferLine(TEST_STRING_CACHE, 3);
11201132
line.setCell(0, createCellData(0, '語', 2));
11211133
line.setCell(1, createCellData(0, '', 0));
11221134
line.setCell(2, createCellData(0, 'a', 1));
@@ -1127,7 +1139,7 @@ describe('Buffer', () => {
11271139
});
11281140

11291141
it('should handle a zero width character in the middle of the string by not including it', () => {
1130-
const line = new BufferLine(3);
1142+
const line = new BufferLine(TEST_STRING_CACHE, 3);
11311143
line.setCell(0, createCellData(0, '語', 2));
11321144
line.setCell(1, createCellData(0, '', 0));
11331145
line.setCell(2, createCellData(0, 'a', 1));
@@ -1144,7 +1156,7 @@ describe('Buffer', () => {
11441156
});
11451157

11461158
it('should handle single width emojis', () => {
1147-
const line = new BufferLine(2);
1159+
const line = new BufferLine(TEST_STRING_CACHE, 2);
11481160
line.setCell(0, createCellData(0, '😁', 1));
11491161
line.setCell(1, createCellData(0, 'a', 1));
11501162
buffer.lines.set(0, line);
@@ -1157,7 +1169,7 @@ describe('Buffer', () => {
11571169
});
11581170

11591171
it('should handle double width emojis', () => {
1160-
const line = new BufferLine(2);
1172+
const line = new BufferLine(TEST_STRING_CACHE, 2);
11611173
line.setCell(0, createCellData(0, '😁', 2));
11621174
line.setCell(1, createCellData(0, '', 0));
11631175
buffer.lines.set(0, line);
@@ -1168,7 +1180,7 @@ describe('Buffer', () => {
11681180
const str2 = buffer.translateBufferLineToString(0, true, 0, 2);
11691181
assert.equal(str2, '😁');
11701182

1171-
const line2 = new BufferLine(3);
1183+
const line2 = new BufferLine(TEST_STRING_CACHE, 3);
11721184
line2.setCell(0, createCellData(0, '😁', 2));
11731185
line2.setCell(1, createCellData(0, '', 0));
11741186
line2.setCell(2, createCellData(0, 'a', 1));
@@ -1179,6 +1191,99 @@ describe('Buffer', () => {
11791191
});
11801192
});
11811193

1194+
describe('line string cache cleanup', () => {
1195+
it('should clear shared cache entries with a single timer', () => {
1196+
const originalSetTimeout = globalThis.setTimeout;
1197+
const originalClearTimeout = globalThis.clearTimeout;
1198+
const originalDateNow = Date.now;
1199+
let timeoutId = 0;
1200+
let now = 0;
1201+
const clearedTimeouts: number[] = [];
1202+
const scheduledTimeouts = new Map<number, { delay: number, fire: () => void }>();
1203+
(globalThis as any).setTimeout = ((handler: (...args: any[]) => void, timeout?: number) => {
1204+
const id = ++timeoutId;
1205+
scheduledTimeouts.set(id, {
1206+
delay: timeout ?? 0,
1207+
fire: () => {
1208+
scheduledTimeouts.delete(id);
1209+
handler();
1210+
}
1211+
});
1212+
return id as ReturnType<typeof setTimeout>;
1213+
}) as typeof setTimeout;
1214+
(globalThis as any).clearTimeout = ((id: ReturnType<typeof setTimeout>) => {
1215+
const numericId = id as unknown as number;
1216+
clearedTimeouts.push(numericId);
1217+
scheduledTimeouts.delete(numericId);
1218+
}) as typeof clearTimeout;
1219+
Date.now = () => now;
1220+
try {
1221+
buffer.fillViewportRows();
1222+
buffer.lines.get(0)!.setCell(0, createCellData(0, 'a', 1));
1223+
buffer.lines.get(1)!.setCell(0, createCellData(0, 'b', 1));
1224+
1225+
assert.equal(buffer.translateBufferLineToString(0, false), `a${' '.repeat(INIT_COLS - 1)}`);
1226+
assert.equal(buffer.translateBufferLineToString(1, false), `b${' '.repeat(INIT_COLS - 1)}`);
1227+
1228+
const cache = buffer.getStringCache();
1229+
assert.equal(cache.entries.size, 2);
1230+
assert.ok(buffer.getStringCacheClearTimeout() !== undefined);
1231+
assert.equal(scheduledTimeouts.size, 1);
1232+
assert.equal([...scheduledTimeouts.values()][0].delay, 15000);
1233+
const initialTimerCreationCount = timeoutId;
1234+
1235+
now = 5000;
1236+
assert.equal(buffer.translateBufferLineToString(0, false), `a${' '.repeat(INIT_COLS - 1)}`);
1237+
assert.equal(timeoutId, initialTimerCreationCount);
1238+
assert.equal(scheduledTimeouts.size, 1);
1239+
assert.deepEqual(clearedTimeouts, []);
1240+
1241+
now = 15000;
1242+
[...scheduledTimeouts.values()][0].fire();
1243+
assert.equal(timeoutId, initialTimerCreationCount + 1);
1244+
assert.ok(buffer.getStringCacheClearTimeout() !== undefined);
1245+
assert.equal(scheduledTimeouts.size, 1);
1246+
assert.equal([...scheduledTimeouts.values()][0].delay, 5000);
1247+
1248+
now = 20000;
1249+
[...scheduledTimeouts.values()][0].fire();
1250+
1251+
assert.equal(cache.entries.size, 0);
1252+
assert.equal(buffer.getStringCacheClearTimeout(), undefined);
1253+
1254+
assert.equal(buffer.translateBufferLineToString(0, false), `a${' '.repeat(INIT_COLS - 1)}`);
1255+
assert.equal(cache.entries.size, 1);
1256+
} finally {
1257+
Date.now = originalDateNow;
1258+
globalThis.setTimeout = originalSetTimeout;
1259+
globalThis.clearTimeout = originalClearTimeout;
1260+
}
1261+
});
1262+
1263+
it('should reset line string cache state on clear and resize', () => {
1264+
buffer.fillViewportRows();
1265+
buffer.lines.get(0)!.setCell(0, createCellData(0, 'a', 1));
1266+
buffer.translateBufferLineToString(0, false);
1267+
1268+
const cache = buffer.getStringCache();
1269+
assert.equal(cache.entries.size, 1);
1270+
assert.ok(buffer.getStringCacheClearTimeout() !== undefined);
1271+
1272+
buffer.clear();
1273+
assert.equal(cache.entries.size, 0);
1274+
assert.equal(buffer.getStringCacheClearTimeout(), undefined);
1275+
1276+
buffer.fillViewportRows();
1277+
buffer.lines.get(0)!.setCell(0, createCellData(0, 'b', 1));
1278+
buffer.translateBufferLineToString(0, false);
1279+
assert.equal(cache.entries.size, 1);
1280+
1281+
buffer.resize(INIT_COLS - 1, INIT_ROWS);
1282+
assert.equal(cache.entries.size, 0);
1283+
assert.equal(buffer.getStringCacheClearTimeout(), undefined);
1284+
});
1285+
});
1286+
11821287
describe('memory cleanup after shrinking', () => {
11831288
it('should realign memory from idle task execution', async () => {
11841289
buffer.fillViewportRows();

0 commit comments

Comments
 (0)