Merge pull request #189720 from microsoft/tyriar/187082_2

Improve management of events/memory in terminal
This commit is contained in:
Daniel Imms
2023-08-08 17:14:04 -07:00
committed by GitHub
17 changed files with 191 additions and 178 deletions
@@ -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<string, IMarker> = new Map();
private _anonymousMarkers: Map<number, IMarker> = new Map();
private readonly _onMarkAdded = new Emitter<IMarkProperties>();
private readonly _onMarkAdded = this._register(new Emitter<IMarkProperties>());
readonly onMarkAdded = this._onMarkAdded.event;
constructor(
private readonly _terminal: Terminal
) {
super();
}
*markers(): IterableIterator<IMarker> {
@@ -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<ITerminalCommand>();
private readonly _onCommandStarted = this._register(new Emitter<ITerminalCommand>());
readonly onCommandStarted = this._onCommandStarted.event;
private readonly _onBeforeCommandFinished = new Emitter<ITerminalCommand>();
private readonly _onBeforeCommandFinished = this._register(new Emitter<ITerminalCommand>());
readonly onBeforeCommandFinished = this._onBeforeCommandFinished.event;
private readonly _onCommandFinished = new Emitter<ITerminalCommand>();
private readonly _onCommandFinished = this._register(new Emitter<ITerminalCommand>());
readonly onCommandFinished = this._onCommandFinished.event;
private readonly _onCommandExecuted = new Emitter<void>();
private readonly _onCommandExecuted = this._register(new Emitter<void>());
readonly onCommandExecuted = this._onCommandExecuted.event;
private readonly _onCommandInvalidated = new Emitter<ITerminalCommand[]>();
private readonly _onCommandInvalidated = this._register(new Emitter<ITerminalCommand[]>());
readonly onCommandInvalidated = this._onCommandInvalidated.event;
private readonly _onCurrentCommandInvalidated = new Emitter<ICommandInvalidationRequest>();
private readonly _onCurrentCommandInvalidated = this._register(new Emitter<ICommandInvalidationRequest>());
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();
}
@@ -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</*cwd*/string, /*frequency*/number>();
@@ -18,7 +19,7 @@ export class CwdDetectionCapability implements ICwdDetectionCapability {
return Array.from(this._cwds.keys());
}
private readonly _onDidChangeCwd = new Emitter<string>();
private readonly _onDidChangeCwd = this._register(new Emitter<string>());
readonly onDidChangeCwd = this._onDidChangeCwd.event;
getCwd(): string {
@@ -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;
@@ -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<void>();
private readonly _onConfigChanged = this._register(new Emitter<void>());
get onConfigChanged(): Event<void> { 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)) {
@@ -33,15 +33,15 @@ export class TerminalEditorService extends Disposable implements ITerminalEditor
private _editorInputs: Map</*resource*/string, TerminalEditorInput> = new Map();
private _instanceDisposables: Map</*resource*/string, IDisposable[]> = new Map();
private readonly _onDidDisposeInstance = new Emitter<ITerminalInstance>();
private readonly _onDidDisposeInstance = this._register(new Emitter<ITerminalInstance>());
readonly onDidDisposeInstance = this._onDidDisposeInstance.event;
private readonly _onDidFocusInstance = new Emitter<ITerminalInstance>();
private readonly _onDidFocusInstance = this._register(new Emitter<ITerminalInstance>());
readonly onDidFocusInstance = this._onDidFocusInstance.event;
private readonly _onDidChangeInstanceCapability = new Emitter<ITerminalInstance>();
private readonly _onDidChangeInstanceCapability = this._register(new Emitter<ITerminalInstance>());
readonly onDidChangeInstanceCapability = this._onDidChangeInstanceCapability.event;
private readonly _onDidChangeActiveInstance = new Emitter<ITerminalInstance | undefined>();
private readonly _onDidChangeActiveInstance = this._register(new Emitter<ITerminalInstance | undefined>());
readonly onDidChangeActiveInstance = this._onDidChangeActiveInstance.event;
private readonly _onDidChangeInstances = new Emitter<void>();
private readonly _onDidChangeInstances = this._register(new Emitter<void>());
readonly onDidChangeInstances = this._onDidChangeInstances.event;
constructor(
@@ -278,9 +278,9 @@ export class TerminalGroup extends Disposable implements ITerminalGroup {
readonly onDisposed = this._onDisposed.event;
private readonly _onInstancesChanged: Emitter<void> = this._register(new Emitter<void>());
readonly onInstancesChanged = this._onInstancesChanged.event;
private readonly _onDidChangeActiveInstance = new Emitter<ITerminalInstance | undefined>();
private readonly _onDidChangeActiveInstance = this._register(new Emitter<ITerminalInstance | undefined>());
readonly onDidChangeActiveInstance = this._onDidChangeActiveInstance.event;
private readonly _onPanelOrientationChanged = new Emitter<Orientation>();
private readonly _onPanelOrientationChanged = this._register(new Emitter<Orientation>());
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 {
@@ -33,27 +33,27 @@ export class TerminalGroupService extends Disposable implements ITerminalGroupSe
private _container: HTMLElement | undefined;
private readonly _onDidChangeActiveGroup = new Emitter<ITerminalGroup | undefined>();
private readonly _onDidChangeActiveGroup = this._register(new Emitter<ITerminalGroup | undefined>());
readonly onDidChangeActiveGroup = this._onDidChangeActiveGroup.event;
private readonly _onDidDisposeGroup = new Emitter<ITerminalGroup>();
private readonly _onDidDisposeGroup = this._register(new Emitter<ITerminalGroup>());
readonly onDidDisposeGroup = this._onDidDisposeGroup.event;
private readonly _onDidChangeGroups = new Emitter<void>();
private readonly _onDidChangeGroups = this._register(new Emitter<void>());
readonly onDidChangeGroups = this._onDidChangeGroups.event;
private readonly _onDidShow = new Emitter<void>();
private readonly _onDidShow = this._register(new Emitter<void>());
readonly onDidShow = this._onDidShow.event;
private readonly _onDidDisposeInstance = new Emitter<ITerminalInstance>();
private readonly _onDidDisposeInstance = this._register(new Emitter<ITerminalInstance>());
readonly onDidDisposeInstance = this._onDidDisposeInstance.event;
private readonly _onDidFocusInstance = new Emitter<ITerminalInstance>();
private readonly _onDidFocusInstance = this._register(new Emitter<ITerminalInstance>());
readonly onDidFocusInstance = this._onDidFocusInstance.event;
private readonly _onDidChangeActiveInstance = new Emitter<ITerminalInstance | undefined>();
private readonly _onDidChangeActiveInstance = this._register(new Emitter<ITerminalInstance | undefined>());
readonly onDidChangeActiveInstance = this._onDidChangeActiveInstance.event;
private readonly _onDidChangeInstances = new Emitter<void>();
private readonly _onDidChangeInstances = this._register(new Emitter<void>());
readonly onDidChangeInstances = this._onDidChangeInstances.event;
private readonly _onDidChangeInstanceCapability = new Emitter<ITerminalInstance>();
private readonly _onDidChangeInstanceCapability = this._register(new Emitter<ITerminalInstance>());
readonly onDidChangeInstanceCapability = this._onDidChangeInstanceCapability.event;
private readonly _onDidChangePanelOrientation = new Emitter<Orientation>();
private readonly _onDidChangePanelOrientation = this._register(new Emitter<Orientation>());
readonly onDidChangePanelOrientation = this._onDidChangePanelOrientation.event;
constructor(
@@ -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';
@@ -168,9 +168,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<IDisposable> = this._register(new MutableDisposable());
private _widgetManager: TerminalWidgetManager = new TerminalWidgetManager();
private _dndObserver: IDisposable | undefined;
private _dndObserver: MutableDisposable<IDisposable> = this._register(new MutableDisposable());
private _lastLayoutDimensions: dom.Dimension | undefined;
private _hasHadInput: boolean;
private _description?: string;
@@ -245,7 +245,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; }
@@ -450,8 +450,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();
@@ -527,10 +525,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
@@ -564,6 +562,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;
}
});
}
}
@@ -745,13 +751,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<void>(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,
@@ -761,17 +767,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) => {
@@ -1046,14 +1052,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 {
@@ -1339,12 +1344,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);
@@ -1353,7 +1358,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);
@@ -1364,7 +1369,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;
@@ -1912,8 +1917,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
@@ -1958,7 +1962,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"),
@@ -1969,7 +1973,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();
@@ -2007,7 +2011,7 @@ export class TerminalInstance extends Disposable implements ITerminalInstance {
}
}
await this._refreshScrollbar();
this._labelComputer?.refreshLabel();
this._labelComputer?.refreshLabel(this);
this.focus();
}
@@ -2151,7 +2155,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) {
@@ -2250,9 +2254,9 @@ export class TerminalInstance extends Disposable implements ITerminalInstance {
class TerminalInstanceDragAndDropController extends Disposable implements dom.IDragAndDropObserverCallbacks {
private _dropOverlay?: HTMLElement;
private readonly _onDropFile = new Emitter<string | URI>();
private readonly _onDropFile = this._register(new Emitter<string | URI>());
get onDropFile(): Event<string | URI> { return this._onDropFile.event; }
private readonly _onDropTerminal = new Emitter<IRequestAddInstanceToGroupEvent>();
private readonly _onDropTerminal = this._register(new Emitter<IRequestAddInstanceToGroupEvent>());
get onDropTerminal(): Event<IRequestAddInstanceToGroupEvent> { return this._onDropTerminal.event; }
constructor(
@@ -2404,65 +2408,65 @@ export class TerminalLabelComputer extends Disposable {
constructor(
private readonly _configHelper: TerminalConfigHelper,
private readonly _instance: Pick<ITerminalInstance, 'shellLaunchConfig' | 'cwd' | 'fixedCols' | 'fixedRows' | 'initialCwd' | 'processName' | 'sequence' | 'userHome' | 'workspaceFolder' | 'staticTitle' | 'capabilities' | 'title' | 'description'>,
@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<ITerminalInstance, 'shellLaunchConfig' | 'cwd' | 'fixedCols' | 'fixedRows' | 'initialCwd' | 'processName' | 'sequence' | 'userHome' | 'workspaceFolder' | 'staticTitle' | 'capabilities' | 'title' | 'description'>, 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<ITerminalInstance, 'shellLaunchConfig' | 'cwd' | 'fixedCols' | 'fixedRows' | 'initialCwd' | 'processName' | 'sequence' | 'userHome' | 'workspaceFolder' | 'staticTitle' | 'capabilities' | 'title' | 'description'>,
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) {
@@ -2472,7 +2476,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;
}
}
@@ -25,7 +25,7 @@ export class TerminalInstanceService extends Disposable implements ITerminalInst
private _configHelper: TerminalConfigHelper;
private _backendRegistration = new Map<string | undefined, { promise: Promise<void>; resolve: () => void }>();
private readonly _onDidCreateInstance = new Emitter<ITerminalInstance>();
private readonly _onDidCreateInstance = this._register(new Emitter<ITerminalInstance>());
get onDidCreateInstance(): Event<ITerminalInstance> { return this._onDidCreateInstance.event; }
constructor(
@@ -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<boolean>;
@@ -39,7 +39,7 @@ export class TerminalProfileService implements ITerminalProfileService {
private _platformConfigJustRefreshed = false;
private readonly _profileProviders: Map</*ext id*/string, Map</*provider id*/string, ITerminalProfileProvider>> = new Map();
private readonly _onDidChangeAvailableProfiles = new Emitter<ITerminalProfile[]>();
private readonly _onDidChangeAvailableProfiles = this._register(new Emitter<ITerminalProfile[]>());
get onDidChangeAvailableProfiles(): Event<ITerminalProfile[]> { return this._onDidChangeAvailableProfiles.event; }
get profilesReady(): Promise<void> { 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());
@@ -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<ITerminalInstanceHost, ITerminalInstance | undefined> = new Map();
@@ -120,47 +120,47 @@ export class TerminalService implements ITerminalService {
private _editingTerminal: ITerminalInstance | undefined;
private readonly _onDidChangeActiveGroup = new Emitter<ITerminalGroup | undefined>();
private readonly _onDidChangeActiveGroup = this._register(new Emitter<ITerminalGroup | undefined>());
get onDidChangeActiveGroup(): Event<ITerminalGroup | undefined> { return this._onDidChangeActiveGroup.event; }
private readonly _onDidCreateInstance = new Emitter<ITerminalInstance>();
private readonly _onDidCreateInstance = this._register(new Emitter<ITerminalInstance>());
get onDidCreateInstance(): Event<ITerminalInstance> { return this._onDidCreateInstance.event; }
private readonly _onDidDisposeInstance = new Emitter<ITerminalInstance>();
private readonly _onDidDisposeInstance = this._register(new Emitter<ITerminalInstance>());
get onDidDisposeInstance(): Event<ITerminalInstance> { return this._onDidDisposeInstance.event; }
private readonly _onDidFocusInstance = new Emitter<ITerminalInstance>();
private readonly _onDidFocusInstance = this._register(new Emitter<ITerminalInstance>());
get onDidFocusInstance(): Event<ITerminalInstance> { return this._onDidFocusInstance.event; }
private readonly _onDidReceiveProcessId = new Emitter<ITerminalInstance>();
private readonly _onDidReceiveProcessId = this._register(new Emitter<ITerminalInstance>());
get onDidReceiveProcessId(): Event<ITerminalInstance> { return this._onDidReceiveProcessId.event; }
private readonly _onDidRequestStartExtensionTerminal = new Emitter<IStartExtensionTerminalRequest>();
private readonly _onDidRequestStartExtensionTerminal = this._register(new Emitter<IStartExtensionTerminalRequest>());
get onDidRequestStartExtensionTerminal(): Event<IStartExtensionTerminalRequest> { return this._onDidRequestStartExtensionTerminal.event; }
private readonly _onDidChangeInstanceDimensions = new Emitter<ITerminalInstance>();
private readonly _onDidChangeInstanceDimensions = this._register(new Emitter<ITerminalInstance>());
get onDidChangeInstanceDimensions(): Event<ITerminalInstance> { return this._onDidChangeInstanceDimensions.event; }
private readonly _onDidMaxiumumDimensionsChange = new Emitter<ITerminalInstance>();
private readonly _onDidMaxiumumDimensionsChange = this._register(new Emitter<ITerminalInstance>());
get onDidMaximumDimensionsChange(): Event<ITerminalInstance> { return this._onDidMaxiumumDimensionsChange.event; }
private readonly _onDidChangeInstanceCapability = new Emitter<ITerminalInstance>();
private readonly _onDidChangeInstanceCapability = this._register(new Emitter<ITerminalInstance>());
get onDidChangeInstanceCapability(): Event<ITerminalInstance> { return this._onDidChangeInstanceCapability.event; }
private readonly _onDidChangeInstances = new Emitter<void>();
private readonly _onDidChangeInstances = this._register(new Emitter<void>());
get onDidChangeInstances(): Event<void> { return this._onDidChangeInstances.event; }
private readonly _onDidChangeInstanceTitle = new Emitter<ITerminalInstance | undefined>();
private readonly _onDidChangeInstanceTitle = this._register(new Emitter<ITerminalInstance | undefined>());
get onDidChangeInstanceTitle(): Event<ITerminalInstance | undefined> { 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<ITerminalInstance | undefined>();
private readonly _onDidChangeActiveInstance = this._register(new Emitter<ITerminalInstance | undefined>());
get onDidChangeActiveInstance(): Event<ITerminalInstance | undefined> { return this._onDidChangeActiveInstance.event; }
private readonly _onDidChangeInstancePrimaryStatus = new Emitter<ITerminalInstance>();
private readonly _onDidChangeInstancePrimaryStatus = this._register(new Emitter<ITerminalInstance>());
get onDidChangeInstancePrimaryStatus(): Event<ITerminalInstance> { return this._onDidChangeInstancePrimaryStatus.event; }
private readonly _onDidInputInstanceData = new Emitter<ITerminalInstance>();
private readonly _onDidInputInstanceData = this._register(new Emitter<ITerminalInstance>());
get onDidInputInstanceData(): Event<ITerminalInstance> { return this._onDidInputInstanceData.event; }
private readonly _onDidChangeSelection = new Emitter<ITerminalInstance>();
private readonly _onDidChangeSelection = this._register(new Emitter<ITerminalInstance>());
get onDidChangeSelection(): Event<ITerminalInstance> { return this._onDidChangeSelection.event; }
private readonly _onDidDisposeGroup = new Emitter<ITerminalGroup>();
private readonly _onDidDisposeGroup = this._register(new Emitter<ITerminalGroup>());
get onDidDisposeGroup(): Event<ITerminalGroup> { return this._onDidDisposeGroup.event; }
private readonly _onDidChangeGroups = new Emitter<void>();
private readonly _onDidChangeGroups = this._register(new Emitter<void>());
get onDidChangeGroups(): Event<void> { return this._onDidChangeGroups.event; }
private readonly _onDidRegisterProcessSupport = new Emitter<void>();
private readonly _onDidRegisterProcessSupport = this._register(new Emitter<void>());
get onDidRegisterProcessSupport(): Event<void> { return this._onDidRegisterProcessSupport.event; }
private readonly _onDidChangeConnectionState = new Emitter<void>();
private readonly _onDidChangeConnectionState = this._register(new Emitter<void>());
get onDidChangeConnectionState(): Event<void> { 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
@@ -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<ITerminalInstance> {
}
});
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<ITerminalInstance> {
}
}
class TabDecorationsProvider implements IDecorationsProvider {
class TabDecorationsProvider extends Disposable implements IDecorationsProvider {
readonly label: string = localize('label', "Terminal");
private readonly _onDidChange = new Emitter<URI[]>();
private readonly _onDidChange = this._register(new Emitter<URI[]>());
readonly onDidChange = this._onDidChange.event;
constructor(
@ITerminalService private readonly _terminalService: ITerminalService
) {
this._terminalService.onDidChangeInstancePrimaryStatus(e => this._onDidChange.fire([e.resource]));
}
get onDidChange(): Event<URI[]> {
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();
}
}
@@ -83,9 +83,9 @@ export class SuggestAddon extends Disposable implements ITerminalAddon, ISuggest
private _cursorIndexDelta: number = 0;
private _inputQueue?: string[];
private readonly _onBell = new Emitter<void>();
private readonly _onBell = this._register(new Emitter<void>());
readonly onBell = this._onBell.event;
private readonly _onAcceptedCompletion = new Emitter<string>();
private readonly _onAcceptedCompletion = this._register(new Emitter<string>());
readonly onAcceptedCompletion = this._onAcceptedCompletion.event;
constructor(
@@ -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<void>();
private readonly _onDidRequestFocus = this.add(new Emitter<void>());
readonly onDidRequestFocus = this._onDidRequestFocus.event;
private readonly _onDidRequestSendText = new Emitter<string>();
private readonly _onDidRequestSendText = this.add(new Emitter<string>());
readonly onDidRequestSendText = this._onDidRequestSendText.event;
private readonly _onDidRequestFreePort = new Emitter<string>();
private readonly _onDidRequestFreePort = this.add(new Emitter<string>());
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<void>();
private readonly _onDidChangeSelection = this.add(new Emitter<void>());
readonly onDidChangeSelection = this._onDidChangeSelection.event;
private readonly _onDidChangeFocus = new Emitter<boolean>();
private readonly _onDidChangeFocus = this.add(new Emitter<boolean>());
readonly onDidChangeFocus = this._onDidChangeFocus.event;
private readonly _onDidDispose = new Emitter<void>();
private readonly _onDidDispose = this.add(new Emitter<void>());
readonly onDidDispose = this._onDidDispose.event;
get markTracker(): IMarkTracker { return this._markNavigationAddon; }
@@ -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<string, IEnvironmentVariableCollectionWithPersistence> = new Map();
mergedCollection: IMergedEnvironmentVariableCollection;
private readonly _onDidChangeCollections = new Emitter<IMergedEnvironmentVariableCollection>();
private readonly _onDidChangeCollections = this._register(new Emitter<IMergedEnvironmentVariableCollection>());
get onDidChangeCollections(): Event<IMergedEnvironmentVariableCollection> { 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) {
@@ -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');
}