diff --git a/src/common/Async.ts b/src/common/Async.ts index 7345f19da..276532fb8 100644 --- a/src/common/Async.ts +++ b/src/common/Async.ts @@ -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; diff --git a/src/common/services/DecorationService.ts b/src/common/services/DecorationService.ts index 645a63818..a582c18af 100644 --- a/src/common/services/DecorationService.ts +++ b/src/common/services/DecorationService.ts @@ -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 = new Map(); private readonly _decorations = new Set(); private readonly _bufferLineListeners = this._register(new MutableDisposable()); - + 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) {