Merge branch 'master' into 4198_kitty

This commit is contained in:
Daniel Imms
2026-01-09 12:51:48 -08:00
committed by GitHub
7 changed files with 108 additions and 3 deletions
+23 -1
View File
@@ -38,6 +38,9 @@ export class SearchLineCache extends Disposable {
private _linesCache: LineCacheEntry[] | undefined;
private _linesCacheTimeout = this._register(new MutableDisposable());
private _linesCacheDisposables = this._register(new MutableDisposable());
// Track access to avoid recreating a timeout on every init call which occurs once per search
// result (findNext/findPrevious -> _highlightAllMatches -> find loop).
private _lastAccessTimestamp = 0;
constructor(private readonly _terminal: Terminal) {
super();
@@ -57,15 +60,34 @@ export class SearchLineCache extends Disposable {
);
}
this._linesCacheTimeout.value = disposableTimeout(() => this._destroyLinesCache(), Constants.LINES_CACHE_TIME_TO_LIVE);
this._lastAccessTimestamp = Date.now();
if (!this._linesCacheTimeout.value) {
this._scheduleLinesCacheTimeout(Constants.LINES_CACHE_TIME_TO_LIVE);
}
}
private _destroyLinesCache(): void {
this._linesCache = undefined;
this._lastAccessTimestamp = 0;
this._linesCacheDisposables.clear();
this._linesCacheTimeout.clear();
}
private _scheduleLinesCacheTimeout(delay: number): void {
this._linesCacheTimeout.value = disposableTimeout(() => {
if (!this._linesCache) {
return;
}
const now = Date.now();
const elapsed = now - this._lastAccessTimestamp;
if (elapsed >= Constants.LINES_CACHE_TIME_TO_LIVE) {
this._destroyLinesCache();
return;
}
this._scheduleLinesCacheTimeout(Constants.LINES_CACHE_TIME_TO_LIVE - elapsed);
}, delay);
}
public getLineFromCache(row: number): LineCacheEntry | undefined {
return this._linesCache?.[row];
}
+6 -2
View File
@@ -671,7 +671,9 @@ function sgrTest(term: Terminal): void {
{ ps: 47, name: 'Background White' },
{ ps: 49, name: 'Background default' },
{ ps: 53, name: 'Overlined' },
{ ps: 55, name: 'Not overlined' }
{ ps: 55, name: 'Not overlined' },
{ ps: 221, name: 'Not bold (kitty)' },
{ ps: 222, name: 'Not faint (kitty)' }
];
const maxNameLength = entries.reduce<number>((p, c) => Math.max(c.name.length, p), 0);
for (const e of entries) {
@@ -684,7 +686,9 @@ function sgrTest(term: Terminal): void {
const comboEntries: { ps: number[] }[] = [
{ ps: [1, 2, 3, 4, 5, 6, 7, 9] },
{ ps: [2, 41] },
{ ps: [4, 53] }
{ ps: [4, 53] },
{ ps: [1, 2, 221] },
{ ps: [1, 2, 222] }
];
term.write('\n\n\r');
term.writeln(`Combinations`);
+4
View File
@@ -176,6 +176,10 @@ export abstract class CoreTerminal extends Disposable implements ICoreTerminal {
x = Math.max(x, MINIMUM_COLS);
y = Math.max(y, MINIMUM_ROWS);
// Flush pending writes before resize to avoid race conditions where async
// writes are processed with incorrect dimensions
this._writeBuffer.flushSync();
this._bufferService.resize(x, y);
}
+16
View File
@@ -716,6 +716,22 @@ describe('InputHandler', () => {
await inputHandler.parseP('\x1b[22m');
assert.equal(!!inputHandler.curAttrData.isDim(), false);
});
it('SGR 221 resets bold only (kitty)', async () => {
await inputHandler.parseP('\x1b[1;2m');
assert.equal(!!inputHandler.curAttrData.isBold(), true);
assert.equal(!!inputHandler.curAttrData.isDim(), true);
await inputHandler.parseP('\x1b[221m');
assert.equal(!!inputHandler.curAttrData.isBold(), false);
assert.equal(!!inputHandler.curAttrData.isDim(), true);
});
it('SGR 222 resets faint only (kitty)', async () => {
await inputHandler.parseP('\x1b[1;2m');
assert.equal(!!inputHandler.curAttrData.isBold(), true);
assert.equal(!!inputHandler.curAttrData.isDim(), true);
await inputHandler.parseP('\x1b[222m');
assert.equal(!!inputHandler.curAttrData.isBold(), true);
assert.equal(!!inputHandler.curAttrData.isDim(), false);
});
it('italic', async () => {
await inputHandler.parseP('\x1b[3m');
assert.equal(!!inputHandler.curAttrData.isItalic(), true);
+8
View File
@@ -2538,6 +2538,8 @@ export class InputHandler extends Disposable implements IInputHandler {
* | 53 | Overlined. | #Y |
* | 55 | Not Overlined. | #Y |
* | 58 | Underline color: Extended color. | #P[Support for RGB and indexed colors, see below.] |
* | 221 | Not bold (kitty extension). | #Y |
* | 222 | Not faint (kitty extension). | #Y |
* | 90 - 97 | Bright foreground color (analogous to 30 - 37). | #Y |
* | 100 - 107 | Bright background color (analogous to 40 - 47). | #Y |
*
@@ -2670,6 +2672,12 @@ export class InputHandler extends Disposable implements IInputHandler {
} else if (p === 55) {
// not overline
attr.bg &= ~BgFlags.OVERLINE;
} else if (p === 221) {
// not bold (kitty extension)
attr.fg &= ~FgFlags.BOLD;
} else if (p === 222) {
// not faint (kitty extension)
attr.bg &= ~BgFlags.DIM;
} else if (p === 59) {
attr.extended = attr.extended.clone();
attr.extended.underlineColor = -1;
+19
View File
@@ -106,5 +106,24 @@ describe('WriteBuffer', () => {
wb.writeSync('1', 10);
assert.equal(last, '11'); // 1 + 10 sub calls = 11
});
it('flushSync processes all pending writes', done => {
wb.write('a', () => { cbStack.push('a'); });
wb.write('b', () => { cbStack.push('b'); });
wb.write('c', () => { cbStack.push('c'); });
wb.flushSync();
assert.deepEqual(stack, ['a', 'b', 'c']);
assert.deepEqual(cbStack, ['a', 'b', 'c']);
wb.write('x', () => { cbStack.push('x'); });
wb.write('', () => {
assert.deepEqual(stack, ['a', 'b', 'c', 'x', '']);
assert.deepEqual(cbStack, ['a', 'b', 'c', 'x']);
done();
});
});
it('flushSync with no pending writes is a no-op', () => {
wb.flushSync();
assert.deepEqual(stack, []);
assert.deepEqual(cbStack, []);
});
});
});
+32
View File
@@ -54,6 +54,38 @@ export class WriteBuffer extends Disposable {
this._didUserInput = true;
}
/**
* Flushes all pending writes synchronously. This is useful when you need to
* ensure all queued data is processed before performing an operation that
* depends upon everything being parsed like resize.
*
* Note: This is unreliable with async parser handlers as it does not wait for
* promises to resolve.
*/
public flushSync(): void {
// exit early if another sync write loop is active
if (this._isSyncWriting) {
return;
}
this._isSyncWriting = true;
// Process all pending chunks synchronously
let chunk: string | Uint8Array | undefined;
while (chunk = this._writeBuffer.shift()) {
this._action(chunk);
const cb = this._callbacks.shift();
if (cb) cb();
}
// Reset buffer state
this._pendingData = 0;
this._bufferOffset = 0x7FFFFFFF;
this._writeBuffer.length = 0;
this._callbacks.length = 0;
this._isSyncWriting = false;
}
/**
* @deprecated Unreliable, to be removed soon.
*/