Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 23 additions & 1 deletion addons/addon-search/src/SearchLineCache.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand All @@ -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];
}
Expand Down
4 changes: 4 additions & 0 deletions src/common/CoreTerminal.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}

Expand Down
19 changes: 19 additions & 0 deletions src/common/input/WriteBuffer.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 changes: 32 additions & 0 deletions src/common/input/WriteBuffer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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.
*/
Expand Down
Loading