From 2da44e17588f834d77c40fb2b0696e083fef763b Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Sat, 5 Aug 2023 08:51:23 -0700 Subject: [PATCH 1/2] Improve management of events/memory in terminal Part of #187082 --- .../capabilities/bufferMarkCapability.ts | 6 +- .../commandDetectionCapability.ts | 20 +-- .../capabilities/cwdDetectionCapability.ts | 5 +- .../common/xterm/shellIntegrationAddon.ts | 6 +- .../terminal/browser/terminalConfigHelper.ts | 6 +- .../terminal/browser/terminalEditorService.ts | 10 +- .../contrib/terminal/browser/terminalGroup.ts | 18 +-- .../terminal/browser/terminalGroupService.ts | 20 +-- .../terminal/browser/terminalInstance.ts | 116 +++++++++--------- .../browser/terminalInstanceService.ts | 2 +- .../browser/terminalProfileService.ts | 8 +- .../terminal/browser/terminalService.ts | 50 ++++---- .../terminal/browser/terminalTabsList.ts | 23 ++-- .../terminal/browser/xterm/suggestAddon.ts | 4 +- .../terminal/browser/xterm/xtermTerminal.ts | 16 +-- .../common/environmentVariableService.ts | 7 +- 16 files changed, 165 insertions(+), 152 deletions(-) diff --git a/src/vs/platform/terminal/common/capabilities/bufferMarkCapability.ts b/src/vs/platform/terminal/common/capabilities/bufferMarkCapability.ts index 229702af9d6..9759eeb3c59 100644 --- a/src/vs/platform/terminal/common/capabilities/bufferMarkCapability.ts +++ b/src/vs/platform/terminal/common/capabilities/bufferMarkCapability.ts @@ -4,6 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { Emitter } from 'vs/base/common/event'; +import { Disposable } from 'vs/base/common/lifecycle'; import { IBufferMarkCapability, TerminalCapability, IMarkProperties } from 'vs/platform/terminal/common/capabilities/capabilities'; // Importing types is safe in any layer // eslint-disable-next-line local/code-import-patterns @@ -13,19 +14,20 @@ import type { IMarker, Terminal } from 'xterm-headless'; * Manages "marks" in the buffer which are lines that are tracked when lines are added to or removed * from the buffer. */ -export class BufferMarkCapability implements IBufferMarkCapability { +export class BufferMarkCapability extends Disposable implements IBufferMarkCapability { readonly type = TerminalCapability.BufferMarkDetection; private _idToMarkerMap: Map = new Map(); private _anonymousMarkers: Map = new Map(); - private readonly _onMarkAdded = new Emitter(); + private readonly _onMarkAdded = this._register(new Emitter()); readonly onMarkAdded = this._onMarkAdded.event; constructor( private readonly _terminal: Terminal ) { + super(); } *markers(): IterableIterator { diff --git a/src/vs/platform/terminal/common/capabilities/commandDetectionCapability.ts b/src/vs/platform/terminal/common/capabilities/commandDetectionCapability.ts index 2fec93e7b7b..c9005bff55f 100644 --- a/src/vs/platform/terminal/common/capabilities/commandDetectionCapability.ts +++ b/src/vs/platform/terminal/common/capabilities/commandDetectionCapability.ts @@ -6,6 +6,7 @@ import { timeout } from 'vs/base/common/async'; import { debounce } from 'vs/base/common/decorators'; import { Emitter } from 'vs/base/common/event'; +import { Disposable } from 'vs/base/common/lifecycle'; import { ILogService } from 'vs/platform/log/common/log'; import { ICommandDetectionCapability, TerminalCapability, ITerminalCommand, IHandleCommandOptions, ICommandInvalidationRequest, CommandInvalidationReason, ISerializedTerminalCommand, ISerializedCommandDetectionCapability } from 'vs/platform/terminal/common/capabilities/capabilities'; import { ITerminalOutputMatch, ITerminalOutputMatcher } from 'vs/platform/terminal/common/terminal'; @@ -55,7 +56,7 @@ interface ITerminalDimensions { rows: number; } -export class CommandDetectionCapability implements ICommandDetectionCapability { +export class CommandDetectionCapability extends Disposable implements ICommandDetectionCapability { readonly type = TerminalCapability.CommandDetection; protected _commands: ITerminalCommand[] = []; @@ -97,29 +98,30 @@ export class CommandDetectionCapability implements ICommandDetectionCapability { return true; } - private readonly _onCommandStarted = new Emitter(); + private readonly _onCommandStarted = this._register(new Emitter()); readonly onCommandStarted = this._onCommandStarted.event; - private readonly _onBeforeCommandFinished = new Emitter(); + private readonly _onBeforeCommandFinished = this._register(new Emitter()); readonly onBeforeCommandFinished = this._onBeforeCommandFinished.event; - private readonly _onCommandFinished = new Emitter(); + private readonly _onCommandFinished = this._register(new Emitter()); readonly onCommandFinished = this._onCommandFinished.event; - private readonly _onCommandExecuted = new Emitter(); + private readonly _onCommandExecuted = this._register(new Emitter()); readonly onCommandExecuted = this._onCommandExecuted.event; - private readonly _onCommandInvalidated = new Emitter(); + private readonly _onCommandInvalidated = this._register(new Emitter()); readonly onCommandInvalidated = this._onCommandInvalidated.event; - private readonly _onCurrentCommandInvalidated = new Emitter(); + private readonly _onCurrentCommandInvalidated = this._register(new Emitter()); readonly onCurrentCommandInvalidated = this._onCurrentCommandInvalidated.event; constructor( private readonly _terminal: Terminal, private readonly _logService: ILogService ) { + super(); this._dimensions = { cols: this._terminal.cols, rows: this._terminal.rows }; - this._terminal.onResize(e => this._handleResize(e)); - this._terminal.onCursorMove(() => this._handleCursorMove()); + this._register(this._terminal.onResize(e => this._handleResize(e))); + this._register(this._terminal.onCursorMove(() => this._handleCursorMove())); this._setupClearListeners(); } diff --git a/src/vs/platform/terminal/common/capabilities/cwdDetectionCapability.ts b/src/vs/platform/terminal/common/capabilities/cwdDetectionCapability.ts index 7e3fcfa4a5d..96417aca6fc 100644 --- a/src/vs/platform/terminal/common/capabilities/cwdDetectionCapability.ts +++ b/src/vs/platform/terminal/common/capabilities/cwdDetectionCapability.ts @@ -4,9 +4,10 @@ *--------------------------------------------------------------------------------------------*/ import { Emitter } from 'vs/base/common/event'; +import { Disposable } from 'vs/base/common/lifecycle'; import { ICwdDetectionCapability, TerminalCapability } from 'vs/platform/terminal/common/capabilities/capabilities'; -export class CwdDetectionCapability implements ICwdDetectionCapability { +export class CwdDetectionCapability extends Disposable implements ICwdDetectionCapability { readonly type = TerminalCapability.CwdDetection; private _cwd = ''; private _cwds = new Map(); @@ -18,7 +19,7 @@ export class CwdDetectionCapability implements ICwdDetectionCapability { return Array.from(this._cwds.keys()); } - private readonly _onDidChangeCwd = new Emitter(); + private readonly _onDidChangeCwd = this._register(new Emitter()); readonly onDidChangeCwd = this._onDidChangeCwd.event; getCwd(): string { diff --git a/src/vs/platform/terminal/common/xterm/shellIntegrationAddon.ts b/src/vs/platform/terminal/common/xterm/shellIntegrationAddon.ts index a537f4ea557..16a760d57b1 100644 --- a/src/vs/platform/terminal/common/xterm/shellIntegrationAddon.ts +++ b/src/vs/platform/terminal/common/xterm/shellIntegrationAddon.ts @@ -500,7 +500,7 @@ export class ShellIntegrationAddon extends Disposable implements IShellIntegrati protected _createOrGetCwdDetection(): ICwdDetectionCapability { let cwdDetection = this.capabilities.get(TerminalCapability.CwdDetection); if (!cwdDetection) { - cwdDetection = new CwdDetectionCapability(); + cwdDetection = this._register(new CwdDetectionCapability()); this.capabilities.add(TerminalCapability.CwdDetection, cwdDetection); } return cwdDetection; @@ -509,7 +509,7 @@ export class ShellIntegrationAddon extends Disposable implements IShellIntegrati protected _createOrGetCommandDetection(terminal: Terminal): ICommandDetectionCapability { let commandDetection = this.capabilities.get(TerminalCapability.CommandDetection); if (!commandDetection) { - commandDetection = new CommandDetectionCapability(terminal, this._logService); + commandDetection = this._register(new CommandDetectionCapability(terminal, this._logService)); this.capabilities.add(TerminalCapability.CommandDetection, commandDetection); } return commandDetection; @@ -518,7 +518,7 @@ export class ShellIntegrationAddon extends Disposable implements IShellIntegrati protected _createOrGetBufferMarkDetection(terminal: Terminal): IBufferMarkCapability { let bufferMarkDetection = this.capabilities.get(TerminalCapability.BufferMarkDetection); if (!bufferMarkDetection) { - bufferMarkDetection = new BufferMarkCapability(terminal); + bufferMarkDetection = this._register(new BufferMarkCapability(terminal)); this.capabilities.add(TerminalCapability.BufferMarkDetection, bufferMarkDetection); } return bufferMarkDetection; diff --git a/src/vs/workbench/contrib/terminal/browser/terminalConfigHelper.ts b/src/vs/workbench/contrib/terminal/browser/terminalConfigHelper.ts index 1837ce21cb4..39d3a0ceb03 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalConfigHelper.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalConfigHelper.ts @@ -19,6 +19,7 @@ import { IProductService } from 'vs/platform/product/common/productService'; import { IXtermCore } from 'vs/workbench/contrib/terminal/browser/xterm-private'; import { IShellLaunchConfig } from 'vs/platform/terminal/common/terminal'; import { isLinux, isWindows } from 'vs/base/common/platform'; +import { Disposable } from 'vs/base/common/lifecycle'; const enum FontConstants { MinimumFontSize = 6, @@ -29,7 +30,7 @@ const enum FontConstants { * Encapsulates terminal configuration logic, the primary purpose of this file is so that platform * specific test cases can be written. */ -export class TerminalConfigHelper implements IBrowserTerminalConfigHelper { +export class TerminalConfigHelper extends Disposable implements IBrowserTerminalConfigHelper { panelContainer: HTMLElement | undefined; private _charMeasureElement: HTMLElement | undefined; @@ -37,7 +38,7 @@ export class TerminalConfigHelper implements IBrowserTerminalConfigHelper { protected _linuxDistro: LinuxDistro = LinuxDistro.Unknown; config!: ITerminalConfiguration; - private readonly _onConfigChanged = new Emitter(); + private readonly _onConfigChanged = this._register(new Emitter()); get onConfigChanged(): Event { return this._onConfigChanged.event; } constructor( @@ -47,6 +48,7 @@ export class TerminalConfigHelper implements IBrowserTerminalConfigHelper { @IInstantiationService private readonly _instantiationService: IInstantiationService, @IProductService private readonly _productService: IProductService, ) { + super(); this._updateConfig(); this._configurationService.onDidChangeConfiguration(e => { if (e.affectsConfiguration(TERMINAL_CONFIG_SECTION)) { diff --git a/src/vs/workbench/contrib/terminal/browser/terminalEditorService.ts b/src/vs/workbench/contrib/terminal/browser/terminalEditorService.ts index 02217becd6a..50d3e14ba2a 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalEditorService.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalEditorService.ts @@ -33,15 +33,15 @@ export class TerminalEditorService extends Disposable implements ITerminalEditor private _editorInputs: Map = new Map(); private _instanceDisposables: Map = new Map(); - private readonly _onDidDisposeInstance = new Emitter(); + private readonly _onDidDisposeInstance = this._register(new Emitter()); readonly onDidDisposeInstance = this._onDidDisposeInstance.event; - private readonly _onDidFocusInstance = new Emitter(); + private readonly _onDidFocusInstance = this._register(new Emitter()); readonly onDidFocusInstance = this._onDidFocusInstance.event; - private readonly _onDidChangeInstanceCapability = new Emitter(); + private readonly _onDidChangeInstanceCapability = this._register(new Emitter()); readonly onDidChangeInstanceCapability = this._onDidChangeInstanceCapability.event; - private readonly _onDidChangeActiveInstance = new Emitter(); + private readonly _onDidChangeActiveInstance = this._register(new Emitter()); readonly onDidChangeActiveInstance = this._onDidChangeActiveInstance.event; - private readonly _onDidChangeInstances = new Emitter(); + private readonly _onDidChangeInstances = this._register(new Emitter()); readonly onDidChangeInstances = this._onDidChangeInstances.event; constructor( diff --git a/src/vs/workbench/contrib/terminal/browser/terminalGroup.ts b/src/vs/workbench/contrib/terminal/browser/terminalGroup.ts index fd7ecfa56b2..f786225c38d 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalGroup.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalGroup.ts @@ -278,9 +278,9 @@ export class TerminalGroup extends Disposable implements ITerminalGroup { readonly onDisposed = this._onDisposed.event; private readonly _onInstancesChanged: Emitter = this._register(new Emitter()); readonly onInstancesChanged = this._onInstancesChanged.event; - private readonly _onDidChangeActiveInstance = new Emitter(); + private readonly _onDidChangeActiveInstance = this._register(new Emitter()); readonly onDidChangeActiveInstance = this._onDidChangeActiveInstance.event; - private readonly _onPanelOrientationChanged = new Emitter(); + private readonly _onPanelOrientationChanged = this._register(new Emitter()); readonly onPanelOrientationChanged = this._onPanelOrientationChanged.event; constructor( @@ -380,13 +380,6 @@ export class TerminalGroup extends Disposable implements ITerminalGroup { removeInstance(instance: ITerminalInstance) { this._removeInstance(instance); - - // Dispose instance event listeners - const disposables = this._instanceDisposables.get(instance.instanceId); - if (disposables) { - dispose(disposables); - this._instanceDisposables.delete(instance.instanceId); - } } private _removeInstance(instance: ITerminalInstance) { @@ -418,6 +411,13 @@ export class TerminalGroup extends Disposable implements ITerminalGroup { } else { this._onInstancesChanged.fire(); } + + // Dispose instance event listeners + const disposables = this._instanceDisposables.get(instance.instanceId); + if (disposables) { + dispose(disposables); + this._instanceDisposables.delete(instance.instanceId); + } } moveInstance(instance: ITerminalInstance, index: number): void { diff --git a/src/vs/workbench/contrib/terminal/browser/terminalGroupService.ts b/src/vs/workbench/contrib/terminal/browser/terminalGroupService.ts index 990072a3570..d321432ae2a 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalGroupService.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalGroupService.ts @@ -33,27 +33,27 @@ export class TerminalGroupService extends Disposable implements ITerminalGroupSe private _container: HTMLElement | undefined; - private readonly _onDidChangeActiveGroup = new Emitter(); + private readonly _onDidChangeActiveGroup = this._register(new Emitter()); readonly onDidChangeActiveGroup = this._onDidChangeActiveGroup.event; - private readonly _onDidDisposeGroup = new Emitter(); + private readonly _onDidDisposeGroup = this._register(new Emitter()); readonly onDidDisposeGroup = this._onDidDisposeGroup.event; - private readonly _onDidChangeGroups = new Emitter(); + private readonly _onDidChangeGroups = this._register(new Emitter()); readonly onDidChangeGroups = this._onDidChangeGroups.event; - private readonly _onDidShow = new Emitter(); + private readonly _onDidShow = this._register(new Emitter()); readonly onDidShow = this._onDidShow.event; - private readonly _onDidDisposeInstance = new Emitter(); + private readonly _onDidDisposeInstance = this._register(new Emitter()); readonly onDidDisposeInstance = this._onDidDisposeInstance.event; - private readonly _onDidFocusInstance = new Emitter(); + private readonly _onDidFocusInstance = this._register(new Emitter()); readonly onDidFocusInstance = this._onDidFocusInstance.event; - private readonly _onDidChangeActiveInstance = new Emitter(); + private readonly _onDidChangeActiveInstance = this._register(new Emitter()); readonly onDidChangeActiveInstance = this._onDidChangeActiveInstance.event; - private readonly _onDidChangeInstances = new Emitter(); + private readonly _onDidChangeInstances = this._register(new Emitter()); readonly onDidChangeInstances = this._onDidChangeInstances.event; - private readonly _onDidChangeInstanceCapability = new Emitter(); + private readonly _onDidChangeInstanceCapability = this._register(new Emitter()); readonly onDidChangeInstanceCapability = this._onDidChangeInstanceCapability.event; - private readonly _onDidChangePanelOrientation = new Emitter(); + private readonly _onDidChangePanelOrientation = this._register(new Emitter()); readonly onDidChangePanelOrientation = this._onDidChangePanelOrientation.event; constructor( diff --git a/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts b/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts index 3d65f2cf44b..be186d2e2ad 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts @@ -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, IDisposable, 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'; @@ -169,9 +169,9 @@ export class TerminalInstance extends Disposable implements ITerminalInstance { private _containerReadyBarrier: AutoOpenBarrier; private _attachBarrier: AutoOpenBarrier; private _icon: TerminalIcon | undefined; - private _messageTitleDisposable: IDisposable | undefined; + private _messageTitleDisposable: MutableDisposable = this._register(new MutableDisposable()); private _widgetManager: TerminalWidgetManager = new TerminalWidgetManager(); - private _dndObserver: IDisposable | undefined; + private _dndObserver: MutableDisposable = this._register(new MutableDisposable()); private _lastLayoutDimensions: dom.Dimension | undefined; private _hasHadInput: boolean; private _description?: string; @@ -246,7 +246,7 @@ export class TerminalInstance extends Disposable implements ITerminalInstance { get exitCode(): number | undefined { return this._exitCode; } get exitReason(): TerminalExitReason | undefined { return this._exitReason; } get hadFocusOnExit(): boolean { return this._hadFocusOnExit; } - get isTitleSetByProcess(): boolean { return !!this._messageTitleDisposable; } + get isTitleSetByProcess(): boolean { return !!this._messageTitleDisposable.value; } get shellLaunchConfig(): IShellLaunchConfig { return this._shellLaunchConfig; } get shellType(): TerminalShellType | undefined { return this._shellType; } get os(): OperatingSystem | undefined { return this._processManager.os; } @@ -451,8 +451,6 @@ export class TerminalInstance extends Disposable implements ITerminalInstance { this._initDimensions(); this._processManager = this._createProcessManager(); - this._register(toDisposable(() => this._dndObserver?.dispose())); - this._containerReadyBarrier = new AutoOpenBarrier(Constants.WaitForContainerThreshold); this._attachBarrier = new AutoOpenBarrier(1000); this._xtermReadyPromise = this._createXterm(); @@ -528,10 +526,10 @@ export class TerminalInstance extends Disposable implements ITerminalInstance { e.affectsConfiguration(TerminalSettingId.TerminalTitle) || e.affectsConfiguration(TerminalSettingId.TerminalTitleSeparator) || e.affectsConfiguration(TerminalSettingId.TerminalDescription)) { - this._labelComputer?.refreshLabel(); + this._labelComputer?.refreshLabel(this); } })); - this._register(this._workspaceContextService.onDidChangeWorkspaceFolders(() => this._labelComputer?.refreshLabel())); + this._register(this._workspaceContextService.onDidChangeWorkspaceFolders(() => this._labelComputer?.refreshLabel(this))); this._register(this.onDidBlur(() => this.xterm?.suggestController?.hideSuggestWidget())); // Clear out initial data events after 10 seconds, hopefully extension hosts are up and @@ -565,6 +563,14 @@ export class TerminalInstance extends Disposable implements ITerminalInstance { }); this.onDisposed(() => { contribution.dispose(); + this._contributions.delete(desc.id); + // Just in case to prevent potential future memory leaks due to cyclic dependency. + if ('instance' in contribution) { + delete contribution.instance; + } + if ('_instance' in contribution) { + delete contribution._instance; + } }); } } @@ -746,13 +752,13 @@ export class TerminalInstance extends Disposable implements ITerminalInstance { // Write initial text, deferring onLineFeed listener when applicable to avoid firing // onLineData events containing initialText const initialTextWrittenPromise = this._shellLaunchConfig.initialText ? new Promise(r => this._writeInitialText(xterm, r)) : undefined; - const lineDataEventAddon = new LineDataEventAddon(initialTextWrittenPromise); + const lineDataEventAddon = this._register(new LineDataEventAddon(initialTextWrittenPromise)); lineDataEventAddon.onLineData(e => this._onLineData.fire(e)); this._lineDataEventAddon = lineDataEventAddon; // Delay the creation of the bell listener to avoid showing the bell when the terminal // starts up or reconnects setTimeout(() => { - xterm.raw.onBell(() => { + this._register(xterm.raw.onBell(() => { if (this._configHelper.config.enableBell) { this.statusList.add({ id: TerminalStatus.Bell, @@ -762,17 +768,17 @@ export class TerminalInstance extends Disposable implements ITerminalInstance { }, this._configHelper.config.bellDuration); this._audioCueService.playSound(AudioCue.terminalBell.sound.getSound()); } - }); + })); }, 1000); - xterm.raw.onSelectionChange(async () => this._onSelectionChange()); - xterm.raw.buffer.onBufferChange(() => this._refreshAltBufferContextKey()); + this._register(xterm.raw.onSelectionChange(async () => this._onSelectionChange())); + this._register(xterm.raw.buffer.onBufferChange(() => this._refreshAltBufferContextKey())); this._processManager.onProcessData(e => this._onProcessData(e)); - xterm.raw.onData(async data => { + this._register(xterm.raw.onData(async data => { await this._processManager.write(data); this._onDidInputData.fire(this); - }); - xterm.raw.onBinary(data => this._processManager.processBinary(data)); + })); + this._register(xterm.raw.onBinary(data => this._processManager.processBinary(data))); // Init winpty compat and link handler after process creation as they rely on the // underlying process OS this._processManager.onProcessReady(async (processTraits) => { @@ -1047,14 +1053,13 @@ export class TerminalInstance extends Disposable implements ITerminalInstance { } private _initDragAndDrop(container: HTMLElement) { - this._dndObserver?.dispose(); - const dndController = this._scopedInstantiationService.createInstance(TerminalInstanceDragAndDropController, container); + const dndController = this._register(this._scopedInstantiationService.createInstance(TerminalInstanceDragAndDropController, container)); dndController.onDropTerminal(e => this._onRequestAddInstanceToGroup.fire(e)); dndController.onDropFile(async path => { this.focus(); await this.sendPath(path, false); }); - this._dndObserver = new dom.DragAndDropObserver(container, dndController); + this._dndObserver.value = new dom.DragAndDropObserver(container, dndController); } hasSelection(): boolean { @@ -1340,12 +1345,12 @@ export class TerminalInstance extends Disposable implements ITerminalInstance { // Set the initial name based on the _resolved_ shell launch config, this will also // ensure the resolved icon gets shown if (!this._labelComputer) { - this._labelComputer = this._register(this._scopedInstantiationService.createInstance(TerminalLabelComputer, this._configHelper, this)); - this._labelComputer.onDidChangeLabel(e => { + this._labelComputer = this._register(this._scopedInstantiationService.createInstance(TerminalLabelComputer, this._configHelper)); + this._register(this._labelComputer.onDidChangeLabel(e => { this._title = e.title; this._description = e.description; this._onTitleChanged.fire(this); - }); + })); } if (this._shellLaunchConfig.name) { this._setTitle(this._shellLaunchConfig.name, TitleEventSource.Api); @@ -1354,7 +1359,7 @@ export class TerminalInstance extends Disposable implements ITerminalInstance { // _xtermReadyPromise is ready constructed since this is called from the ctor setTimeout(() => { this._xtermReadyPromise.then(xterm => { - this._messageTitleDisposable = xterm.raw.onTitleChange(e => this._onTitleChange(e)); + this._messageTitleDisposable.value = xterm.raw.onTitleChange(e => this._onTitleChange(e)); }); }); this._setTitle(this._shellLaunchConfig.executable, TitleEventSource.Process); @@ -1365,7 +1370,7 @@ export class TerminalInstance extends Disposable implements ITerminalInstance { switch (type) { case ProcessPropertyType.Cwd: this._cwd = value; - this._labelComputer?.refreshLabel(); + this._labelComputer?.refreshLabel(this); break; case ProcessPropertyType.InitialCwd: this._initialCwd = value; @@ -1913,8 +1918,7 @@ export class TerminalInstance extends Disposable implements ITerminalInstance { // If the title has not been set by the API or the rename command, unregister the handler that // automatically updates the terminal name this._staticTitle = title; - dispose(this._messageTitleDisposable); - this._messageTitleDisposable = undefined; + this._messageTitleDisposable.value = undefined; break; case TitleEventSource.Sequence: // On Windows, some shells will fire this with the full path which we want to trim @@ -1959,7 +1963,7 @@ export class TerminalInstance extends Disposable implements ITerminalInstance { return; } this._fixedCols = this._parseFixedDimension(cols); - this._labelComputer?.refreshLabel(); + this._labelComputer?.refreshLabel(this); this._terminalHasFixedWidth.set(!!this._fixedCols); const rows = await this._quickInputService.input({ title: nls.localize('setTerminalDimensionsRow', "Set Fixed Dimensions: Row"), @@ -1970,7 +1974,7 @@ export class TerminalInstance extends Disposable implements ITerminalInstance { return; } this._fixedRows = this._parseFixedDimension(rows); - this._labelComputer?.refreshLabel(); + this._labelComputer?.refreshLabel(this); await this._refreshScrollbar(); this._resize(); this.focus(); @@ -2008,7 +2012,7 @@ export class TerminalInstance extends Disposable implements ITerminalInstance { } } await this._refreshScrollbar(); - this._labelComputer?.refreshLabel(); + this._labelComputer?.refreshLabel(this); this.focus(); } @@ -2152,7 +2156,7 @@ export class TerminalInstance extends Disposable implements ITerminalInstance { title = this._updateTitleProperties(title, eventSource); const titleChanged = title !== this._title; this._title = title; - this._labelComputer?.refreshLabel(reset); + this._labelComputer?.refreshLabel(this, reset); this._setAriaLabel(this.xterm?.raw, this._instanceId, this._title); if (titleChanged) { @@ -2251,9 +2255,9 @@ export class TerminalInstance extends Disposable implements ITerminalInstance { class TerminalInstanceDragAndDropController extends Disposable implements dom.IDragAndDropObserverCallbacks { private _dropOverlay?: HTMLElement; - private readonly _onDropFile = new Emitter(); + private readonly _onDropFile = this._register(new Emitter()); get onDropFile(): Event { return this._onDropFile.event; } - private readonly _onDropTerminal = new Emitter(); + private readonly _onDropTerminal = this._register(new Emitter()); get onDropTerminal(): Event { return this._onDropTerminal.event; } constructor( @@ -2405,65 +2409,65 @@ export class TerminalLabelComputer extends Disposable { constructor( private readonly _configHelper: TerminalConfigHelper, - private readonly _instance: Pick, @IFileService private readonly _fileService: IFileService, @IWorkspaceContextService private readonly _workspaceContextService: IWorkspaceContextService ) { super(); } - refreshLabel(reset?: boolean): void { - this._title = this.computeLabel(this._configHelper.config.tabs.title, TerminalLabelType.Title, reset); - this._description = this.computeLabel(this._configHelper.config.tabs.description, TerminalLabelType.Description); - if (this._title !== this._instance.title || this._description !== this._instance.description || reset) { + refreshLabel(instance: Pick, reset?: boolean): void { + this._title = this.computeLabel(instance, this._configHelper.config.tabs.title, TerminalLabelType.Title, reset); + this._description = this.computeLabel(instance, this._configHelper.config.tabs.description, TerminalLabelType.Description); + if (this._title !== instance.title || this._description !== instance.description || reset) { this._onDidChangeLabel.fire({ title: this._title, description: this._description }); } } computeLabel( + instance: Pick, labelTemplate: string, labelType: TerminalLabelType, reset?: boolean ) { - const type = this._instance.shellLaunchConfig.attachPersistentProcess?.type || this._instance.shellLaunchConfig.type; + const type = instance.shellLaunchConfig.attachPersistentProcess?.type || instance.shellLaunchConfig.type; const templateProperties: ITerminalLabelTemplateProperties = { - cwd: this._instance.cwd || this._instance.initialCwd || '', + cwd: instance.cwd || instance.initialCwd || '', cwdFolder: '', - workspaceFolder: this._instance.workspaceFolder ? path.basename(this._instance.workspaceFolder.uri.fsPath) : undefined, + workspaceFolder: instance.workspaceFolder ? path.basename(instance.workspaceFolder.uri.fsPath) : undefined, local: type === 'Local' ? type : undefined, - process: this._instance.processName, - sequence: this._instance.sequence, + process: instance.processName, + sequence: instance.sequence, task: type === 'Task' ? type : undefined, - fixedDimensions: this._instance.fixedCols - ? (this._instance.fixedRows ? `\u2194${this._instance.fixedCols} \u2195${this._instance.fixedRows}` : `\u2194${this._instance.fixedCols}`) - : (this._instance.fixedRows ? `\u2195${this._instance.fixedRows}` : ''), + fixedDimensions: instance.fixedCols + ? (instance.fixedRows ? `\u2194${instance.fixedCols} \u2195${instance.fixedRows}` : `\u2194${instance.fixedCols}`) + : (instance.fixedRows ? `\u2195${instance.fixedRows}` : ''), separator: { label: this._configHelper.config.tabs.separator } }; labelTemplate = labelTemplate.trim(); if (!labelTemplate) { - return labelType === TerminalLabelType.Title ? (this._instance.processName || '') : ''; + return labelType === TerminalLabelType.Title ? (instance.processName || '') : ''; } - if (!reset && this._instance.staticTitle && labelType === TerminalLabelType.Title) { - return this._instance.staticTitle.replace(/[\n\r\t]/g, '') || templateProperties.process?.replace(/[\n\r\t]/g, '') || ''; + if (!reset && instance.staticTitle && labelType === TerminalLabelType.Title) { + return instance.staticTitle.replace(/[\n\r\t]/g, '') || templateProperties.process?.replace(/[\n\r\t]/g, '') || ''; } - const detection = this._instance.capabilities.has(TerminalCapability.CwdDetection) || this._instance.capabilities.has(TerminalCapability.NaiveCwdDetection); + const detection = instance.capabilities.has(TerminalCapability.CwdDetection) || instance.capabilities.has(TerminalCapability.NaiveCwdDetection); const folders = this._workspaceContextService.getWorkspace().folders; const multiRootWorkspace = folders.length > 1; // Only set cwdFolder if detection is on - if (templateProperties.cwd && detection && (!this._instance.shellLaunchConfig.isFeatureTerminal || labelType === TerminalLabelType.Title)) { + if (templateProperties.cwd && detection && (!instance.shellLaunchConfig.isFeatureTerminal || labelType === TerminalLabelType.Title)) { const cwdUri = URI.from({ - scheme: this._instance.workspaceFolder?.uri.scheme || Schemas.file, - path: this._instance.cwd ? path.resolve(this._instance.cwd) : undefined + scheme: instance.workspaceFolder?.uri.scheme || Schemas.file, + path: instance.cwd ? path.resolve(instance.cwd) : undefined }); // Multi-root workspaces always show cwdFolder to disambiguate them, otherwise only show // when it differs from the workspace folder in which it was launched from let showCwd = false; if (multiRootWorkspace) { showCwd = true; - } else if (this._instance.workspaceFolder?.uri) { - const caseSensitive = this._fileService.hasCapability(this._instance.workspaceFolder.uri, FileSystemProviderCapabilities.PathCaseSensitive); - showCwd = cwdUri.fsPath.localeCompare(this._instance.workspaceFolder.uri.fsPath, undefined, { sensitivity: caseSensitive ? 'case' : 'base' }) !== 0; + } else if (instance.workspaceFolder?.uri) { + const caseSensitive = this._fileService.hasCapability(instance.workspaceFolder.uri, FileSystemProviderCapabilities.PathCaseSensitive); + showCwd = cwdUri.fsPath.localeCompare(instance.workspaceFolder.uri.fsPath, undefined, { sensitivity: caseSensitive ? 'case' : 'base' }) !== 0; } if (showCwd) { @@ -2473,7 +2477,7 @@ export class TerminalLabelComputer extends Disposable { // Remove special characters that could mess with rendering const label = template(labelTemplate, (templateProperties as unknown) as { [key: string]: string | ISeparator | undefined | null }).replace(/[\n\r\t]/g, '').trim(); - return label === '' && labelType === TerminalLabelType.Title ? (this._instance.processName || '') : label; + return label === '' && labelType === TerminalLabelType.Title ? (instance.processName || '') : label; } } diff --git a/src/vs/workbench/contrib/terminal/browser/terminalInstanceService.ts b/src/vs/workbench/contrib/terminal/browser/terminalInstanceService.ts index ffa9c55f759..d341cee3178 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalInstanceService.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalInstanceService.ts @@ -25,7 +25,7 @@ export class TerminalInstanceService extends Disposable implements ITerminalInst private _configHelper: TerminalConfigHelper; private _backendRegistration = new Map; resolve: () => void }>(); - private readonly _onDidCreateInstance = new Emitter(); + private readonly _onDidCreateInstance = this._register(new Emitter()); get onDidCreateInstance(): Event { return this._onDidCreateInstance.event; } constructor( diff --git a/src/vs/workbench/contrib/terminal/browser/terminalProfileService.ts b/src/vs/workbench/contrib/terminal/browser/terminalProfileService.ts index d161a40cb6e..e91c102646f 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalProfileService.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalProfileService.ts @@ -7,7 +7,7 @@ import { equals } from 'vs/base/common/arrays'; import { AutoOpenBarrier } from 'vs/base/common/async'; import { throttle } from 'vs/base/common/decorators'; import { Emitter, Event } from 'vs/base/common/event'; -import { IDisposable, toDisposable } from 'vs/base/common/lifecycle'; +import { Disposable, IDisposable, toDisposable } from 'vs/base/common/lifecycle'; import { isMacintosh, isWeb, isWindows, OperatingSystem, OS } from 'vs/base/common/platform'; import { ConfigurationTarget, IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { IContextKey, IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; @@ -27,7 +27,7 @@ import { IRemoteAgentService } from 'vs/workbench/services/remote/common/remoteA * Links TerminalService with TerminalProfileResolverService * and keeps the available terminal profiles updated */ -export class TerminalProfileService implements ITerminalProfileService { +export class TerminalProfileService extends Disposable implements ITerminalProfileService { declare _serviceBrand: undefined; private _webExtensionContributedProfileContextKey: IContextKey; @@ -39,7 +39,7 @@ export class TerminalProfileService implements ITerminalProfileService { private _platformConfigJustRefreshed = false; private readonly _profileProviders: Map> = new Map(); - private readonly _onDidChangeAvailableProfiles = new Emitter(); + private readonly _onDidChangeAvailableProfiles = this._register(new Emitter()); get onDidChangeAvailableProfiles(): Event { return this._onDidChangeAvailableProfiles.event; } get profilesReady(): Promise { return this._profilesReadyPromise; } @@ -62,6 +62,8 @@ export class TerminalProfileService implements ITerminalProfileService { @IWorkbenchEnvironmentService private readonly _environmentService: IWorkbenchEnvironmentService, @ITerminalInstanceService private readonly _terminalInstanceService: ITerminalInstanceService ) { + super(); + // in web, we don't want to show the dropdown unless there's a web extension // that contributes a profile this._extensionService.onDidChangeExtensions(() => this.refreshAvailableProfiles()); diff --git a/src/vs/workbench/contrib/terminal/browser/terminalService.ts b/src/vs/workbench/contrib/terminal/browser/terminalService.ts index 27ff2a40b23..23b4cbdfd3d 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalService.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalService.ts @@ -7,7 +7,7 @@ import * as dom from 'vs/base/browser/dom'; import { DeferredPromise, timeout } from 'vs/base/common/async'; import { debounce } from 'vs/base/common/decorators'; import { Emitter, Event } from 'vs/base/common/event'; -import { dispose, IDisposable, toDisposable } from 'vs/base/common/lifecycle'; +import { Disposable, dispose, IDisposable, toDisposable } from 'vs/base/common/lifecycle'; import { Schemas } from 'vs/base/common/network'; import { isMacintosh, isWeb } from 'vs/base/common/platform'; import { URI } from 'vs/base/common/uri'; @@ -53,7 +53,7 @@ import { TerminalCapabilityStore } from 'vs/platform/terminal/common/capabilitie import { ITimerService } from 'vs/workbench/services/timer/browser/timerService'; import { mark } from 'vs/base/common/performance'; -export class TerminalService implements ITerminalService { +export class TerminalService extends Disposable implements ITerminalService { declare _serviceBrand: undefined; private _hostActiveTerminals: Map = new Map(); @@ -120,47 +120,47 @@ export class TerminalService implements ITerminalService { private _editingTerminal: ITerminalInstance | undefined; - private readonly _onDidChangeActiveGroup = new Emitter(); + private readonly _onDidChangeActiveGroup = this._register(new Emitter()); get onDidChangeActiveGroup(): Event { return this._onDidChangeActiveGroup.event; } - private readonly _onDidCreateInstance = new Emitter(); + private readonly _onDidCreateInstance = this._register(new Emitter()); get onDidCreateInstance(): Event { return this._onDidCreateInstance.event; } - private readonly _onDidDisposeInstance = new Emitter(); + private readonly _onDidDisposeInstance = this._register(new Emitter()); get onDidDisposeInstance(): Event { return this._onDidDisposeInstance.event; } - private readonly _onDidFocusInstance = new Emitter(); + private readonly _onDidFocusInstance = this._register(new Emitter()); get onDidFocusInstance(): Event { return this._onDidFocusInstance.event; } - private readonly _onDidReceiveProcessId = new Emitter(); + private readonly _onDidReceiveProcessId = this._register(new Emitter()); get onDidReceiveProcessId(): Event { return this._onDidReceiveProcessId.event; } - private readonly _onDidRequestStartExtensionTerminal = new Emitter(); + private readonly _onDidRequestStartExtensionTerminal = this._register(new Emitter()); get onDidRequestStartExtensionTerminal(): Event { return this._onDidRequestStartExtensionTerminal.event; } - private readonly _onDidChangeInstanceDimensions = new Emitter(); + private readonly _onDidChangeInstanceDimensions = this._register(new Emitter()); get onDidChangeInstanceDimensions(): Event { return this._onDidChangeInstanceDimensions.event; } - private readonly _onDidMaxiumumDimensionsChange = new Emitter(); + private readonly _onDidMaxiumumDimensionsChange = this._register(new Emitter()); get onDidMaximumDimensionsChange(): Event { return this._onDidMaxiumumDimensionsChange.event; } - private readonly _onDidChangeInstanceCapability = new Emitter(); + private readonly _onDidChangeInstanceCapability = this._register(new Emitter()); get onDidChangeInstanceCapability(): Event { return this._onDidChangeInstanceCapability.event; } - private readonly _onDidChangeInstances = new Emitter(); + private readonly _onDidChangeInstances = this._register(new Emitter()); get onDidChangeInstances(): Event { return this._onDidChangeInstances.event; } - private readonly _onDidChangeInstanceTitle = new Emitter(); + private readonly _onDidChangeInstanceTitle = this._register(new Emitter()); get onDidChangeInstanceTitle(): Event { return this._onDidChangeInstanceTitle.event; } - private readonly _onDidChangeInstanceIcon = new Emitter<{ instance: ITerminalInstance; userInitiated: boolean }>(); + private readonly _onDidChangeInstanceIcon = this._register(new Emitter<{ instance: ITerminalInstance; userInitiated: boolean }>()); get onDidChangeInstanceIcon(): Event<{ instance: ITerminalInstance; userInitiated: boolean }> { return this._onDidChangeInstanceIcon.event; } - private readonly _onDidChangeInstanceColor = new Emitter<{ instance: ITerminalInstance; userInitiated: boolean }>(); + private readonly _onDidChangeInstanceColor = this._register(new Emitter<{ instance: ITerminalInstance; userInitiated: boolean }>()); get onDidChangeInstanceColor(): Event<{ instance: ITerminalInstance; userInitiated: boolean }> { return this._onDidChangeInstanceColor.event; } - private readonly _onDidChangeActiveInstance = new Emitter(); + private readonly _onDidChangeActiveInstance = this._register(new Emitter()); get onDidChangeActiveInstance(): Event { return this._onDidChangeActiveInstance.event; } - private readonly _onDidChangeInstancePrimaryStatus = new Emitter(); + private readonly _onDidChangeInstancePrimaryStatus = this._register(new Emitter()); get onDidChangeInstancePrimaryStatus(): Event { return this._onDidChangeInstancePrimaryStatus.event; } - private readonly _onDidInputInstanceData = new Emitter(); + private readonly _onDidInputInstanceData = this._register(new Emitter()); get onDidInputInstanceData(): Event { return this._onDidInputInstanceData.event; } - private readonly _onDidChangeSelection = new Emitter(); + private readonly _onDidChangeSelection = this._register(new Emitter()); get onDidChangeSelection(): Event { return this._onDidChangeSelection.event; } - private readonly _onDidDisposeGroup = new Emitter(); + private readonly _onDidDisposeGroup = this._register(new Emitter()); get onDidDisposeGroup(): Event { return this._onDidDisposeGroup.event; } - private readonly _onDidChangeGroups = new Emitter(); + private readonly _onDidChangeGroups = this._register(new Emitter()); get onDidChangeGroups(): Event { return this._onDidChangeGroups.event; } - private readonly _onDidRegisterProcessSupport = new Emitter(); + private readonly _onDidRegisterProcessSupport = this._register(new Emitter()); get onDidRegisterProcessSupport(): Event { return this._onDidRegisterProcessSupport.event; } - private readonly _onDidChangeConnectionState = new Emitter(); + private readonly _onDidChangeConnectionState = this._register(new Emitter()); get onDidChangeConnectionState(): Event { return this._onDidChangeConnectionState.event; } constructor( @@ -185,7 +185,9 @@ export class TerminalService implements ITerminalService { @IKeybindingService private readonly _keybindingService: IKeybindingService, @ITimerService private readonly _timerService: ITimerService ) { - this._configHelper = this._instantiationService.createInstance(TerminalConfigHelper); + super(); + + this._configHelper = this._register(this._instantiationService.createInstance(TerminalConfigHelper)); // the below avoids having to poll routinely. // we update detected profiles when an instance is created so that, // for example, we detect if you've installed a pwsh diff --git a/src/vs/workbench/contrib/terminal/browser/terminalTabsList.ts b/src/vs/workbench/contrib/terminal/browser/terminalTabsList.ts index 4a3fb064e99..0991297c735 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalTabsList.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalTabsList.ts @@ -46,7 +46,7 @@ import { TerminalContextKeys } from 'vs/workbench/contrib/terminal/common/termin import { getTerminalResourcesFromDragEvent, parseTerminalUri } from 'vs/workbench/contrib/terminal/browser/terminalUri'; import { getInstanceHoverInfo } from 'vs/workbench/contrib/terminal/browser/terminalTooltip'; import { defaultInputBoxStyles } from 'vs/platform/theme/browser/defaultStyles'; -import { Event, Emitter } from 'vs/base/common/event'; +import { Emitter } from 'vs/base/common/event'; import { Schemas } from 'vs/base/common/network'; import { getColorForSeverity } from 'vs/workbench/contrib/terminal/browser/terminalStatusList'; @@ -196,8 +196,8 @@ export class TerminalTabList extends WorkbenchList { } }); if (!this._decorationsProvider) { - this._decorationsProvider = instantiationService.createInstance(TabDecorationsProvider); - decorationsService.registerDecorationsProvider(this._decorationsProvider); + this._decorationsProvider = this.disposables.add(instantiationService.createInstance(TabDecorationsProvider)); + this.disposables.add(decorationsService.registerDecorationsProvider(this._decorationsProvider)); } this.refresh(); } @@ -737,18 +737,17 @@ class TerminalTabsDragAndDrop implements IListDragAndDrop { } } -class TabDecorationsProvider implements IDecorationsProvider { +class TabDecorationsProvider extends Disposable implements IDecorationsProvider { readonly label: string = localize('label', "Terminal"); - private readonly _onDidChange = new Emitter(); + + private readonly _onDidChange = this._register(new Emitter()); + readonly onDidChange = this._onDidChange.event; constructor( @ITerminalService private readonly _terminalService: ITerminalService ) { - this._terminalService.onDidChangeInstancePrimaryStatus(e => this._onDidChange.fire([e.resource])); - } - - get onDidChange(): Event { - return this._onDidChange.event; + super(); + this._register(this._terminalService.onDidChangeInstancePrimaryStatus(e => this._onDidChange.fire([e.resource]))); } provideDecorations(resource: URI): IDecorationData | undefined { @@ -772,8 +771,4 @@ class TabDecorationsProvider implements IDecorationsProvider { tooltip: primaryStatus.tooltip }; } - - dispose(): void { - this.dispose(); - } } diff --git a/src/vs/workbench/contrib/terminal/browser/xterm/suggestAddon.ts b/src/vs/workbench/contrib/terminal/browser/xterm/suggestAddon.ts index 362f70dcde8..7ff72cf1c5a 100644 --- a/src/vs/workbench/contrib/terminal/browser/xterm/suggestAddon.ts +++ b/src/vs/workbench/contrib/terminal/browser/xterm/suggestAddon.ts @@ -83,9 +83,9 @@ export class SuggestAddon extends Disposable implements ITerminalAddon, ISuggest private _cursorIndexDelta: number = 0; private _inputQueue?: string[]; - private readonly _onBell = new Emitter(); + private readonly _onBell = this._register(new Emitter()); readonly onBell = this._onBell.event; - private readonly _onAcceptedCompletion = new Emitter(); + private readonly _onAcceptedCompletion = this._register(new Emitter()); readonly onAcceptedCompletion = this._onAcceptedCompletion.event; constructor( diff --git a/src/vs/workbench/contrib/terminal/browser/xterm/xtermTerminal.ts b/src/vs/workbench/contrib/terminal/browser/xterm/xtermTerminal.ts index d5acf93a0d8..8568498af91 100644 --- a/src/vs/workbench/contrib/terminal/browser/xterm/xtermTerminal.ts +++ b/src/vs/workbench/contrib/terminal/browser/xterm/xtermTerminal.ts @@ -146,21 +146,21 @@ export class XtermTerminal extends DisposableStore implements IXtermTerminal, ID get isStdinDisabled(): boolean { return !!this.raw.options.disableStdin; } - private readonly _onDidRequestRunCommand = new Emitter<{ command: ITerminalCommand; copyAsHtml?: boolean; noNewLine?: boolean }>(); + private readonly _onDidRequestRunCommand = this.add(new Emitter<{ command: ITerminalCommand; copyAsHtml?: boolean; noNewLine?: boolean }>()); readonly onDidRequestRunCommand = this._onDidRequestRunCommand.event; - private readonly _onDidRequestFocus = new Emitter(); + private readonly _onDidRequestFocus = this.add(new Emitter()); readonly onDidRequestFocus = this._onDidRequestFocus.event; - private readonly _onDidRequestSendText = new Emitter(); + private readonly _onDidRequestSendText = this.add(new Emitter()); readonly onDidRequestSendText = this._onDidRequestSendText.event; - private readonly _onDidRequestFreePort = new Emitter(); + private readonly _onDidRequestFreePort = this.add(new Emitter()); readonly onDidRequestFreePort = this._onDidRequestFreePort.event; - private readonly _onDidChangeFindResults = new Emitter<{ resultIndex: number; resultCount: number }>(); + private readonly _onDidChangeFindResults = this.add(new Emitter<{ resultIndex: number; resultCount: number }>()); readonly onDidChangeFindResults = this._onDidChangeFindResults.event; - private readonly _onDidChangeSelection = new Emitter(); + private readonly _onDidChangeSelection = this.add(new Emitter()); readonly onDidChangeSelection = this._onDidChangeSelection.event; - private readonly _onDidChangeFocus = new Emitter(); + private readonly _onDidChangeFocus = this.add(new Emitter()); readonly onDidChangeFocus = this._onDidChangeFocus.event; - private readonly _onDidDispose = new Emitter(); + private readonly _onDidDispose = this.add(new Emitter()); readonly onDidDispose = this._onDidDispose.event; get markTracker(): IMarkTracker { return this._markNavigationAddon; } diff --git a/src/vs/workbench/contrib/terminal/common/environmentVariableService.ts b/src/vs/workbench/contrib/terminal/common/environmentVariableService.ts index 8ae63dab664..0aaa48f6b05 100644 --- a/src/vs/workbench/contrib/terminal/common/environmentVariableService.ts +++ b/src/vs/workbench/contrib/terminal/common/environmentVariableService.ts @@ -12,6 +12,7 @@ import { deserializeEnvironmentDescriptionMap, deserializeEnvironmentVariableCol import { IEnvironmentVariableCollectionWithPersistence, IEnvironmentVariableService } from 'vs/workbench/contrib/terminal/common/environmentVariable'; import { TerminalStorageKeys } from 'vs/workbench/contrib/terminal/common/terminalStorageKeys'; import { IMergedEnvironmentVariableCollection, ISerializableEnvironmentDescriptionMap, ISerializableEnvironmentVariableCollection } from 'vs/platform/terminal/common/environmentVariable'; +import { Disposable } from 'vs/base/common/lifecycle'; interface ISerializableExtensionEnvironmentVariableCollection { extensionIdentifier: string; @@ -22,19 +23,21 @@ interface ISerializableExtensionEnvironmentVariableCollection { /** * Tracks and persists environment variable collections as defined by extensions. */ -export class EnvironmentVariableService implements IEnvironmentVariableService { +export class EnvironmentVariableService extends Disposable implements IEnvironmentVariableService { declare readonly _serviceBrand: undefined; collections: Map = new Map(); mergedCollection: IMergedEnvironmentVariableCollection; - private readonly _onDidChangeCollections = new Emitter(); + private readonly _onDidChangeCollections = this._register(new Emitter()); get onDidChangeCollections(): Event { return this._onDidChangeCollections.event; } constructor( @IExtensionService private readonly _extensionService: IExtensionService, @IStorageService private readonly _storageService: IStorageService ) { + super(); + this._storageService.remove(TerminalStorageKeys.DeprecatedEnvironmentVariableCollections, StorageScope.WORKSPACE); const serializedPersistedCollections = this._storageService.get(TerminalStorageKeys.EnvironmentVariableCollections, StorageScope.WORKSPACE); if (serializedPersistedCollections) { From f011814dfe1083ee92fa5cbe908d3d518de6e964 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Tue, 8 Aug 2023 13:44:47 -0700 Subject: [PATCH 2/2] Fix test compile --- .../test/browser/terminalInstance.test.ts | 52 +++++++++---------- 1 file changed, 26 insertions(+), 26 deletions(-) diff --git a/src/vs/workbench/contrib/terminal/test/browser/terminalInstance.test.ts b/src/vs/workbench/contrib/terminal/test/browser/terminalInstance.test.ts index 9fd40901d9e..295fb4bb119 100644 --- a/src/vs/workbench/contrib/terminal/test/browser/terminalInstance.test.ts +++ b/src/vs/workbench/contrib/terminal/test/browser/terminalInstance.test.ts @@ -197,8 +197,8 @@ suite('Workbench - TerminalInstance', () => { test('should resolve to "" when the template variables are empty', () => { configurationService = new TestConfigurationService({ terminal: { integrated: { tabs: { separator: ' - ', title: '', description: '' } } } }); configHelper = new TerminalConfigHelper(configurationService, null!, null!, null!, null!); - terminalLabelComputer = new TerminalLabelComputer(configHelper, createInstance({ capabilities, processName: '' }), new TestFileService(), mockContextService); - terminalLabelComputer.refreshLabel(); + terminalLabelComputer = new TerminalLabelComputer(configHelper, new TestFileService(), mockContextService); + terminalLabelComputer.refreshLabel(createInstance({ capabilities, processName: '' })); // TODO: // terminalLabelComputer.onLabelChanged(e => { // strictEqual(e.title, ''); @@ -210,80 +210,80 @@ suite('Workbench - TerminalInstance', () => { test('should resolve cwd', () => { configurationService = new TestConfigurationService({ terminal: { integrated: { tabs: { separator: ' - ', title: '${cwd}', description: '${cwd}' } } } }); configHelper = new TerminalConfigHelper(configurationService, null!, null!, null!, null!); - terminalLabelComputer = new TerminalLabelComputer(configHelper, createInstance({ capabilities, cwd: ROOT_1 }), new TestFileService(), mockContextService); - terminalLabelComputer.refreshLabel(); + terminalLabelComputer = new TerminalLabelComputer(configHelper, new TestFileService(), mockContextService); + terminalLabelComputer.refreshLabel(createInstance({ capabilities, cwd: ROOT_1 })); strictEqual(terminalLabelComputer.title, ROOT_1); strictEqual(terminalLabelComputer.description, ROOT_1); }); test('should resolve workspaceFolder', () => { configurationService = new TestConfigurationService({ terminal: { integrated: { tabs: { separator: ' - ', title: '${workspaceFolder}', description: '${workspaceFolder}' } } } }); configHelper = new TerminalConfigHelper(configurationService, null!, null!, null!, null!); - terminalLabelComputer = new TerminalLabelComputer(configHelper, createInstance({ capabilities, processName: 'zsh', workspaceFolder: { uri: URI.from({ scheme: Schemas.file, path: 'folder' }) } as IWorkspaceFolder }), new TestFileService(), mockContextService); - terminalLabelComputer.refreshLabel(); + terminalLabelComputer = new TerminalLabelComputer(configHelper, new TestFileService(), mockContextService); + terminalLabelComputer.refreshLabel(createInstance({ capabilities, processName: 'zsh', workspaceFolder: { uri: URI.from({ scheme: Schemas.file, path: 'folder' }) } as IWorkspaceFolder })); strictEqual(terminalLabelComputer.title, 'folder'); strictEqual(terminalLabelComputer.description, 'folder'); }); test('should resolve local', () => { configurationService = new TestConfigurationService({ terminal: { integrated: { tabs: { separator: ' - ', title: '${local}', description: '${local}' } } } }); configHelper = new TerminalConfigHelper(configurationService, null!, null!, null!, null!); - terminalLabelComputer = new TerminalLabelComputer(configHelper, createInstance({ capabilities, processName: 'zsh', shellLaunchConfig: { type: 'Local' } }), new TestFileService(), mockContextService); - terminalLabelComputer.refreshLabel(); + terminalLabelComputer = new TerminalLabelComputer(configHelper, new TestFileService(), mockContextService); + terminalLabelComputer.refreshLabel(createInstance({ capabilities, processName: 'zsh', shellLaunchConfig: { type: 'Local' } })); strictEqual(terminalLabelComputer.title, 'Local'); strictEqual(terminalLabelComputer.description, 'Local'); }); test('should resolve process', () => { configurationService = new TestConfigurationService({ terminal: { integrated: { tabs: { separator: ' - ', title: '${process}', description: '${process}' } } } }); configHelper = new TerminalConfigHelper(configurationService, null!, null!, null!, null!); - terminalLabelComputer = new TerminalLabelComputer(configHelper, createInstance({ capabilities, processName: 'zsh' }), new TestFileService(), mockContextService); - terminalLabelComputer.refreshLabel(); + terminalLabelComputer = new TerminalLabelComputer(configHelper, new TestFileService(), mockContextService); + terminalLabelComputer.refreshLabel(createInstance({ capabilities, processName: 'zsh' })); strictEqual(terminalLabelComputer.title, 'zsh'); strictEqual(terminalLabelComputer.description, 'zsh'); }); test('should resolve sequence', () => { configurationService = new TestConfigurationService({ terminal: { integrated: { tabs: { separator: ' - ', title: '${sequence}', description: '${sequence}' } } } }); configHelper = new TerminalConfigHelper(configurationService, null!, null!, null!, null!); - terminalLabelComputer = new TerminalLabelComputer(configHelper, createInstance({ capabilities, sequence: 'sequence' }), new TestFileService(), mockContextService); - terminalLabelComputer.refreshLabel(); + terminalLabelComputer = new TerminalLabelComputer(configHelper, new TestFileService(), mockContextService); + terminalLabelComputer.refreshLabel(createInstance({ capabilities, sequence: 'sequence' })); strictEqual(terminalLabelComputer.title, 'sequence'); strictEqual(terminalLabelComputer.description, 'sequence'); }); test('should resolve task', () => { configurationService = new TestConfigurationService({ terminal: { integrated: { tabs: { separator: ' ~ ', title: '${process}${separator}${task}', description: '${task}' } } } }); configHelper = new TerminalConfigHelper(configurationService, null!, null!, null!, null!); - terminalLabelComputer = new TerminalLabelComputer(configHelper, createInstance({ capabilities, processName: 'zsh', shellLaunchConfig: { type: 'Task' } }), new TestFileService(), mockContextService); - terminalLabelComputer.refreshLabel(); + terminalLabelComputer = new TerminalLabelComputer(configHelper, new TestFileService(), mockContextService); + terminalLabelComputer.refreshLabel(createInstance({ capabilities, processName: 'zsh', shellLaunchConfig: { type: 'Task' } })); strictEqual(terminalLabelComputer.title, 'zsh ~ Task'); strictEqual(terminalLabelComputer.description, 'Task'); }); test('should resolve separator', () => { configurationService = new TestConfigurationService({ terminal: { integrated: { tabs: { separator: ' ~ ', title: '${separator}', description: '${separator}' } } } }); configHelper = new TerminalConfigHelper(configurationService, null!, null!, null!, null!); - terminalLabelComputer = new TerminalLabelComputer(configHelper, createInstance({ capabilities, processName: 'zsh', shellLaunchConfig: { type: 'Task' } }), new TestFileService(), mockContextService); - terminalLabelComputer.refreshLabel(); + terminalLabelComputer = new TerminalLabelComputer(configHelper, new TestFileService(), mockContextService); + terminalLabelComputer.refreshLabel(createInstance({ capabilities, processName: 'zsh', shellLaunchConfig: { type: 'Task' } })); strictEqual(terminalLabelComputer.title, 'zsh'); strictEqual(terminalLabelComputer.description, ''); }); test('should always return static title when specified', () => { configurationService = new TestConfigurationService({ terminal: { integrated: { tabs: { separator: ' ~ ', title: '${process}', description: '${workspaceFolder}' } } } }); configHelper = new TerminalConfigHelper(configurationService, null!, null!, null!, null!); - terminalLabelComputer = new TerminalLabelComputer(configHelper, createInstance({ capabilities, processName: 'process', workspaceFolder: { uri: URI.from({ scheme: Schemas.file, path: 'folder' }) } as IWorkspaceFolder, staticTitle: 'my-title' }), new TestFileService(), mockContextService); - terminalLabelComputer.refreshLabel(); + terminalLabelComputer = new TerminalLabelComputer(configHelper, new TestFileService(), mockContextService); + terminalLabelComputer.refreshLabel(createInstance({ capabilities, processName: 'process', workspaceFolder: { uri: URI.from({ scheme: Schemas.file, path: 'folder' }) } as IWorkspaceFolder, staticTitle: 'my-title' })); strictEqual(terminalLabelComputer.title, 'my-title'); strictEqual(terminalLabelComputer.description, 'folder'); }); test('should provide cwdFolder for all cwds only when in multi-root', () => { configurationService = new TestConfigurationService({ terminal: { integrated: { tabs: { separator: ' ~ ', title: '${process}${separator}${cwdFolder}', description: '${cwdFolder}' } } } }); configHelper = new TerminalConfigHelper(configurationService, null!, null!, null!, null!); - terminalLabelComputer = new TerminalLabelComputer(configHelper, createInstance({ capabilities, processName: 'process', workspaceFolder: { uri: URI.from({ scheme: Schemas.file, path: ROOT_1 }) } as IWorkspaceFolder, cwd: ROOT_1 }), new TestFileService(), mockContextService); - terminalLabelComputer.refreshLabel(); + terminalLabelComputer = new TerminalLabelComputer(configHelper, new TestFileService(), mockContextService); + terminalLabelComputer.refreshLabel(createInstance({ capabilities, processName: 'process', workspaceFolder: { uri: URI.from({ scheme: Schemas.file, path: ROOT_1 }) } as IWorkspaceFolder, cwd: ROOT_1 })); // single-root, cwd is same as root strictEqual(terminalLabelComputer.title, 'process'); strictEqual(terminalLabelComputer.description, ''); // multi-root configurationService = new TestConfigurationService({ terminal: { integrated: { tabs: { separator: ' ~ ', title: '${process}${separator}${cwdFolder}', description: '${cwdFolder}' } } } }); configHelper = new TerminalConfigHelper(configurationService, null!, null!, null!, null!); - terminalLabelComputer = new TerminalLabelComputer(configHelper, createInstance({ capabilities, processName: 'process', workspaceFolder: { uri: URI.from({ scheme: Schemas.file, path: ROOT_1 }) } as IWorkspaceFolder, cwd: ROOT_2 }), new TestFileService(), mockMultiRootContextService); - terminalLabelComputer.refreshLabel(); + terminalLabelComputer = new TerminalLabelComputer(configHelper, new TestFileService(), mockMultiRootContextService); + terminalLabelComputer.refreshLabel(createInstance({ capabilities, processName: 'process', workspaceFolder: { uri: URI.from({ scheme: Schemas.file, path: ROOT_1 }) } as IWorkspaceFolder, cwd: ROOT_2 })); if (isWindows) { strictEqual(terminalLabelComputer.title, 'process'); strictEqual(terminalLabelComputer.description, ''); @@ -295,13 +295,13 @@ suite('Workbench - TerminalInstance', () => { test('should hide cwdFolder in single folder workspaces when cwd matches the workspace\'s default cwd even when slashes differ', async () => { configurationService = new TestConfigurationService({ terminal: { integrated: { tabs: { separator: ' ~ ', title: '${process}${separator}${cwdFolder}', description: '${cwdFolder}' } } } }); configHelper = new TerminalConfigHelper(configurationService, null!, null!, null!, null!); - terminalLabelComputer = new TerminalLabelComputer(configHelper, createInstance({ capabilities, processName: 'process', workspaceFolder: { uri: URI.from({ scheme: Schemas.file, path: ROOT_1 }) } as IWorkspaceFolder, cwd: ROOT_1 }), new TestFileService(), mockContextService); - terminalLabelComputer.refreshLabel(); + terminalLabelComputer = new TerminalLabelComputer(configHelper, new TestFileService(), mockContextService); + terminalLabelComputer.refreshLabel(createInstance({ capabilities, processName: 'process', workspaceFolder: { uri: URI.from({ scheme: Schemas.file, path: ROOT_1 }) } as IWorkspaceFolder, cwd: ROOT_1 })); strictEqual(terminalLabelComputer.title, 'process'); strictEqual(terminalLabelComputer.description, ''); if (!isWindows) { - terminalLabelComputer = new TerminalLabelComputer(configHelper, createInstance({ capabilities, processName: 'process', workspaceFolder: { uri: URI.from({ scheme: Schemas.file, path: ROOT_1 }) } as IWorkspaceFolder, cwd: ROOT_2 }), new TestFileService(), mockContextService); - terminalLabelComputer.refreshLabel(); + terminalLabelComputer = new TerminalLabelComputer(configHelper, new TestFileService(), mockContextService); + terminalLabelComputer.refreshLabel(createInstance({ capabilities, processName: 'process', workspaceFolder: { uri: URI.from({ scheme: Schemas.file, path: ROOT_1 }) } as IWorkspaceFolder, cwd: ROOT_2 })); strictEqual(terminalLabelComputer.title, 'process ~ root2'); strictEqual(terminalLabelComputer.description, 'root2'); }