Add MicrotaskTimer and use it for decoration line index sync

Introduce MicrotaskTimer as a TimeoutTimer-style helper that schedules a
single runner via queueMicrotask, with set and cancel but no cancelAndSet.

Use it in DecorationLineCache to batch insert/delete line-index updates after
marker line adjustments, replacing ad-hoc queueMicrotask scheduling.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
Daniel Imms
2026-05-26 09:52:53 -07:00
co-authored by Cursor
parent 9e72a4b6d3
commit 4a0cb5ae6b
2 changed files with 41 additions and 5 deletions
+36
View File
@@ -73,6 +73,42 @@ export class TimeoutTimer implements IDisposable {
}
}
/**
* Schedules a single runner on the microtask queue. Unlike {@link TimeoutTimer}, a scheduled
* microtask cannot be unqueued; {@link cancel} prevents the runner from executing if it has not
* run yet.
*/
export class MicrotaskTimer implements IDisposable {
private _isScheduled = false;
private _isDisposed = false;
public dispose(): void {
this.cancel();
this._isDisposed = true;
}
public cancel(): void {
this._isScheduled = false;
}
public set(runner: () => void): void {
if (this._isDisposed) {
throw new Error('Calling set on a disposed MicrotaskTimer');
}
if (this._isScheduled) {
return;
}
this._isScheduled = true;
queueMicrotask(() => {
if (!this._isScheduled) {
return;
}
this._isScheduled = false;
runner();
});
}
}
export class IntervalTimer implements IDisposable {
private _disposable: IDisposable | undefined;
private _isDisposed = false;
+5 -5
View File
@@ -4,6 +4,7 @@
*/
import type { IDeleteEvent, IInsertEvent } from 'common/CircularList';
import { MicrotaskTimer } from 'common/Async';
import { css } from 'common/Color';
import { Disposable, DisposableStore, MutableDisposable, toDisposable } from 'common/Lifecycle';
import { IBufferService, IDecorationService, IInternalDecoration, ILogService } from 'common/services/Services';
@@ -124,10 +125,12 @@ export class DecorationLineCache extends Disposable {
private readonly _decorationsByLine: Map<number, IInternalDecoration[]> = new Map();
private readonly _decorations = new Set<IInternalDecoration>();
private readonly _bufferLineListeners = this._register(new MutableDisposable<DisposableStore>());
private readonly _lineIndexSyncTimer = this._register(new MicrotaskTimer());
private _lineIndexSyncCallbacks: (() => void)[] = [];
public clear(): void {
this._lineIndexSyncCallbacks.length = 0;
this._lineIndexSyncTimer.cancel();
this._decorationsByLine.clear();
this._decorations.clear();
}
@@ -203,10 +206,7 @@ export class DecorationLineCache extends Disposable {
/** Re-index after marker line updates (buffer listeners may run before markers). */
private _scheduleLineIndexSync(callback: () => void): void {
this._lineIndexSyncCallbacks.push(callback);
if (this._lineIndexSyncCallbacks.length > 1) {
return;
}
queueMicrotask(() => {
this._lineIndexSyncTimer.set(() => {
const callbacks = this._lineIndexSyncCallbacks;
this._lineIndexSyncCallbacks = [];
for (const cb of callbacks) {