Allow deleting from DisposableStore and add management to disposableTimeout

This commit is contained in:
Daniel Imms
2023-09-20 09:34:08 -07:00
parent 8b019170d9
commit b173dd31d2
3 changed files with 54 additions and 49 deletions

View File

@@ -6,7 +6,7 @@
import { CancellationToken, CancellationTokenSource } from 'vs/base/common/cancellation';
import { CancellationError } from 'vs/base/common/errors';
import { Emitter, Event } from 'vs/base/common/event';
import { Disposable, DisposableMap, IDisposable, MutableDisposable, toDisposable } from 'vs/base/common/lifecycle';
import { Disposable, DisposableMap, DisposableStore, IDisposable, MutableDisposable, toDisposable } from 'vs/base/common/lifecycle';
import { extUri as defaultExtUri, IExtUri } from 'vs/base/common/resources';
import { URI } from 'vs/base/common/uri';
import { setTimeout0 } from 'vs/base/common/platform';
@@ -490,9 +490,34 @@ export function timeout(millis: number, token?: CancellationToken): CancelablePr
});
}
export function disposableTimeout(handler: () => void, timeout = 0): IDisposable {
const timer = setTimeout(handler, timeout);
return toDisposable(() => clearTimeout(timer));
/**
* Creates a timeout that can be disposed using its returned value.
* @param handler The timeout handler.
* @param timeout An optional timeout in milliseconds.
* @param store An optional {@link DisposableStore} that will have the timeout disposable managed automatically.
*
* @example
* const store = new DisposableStore;
* // Call the timeout after 1000ms at which point it will be automatically
* // evicted from the store.
* const timeoutDisposable = disposableTimeout(() => {}, 1000, store);
*
* if (foo) {
* // Cancel the timeout and evict it from store.
* timeoutDisposable.dispose();
* }
*/
export function disposableTimeout(handler: () => void, timeout = 0, store?: DisposableStore): IDisposable {
const timer = setTimeout(() => {
handler();
disposable.dispose();
}, timeout);
const disposable = toDisposable(() => {
clearTimeout(timer);
store?.deleteAndLeak(disposable);
});
store?.add(disposable);
return disposable;
}
/**

View File

@@ -430,6 +430,28 @@ export class DisposableStore implements IDisposable {
return o;
}
/**
* Deletes a disposable from store and disposes of it. This will not throw or warn and proceed to dispose the
* disposable even when the disposable is not part in the store.
*/
public delete<T extends IDisposable>(o: T): void {
if (!o) {
return;
}
if ((o as unknown as DisposableStore) === this) {
throw new Error('Cannot dispose a disposable on itself!');
}
this._toDispose.delete(o);
o.dispose();
}
public deleteAndLeak<T extends IDisposable>(o: T): void {
if (!o) {
return;
}
this._toDispose.delete(o);
}
}
/**

View File

@@ -17,7 +17,7 @@ import { ErrorNoTelemetry, onUnexpectedError } from 'vs/base/common/errors';
import { Emitter, Event } from 'vs/base/common/event';
import { KeyCode } from 'vs/base/common/keyCodes';
import { ISeparator, template } from 'vs/base/common/labels';
import { Disposable, DisposableMap, IDisposable, MutableDisposable, dispose, toDisposable } from 'vs/base/common/lifecycle';
import { Disposable, IDisposable, MutableDisposable, dispose, toDisposable } from 'vs/base/common/lifecycle';
import { Schemas } from 'vs/base/common/network';
import * as path from 'vs/base/common/path';
import { OS, OperatingSystem, isMacintosh, isWindows } from 'vs/base/common/platform';
@@ -87,7 +87,6 @@ import { importAMDNodeModule } from 'vs/amdX';
import { ISimpleSelectedSuggestion } from 'vs/workbench/services/suggest/browser/simpleSuggestWidget';
import type { IMarker, Terminal as XTermTerminal } from 'xterm';
import { AccessibilityCommandId } from 'vs/workbench/contrib/accessibility/common/accessibilityCommands';
import { generateUuid } from 'vs/base/common/uuid';
const enum Constants {
/**
@@ -122,52 +121,11 @@ const shellIntegrationSupportedShellTypes = [
WindowsShellType.PowerShell
];
/**
* A wrapper around a {@link DisposableMap} that allows tracking transient disposables and then
* freeing their memory when they are no longer needed. This can be thought of as an alternative to
* a `IDisposable[]` for short lived disposables that allows declaring, initializing and registering
* the disposable in a single line.
*
* Some other benefits:
*
* - Disposable tracking is integrated thanks to using {@link DisposableMap} internally.
* - This is lighter weight and more concise than several {@link MutableDisposable}s.
*
* @example
* class Foo {
* private _transientDisposables = this._register(new TransientDisposableStore());
*
* constructor() {
* // Cancel the callback if Foo is disposed
* this._transientDisposables.addTimeout(() => { ... }, 1000);
* }
* }
*/
class TransientDisposableStore implements IDisposable {
private _map = new DisposableMap<string>();
addTimeout(handler: () => void, timeout = 0) {
const idx = generateUuid();
this._map.set(idx, disposableTimeout(() => {
handler();
this._map.deleteAndDispose(idx);
}, timeout));
}
// More functions can be added as needed
dispose() {
this._map.dispose();
}
}
export class TerminalInstance extends Disposable implements ITerminalInstance {
private static _lastKnownCanvasDimensions: ICanvasDimensions | undefined;
private static _lastKnownGridDimensions: IGridDimensions | undefined;
private static _instanceIdCounter = 1;
private readonly _transientDisposables = this._register(new TransientDisposableStore());
private readonly _scopedInstantiationService: IInstantiationService;
private readonly _processManager: ITerminalProcessManager;
@@ -801,7 +759,7 @@ export class TerminalInstance extends Disposable implements ITerminalInstance {
this._lineDataEventAddon = lineDataEventAddon;
// Delay the creation of the bell listener to avoid showing the bell when the terminal
// starts up or reconnects
this._transientDisposables.addTimeout(() => {
disposableTimeout(() => {
this._register(xterm.raw.onBell(() => {
if (this._configHelper.config.enableBell) {
this.statusList.add({
@@ -813,7 +771,7 @@ export class TerminalInstance extends Disposable implements ITerminalInstance {
this._audioCueService.playSound(AudioCue.terminalBell.sound.getSound());
}
}));
}, 1000);
}, 1000, this._store);
this._register(xterm.raw.onSelectionChange(async () => this._onSelectionChange()));
this._register(xterm.raw.buffer.onBufferChange(() => this._refreshAltBufferContextKey()));