terminalInstance.ts 93.4 KB
Newer Older
1
/*---------------------------------------------------------------------------------------------
T
t-amqi 已提交
2 3 4
 *  Copyright (c) Microsoft Corporation. All rights reserved.
 *  Licensed under the MIT License. See License.txt in the project root for license information.
 *--------------------------------------------------------------------------------------------*/
5

6
import * as path from 'vs/base/common/path';
D
Daniel Imms 已提交
7 8
import * as dom from 'vs/base/browser/dom';
import { StandardKeyboardEvent } from 'vs/base/browser/keyboardEvent';
9
import { debounce } from 'vs/base/common/decorators';
D
Daniel Imms 已提交
10
import { Emitter, Event } from 'vs/base/common/event';
11
import { KeyCode } from 'vs/base/common/keyCodes';
D
Daniel Imms 已提交
12
import { IDisposable, dispose, Disposable, toDisposable } from 'vs/base/common/lifecycle';
13
import { TabFocus } from 'vs/editor/common/config/commonEditorConfig';
D
Daniel Imms 已提交
14
import * as nls from 'vs/nls';
D
Daniel Imms 已提交
15
import { IClipboardService } from 'vs/platform/clipboard/common/clipboardService';
D
Daniel Imms 已提交
16 17 18 19
import { ConfigurationTarget, IConfigurationService } from 'vs/platform/configuration/common/configuration';
import { IContextKey, IContextKeyService } from 'vs/platform/contextkey/common/contextkey';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding';
D
Daniel Imms 已提交
20
import { ILogService } from 'vs/platform/log/common/log';
21
import { INotificationService, IPromptChoice, NeverShowAgainScope, Severity } from 'vs/platform/notification/common/notification';
22
import { IStorageService, StorageScope, StorageTarget } from 'vs/platform/storage/common/storage';
M
meganrogge 已提交
23
import { activeContrastBorder, editorBackground, scrollbarSliderActiveBackground, scrollbarSliderBackground, scrollbarSliderHoverBackground } from 'vs/platform/theme/common/colorRegistry';
M
Megan Rogge 已提交
24
import { ICssStyleCollector, IColorTheme, IThemeService, registerThemingParticipant } from 'vs/platform/theme/common/themeService';
M
meganrogge 已提交
25
import { PANEL_BACKGROUND, SIDE_BAR_BACKGROUND } from 'vs/workbench/common/theme';
D
Daniel Imms 已提交
26
import { TerminalWidgetManager } from 'vs/workbench/contrib/terminal/browser/widgets/widgetManager';
27
import { ITerminalProcessManager, ProcessState, TERMINAL_VIEW_ID, INavigationMode, DEFAULT_COMMANDS_TO_SKIP_SHELL, TERMINAL_CREATION_COMMANDS, ITerminalProfileResolverService } from 'vs/workbench/contrib/terminal/common/terminal';
D
Daniel Imms 已提交
28
import { ansiColorIdentifiers, ansiColorMap, TERMINAL_BACKGROUND_COLOR, TERMINAL_CURSOR_BACKGROUND_COLOR, TERMINAL_CURSOR_FOREGROUND_COLOR, TERMINAL_FOREGROUND_COLOR, TERMINAL_SELECTION_BACKGROUND_COLOR } from 'vs/workbench/contrib/terminal/common/terminalColorRegistry';
29
import { TerminalConfigHelper } from 'vs/workbench/contrib/terminal/browser/terminalConfigHelper';
30
import { TerminalLinkManager } from 'vs/workbench/contrib/terminal/browser/links/terminalLinkManager';
I
isidor 已提交
31
import { IAccessibilityService } from 'vs/platform/accessibility/common/accessibility';
32
import { ITerminalInstanceService, ITerminalInstance, ITerminalExternalLinkProvider, IRequestAddInstanceToGroupEvent } from 'vs/workbench/contrib/terminal/browser/terminal';
33
import { TerminalProcessManager } from 'vs/workbench/contrib/terminal/browser/terminalProcessManager';
D
Daniel Imms 已提交
34
import type { Terminal as XTermTerminal, IBuffer, ITerminalAddon, RendererType, ITheme } from 'xterm';
35 36
import type { SearchAddon, ISearchOptions } from 'xterm-addon-search';
import type { Unicode11Addon } from 'xterm-addon-unicode11';
37
import type { WebglAddon } from 'xterm-addon-webgl';
38
import { CommandTrackerAddon } from 'vs/workbench/contrib/terminal/browser/addons/commandTrackerAddon';
39
import { NavigationModeAddon } from 'vs/workbench/contrib/terminal/browser/addons/navigationModeAddon';
40
import { XTermCore } from 'vs/workbench/contrib/terminal/browser/xterm-private';
D
Daniel Imms 已提交
41
import { IEditorOptions } from 'vs/editor/common/config/editorOptions';
42
import { IViewsService, IViewDescriptorService, ViewContainerLocation } from 'vs/workbench/common/views';
D
Daniel Imms 已提交
43
import { EnvironmentVariableInfoWidget } from 'vs/workbench/contrib/terminal/browser/widgets/environmentVariableInfoWidget';
44
import { TerminalLaunchHelpAction } from 'vs/workbench/contrib/terminal/browser/terminalActions';
C
wip  
Connor Peet 已提交
45
import { TypeAheadAddon } from 'vs/workbench/contrib/terminal/browser/terminalTypeAheadAddon';
46
import { BrowserFeatures } from 'vs/base/browser/canIUse';
47
import { IPreferencesService } from 'vs/workbench/services/preferences/common/preferences';
48
import { IEnvironmentVariableInfo } from 'vs/workbench/contrib/terminal/common/environmentVariable';
M
meganrogge 已提交
49
import { IProcessDataEvent, IShellLaunchConfig, ITerminalDimensionsOverride, ITerminalLaunchError, TerminalShellType, TerminalSettingId, TitleEventSource, TerminalIcon, TerminalSettingPrefix, ITerminalProfileObject, TerminalLocation, ProcessPropertyType, ProcessCapability, IProcessPropertyMap } from 'vs/platform/terminal/common/terminal';
50
import { IProductService } from 'vs/platform/product/common/productService';
51
import { formatMessageForTerminal } from 'vs/workbench/contrib/terminal/common/terminalStrings';
52
import { AutoOpenBarrier } from 'vs/base/common/async';
D
Daniel Imms 已提交
53
import { Codicon, iconRegistry } from 'vs/base/common/codicons';
54
import { ITerminalStatusList, TerminalStatus, TerminalStatusList } from 'vs/workbench/contrib/terminal/browser/terminalStatusList';
D
Daniel Imms 已提交
55
import { IQuickInputService, IQuickPickItem, IQuickPickSeparator } from 'vs/platform/quickinput/common/quickInput';
56
import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService';
M
meganrogge 已提交
57
import { isMacintosh, isWindows, OperatingSystem, OS } from 'vs/base/common/platform';
58
import { URI } from 'vs/base/common/uri';
59
import { DataTransfers } from 'vs/base/browser/dnd';
60
import { CodeDataTransfers, containsDragType, DragAndDropObserver, IDragAndDropObserverCallbacks } from 'vs/workbench/browser/dnd';
D
Daniel Imms 已提交
61
import { getColorClass } from 'vs/workbench/contrib/terminal/browser/terminalIcon';
J
jeanp413 已提交
62 63
import { IWorkbenchLayoutService, Position } from 'vs/workbench/services/layout/browser/layoutService';
import { Orientation } from 'vs/base/browser/ui/sash/sash';
64
import { Color } from 'vs/base/common/color';
65
import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace';
66
import { TerminalStorageKeys } from 'vs/workbench/contrib/terminal/common/terminalStorageKeys';
67
import { TerminalContextKeys } from 'vs/workbench/contrib/terminal/common/terminalContextKey';
D
Daniel Imms 已提交
68
import { getTerminalResourcesFromDragEvent, getTerminalUri } from 'vs/workbench/contrib/terminal/browser/terminalUri';
M
meganrogge 已提交
69 70
import { IEditorService } from 'vs/workbench/services/editor/common/editorService';
import { TerminalEditorInput } from 'vs/workbench/contrib/terminal/browser/terminalEditorInput';
M
meganrogge 已提交
71
import { isSafari } from 'vs/base/browser/browser';
72
import { ISeparator, template } from 'vs/base/common/labels';
M
Megan Rogge 已提交
73
import { IPathService } from 'vs/workbench/services/path/common/pathService';
D
Daniel Imms 已提交
74

75 76
// How long in milliseconds should an average frame take to render for a notification to appear
// which suggests the fallback DOM-based renderer
77 78
const SLOW_CANVAS_RENDER_THRESHOLD = 50;
const NUMBER_OF_FRAMES_TO_MEASURE = 20;
79

80 81
const SHOULD_PROMPT_FOR_PROFILE_MIGRATION_KEY = 'terminals.integrated.profile-migration';

M
meganrogge 已提交
82 83
let migrationMessageShown = false;

84 85 86 87 88 89
const enum Constants {
	/**
	 * The maximum amount of milliseconds to wait for a container before starting to create the
	 * terminal process. This period helps ensure the terminal has good initial dimensions to work
	 * with if it's going to be a foreground terminal.
	 */
90 91 92 93
	WaitForContainerThreshold = 100,

	DefaultCols = 80,
	DefaultRows = 30,
94 95
}

96 97
let xtermConstructor: Promise<typeof XTermTerminal> | undefined;

98 99 100 101 102 103 104 105 106 107
interface ICanvasDimensions {
	width: number;
	height: number;
}

interface IGridDimensions {
	cols: number;
	rows: number;
}

108
export class TerminalInstance extends Disposable implements ITerminalInstance {
109 110
	private static _lastKnownCanvasDimensions: ICanvasDimensions | undefined;
	private static _lastKnownGridDimensions: IGridDimensions | undefined;
D
Daniel Imms 已提交
111
	private static _instanceIdCounter = 1;
112
	private static _suggestedRendererType: 'canvas' | 'dom' | undefined = undefined;
D
Daniel Imms 已提交
113

114
	private _processManager!: ITerminalProcessManager;
115
	private _pressAnyKeyToCloseListener: IDisposable | undefined;
D
Daniel Imms 已提交
116

D
Daniel Imms 已提交
117
	private _instanceId: number;
118 119
	private _latestXtermWriteData: number = 0;
	private _latestXtermParseData: number = 0;
D
Daniel Imms 已提交
120
	private _isExiting: boolean;
K
Kai Wood 已提交
121
	private _hadFocusOnExit: boolean;
D
Daniel Imms 已提交
122
	private _isVisible: boolean;
123
	private _isDisposed: boolean;
D
Daniel Imms 已提交
124
	private _exitCode: number | undefined;
D
Daniel Imms 已提交
125
	private _skipTerminalCommands: string[];
126
	private _shellType: TerminalShellType;
127
	private _title: string = '';
128
	private _titleSource: TitleEventSource = TitleEventSource.Process;
129
	private _container: HTMLElement | undefined;
D
Daniel Imms 已提交
130
	private _wrapperElement: (HTMLElement & { xterm?: XTermTerminal }) | undefined;
131
	private _xterm: XTermTerminal | undefined;
132
	private _xtermCore: XTermCore | undefined;
133
	private _xtermTypeAhead: TypeAheadAddon | undefined;
D
Daniel Imms 已提交
134
	private _xtermSearch: SearchAddon | undefined;
D
Daniel Imms 已提交
135
	private _xtermUnicode11: Unicode11Addon | undefined;
136
	private _xtermElement: HTMLDivElement | undefined;
137
	private _terminalHasTextContextKey: IContextKey<boolean>;
138
	private _terminalA11yTreeFocusContextKey: IContextKey<boolean>;
139 140
	private _cols: number = 0;
	private _rows: number = 0;
M
Megan Rogge 已提交
141 142
	private _cwd: string | undefined = undefined;
	private _initialCwd: string | undefined = undefined;
A
Alex Dima 已提交
143
	private _dimensionsOverride: ITerminalDimensionsOverride | undefined;
144
	private _xtermReadyPromise: Promise<XTermTerminal>;
145
	private _titleReadyPromise: Promise<string>;
146
	private _titleReadyComplete: ((title: string) => any) | undefined;
147
	private _areLinksReady: boolean = false;
148
	private _initialDataEvents: string[] | undefined = [];
149
	private _containerReadyBarrier: AutoOpenBarrier;
150
	private _attachBarrier: AutoOpenBarrier;
D
Daniel Imms 已提交
151

152
	private _messageTitleDisposable: IDisposable | undefined;
153

154
	private _widgetManager: TerminalWidgetManager = this._instantiationService.createInstance(TerminalWidgetManager);
155
	private _linkManager: TerminalLinkManager | undefined;
156
	private _environmentInfo: { widget: EnvironmentVariableInfoWidget, disposable: IDisposable } | undefined;
157
	private _webglAddon: WebglAddon | undefined;
158
	private _commandTrackerAddon: CommandTrackerAddon | undefined;
159
	private _navigationModeAddon: INavigationMode & ITerminalAddon | undefined;
D
Daniel Imms 已提交
160
	private _dndObserver: IDisposable | undefined;
161

162 163
	private readonly _resource: URI;

D
Daniel Imms 已提交
164
	private _lastLayoutDimensions: dom.Dimension | undefined;
J
jeanp413 已提交
165

D
Daniel Imms 已提交
166
	private _hasHadInput: boolean;
M
Megan Rogge 已提交
167

M
meganrogge 已提交
168

169
	readonly statusList: ITerminalStatusList;
D
Daniel Imms 已提交
170
	disableLayout: boolean = false;
171 172 173 174 175 176 177 178

	private _capabilities: ProcessCapability[] = [];
	private _description?: string;
	private _processName: string = '';
	private _sequence?: string;
	private _staticTitle?: string;
	private _workspaceFolder?: string;
	private _labelComputer?: TerminalLabelComputer;
M
Megan Rogge 已提交
179
	private _userHome?: string;
180

181
	target?: TerminalLocation;
182
	get instanceId(): number { return this._instanceId; }
183
	get resource(): URI { return this._resource; }
184
	get cols(): number {
185
		if (this._dimensionsOverride && this._dimensionsOverride.cols) {
A
Alex Dima 已提交
186 187 188
			if (this._dimensionsOverride.forceExactSize) {
				return this._dimensionsOverride.cols;
			}
189 190 191 192
			return Math.min(Math.max(this._dimensionsOverride.cols, 2), this._cols);
		}
		return this._cols;
	}
193
	get rows(): number {
194
		if (this._dimensionsOverride && this._dimensionsOverride.rows) {
A
Alex Dima 已提交
195 196 197
			if (this._dimensionsOverride.forceExactSize) {
				return this._dimensionsOverride.rows;
			}
198 199 200 201
			return Math.min(Math.max(this._dimensionsOverride.rows, 2), this._rows);
		}
		return this._rows;
	}
202 203
	get maxCols(): number { return this._cols; }
	get maxRows(): number { return this._rows; }
D
Daniel Imms 已提交
204
	// TODO: Ideally processId would be merged into processReady
205
	get processId(): number | undefined { return this._processManager.shellProcessId; }
206
	// TODO: How does this work with detached processes?
D
Daniel Imms 已提交
207
	// TODO: Should this be an event as it can fire twice?
208
	get processReady(): Promise<void> { return this._processManager.ptyProcessReady; }
M
meganrogge 已提交
209
	get hasChildProcesses(): boolean { return this.shellLaunchConfig.attachPersistentProcess?.hasChildProcesses || this._processManager.hasChildProcesses; }
210 211 212
	get areLinksReady(): boolean { return this._areLinksReady; }
	get initialDataEvents(): string[] | undefined { return this._initialDataEvents; }
	get exitCode(): number | undefined { return this._exitCode; }
213

214 215 216 217 218 219 220 221
	get hadFocusOnExit(): boolean { return this._hadFocusOnExit; }
	get isTitleSetByProcess(): boolean { return !!this._messageTitleDisposable; }
	get shellLaunchConfig(): IShellLaunchConfig { return this._shellLaunchConfig; }
	get shellType(): TerminalShellType { return this._shellType; }
	get commandTracker(): CommandTrackerAddon | undefined { return this._commandTrackerAddon; }
	get navigationMode(): INavigationMode | undefined { return this._navigationModeAddon; }
	get isDisconnected(): boolean { return this._processManager.isDisconnected; }
	get isRemote(): boolean { return this._processManager.remoteAuthority !== undefined; }
222
	get hasFocus(): boolean { return this._wrapperElement?.contains(document.activeElement) ?? false; }
223
	get title(): string { return this._title; }
224
	get titleSource(): TitleEventSource { return this._titleSource; }
M
Megan Rogge 已提交
225
	get icon(): TerminalIcon | undefined { return this._getIcon(); }
226
	get color(): string | undefined { return this._getColor(); }
227

228 229 230 231 232 233 234 235
	get processName(): string { return this._processName; }
	get sequence(): string | undefined { return this._sequence; }
	get staticTitle(): string | undefined { return this._staticTitle; }
	get workspaceFolder(): string | undefined { return this._workspaceFolder; }
	get cwd(): string | undefined { return this._cwd; }
	get initialCwd(): string | undefined { return this._initialCwd; }
	get capabilities(): ProcessCapability[] { return this._capabilities; }
	get description(): string | undefined { return this._description || this.shellLaunchConfig.description; }
M
Megan Rogge 已提交
236
	get userHome(): string | undefined { return this._userHome; }
237 238 239
	// The onExit event is special in that it fires and is disposed after the terminal instance
	// itself is disposed
	private readonly _onExit = new Emitter<number | undefined>();
240
	readonly onExit = this._onExit.event;
241

242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263
	private readonly _onDisposed = this._register(new Emitter<ITerminalInstance>());
	readonly onDisposed = this._onDisposed.event;
	private readonly _onProcessIdReady = this._register(new Emitter<ITerminalInstance>());
	readonly onProcessIdReady = this._onProcessIdReady.event;
	private readonly _onLinksReady = this._register(new Emitter<ITerminalInstance>());
	readonly onLinksReady = this._onLinksReady.event;
	private readonly _onTitleChanged = this._register(new Emitter<ITerminalInstance>());
	readonly onTitleChanged = this._onTitleChanged.event;
	private readonly _onIconChanged = this._register(new Emitter<ITerminalInstance>());
	readonly onIconChanged = this._onIconChanged.event;
	private readonly _onData = this._register(new Emitter<string>());
	readonly onData = this._onData.event;
	private readonly _onBinary = this._register(new Emitter<string>());
	readonly onBinary = this._onBinary.event;
	private readonly _onLineData = this._register(new Emitter<string>());
	readonly onLineData = this._onLineData.event;
	private readonly _onRequestExtHostProcess = this._register(new Emitter<ITerminalInstance>());
	readonly onRequestExtHostProcess = this._onRequestExtHostProcess.event;
	private readonly _onDimensionsChanged = this._register(new Emitter<void>());
	readonly onDimensionsChanged = this._onDimensionsChanged.event;
	private readonly _onMaximumDimensionsChanged = this._register(new Emitter<void>());
	readonly onMaximumDimensionsChanged = this._onMaximumDimensionsChanged.event;
264 265 266 267
	private readonly _onDidFocus = this._register(new Emitter<ITerminalInstance>());
	readonly onDidFocus = this._onDidFocus.event;
	private readonly _onDidBlur = this._register(new Emitter<ITerminalInstance>());
	readonly onDidBlur = this._onDidBlur.event;
268 269
	private readonly _onDidInputData = this._register(new Emitter<ITerminalInstance>());
	readonly onDidInputData = this._onDidInputData.event;
270 271
	private readonly _onRequestAddInstanceToGroup = this._register(new Emitter<IRequestAddInstanceToGroupEvent>());
	readonly onRequestAddInstanceToGroup = this._onRequestAddInstanceToGroup.event;
272 273
	private readonly _onDidChangeHasChildProcesses = this._register(new Emitter<boolean>());
	readonly onDidChangeHasChildProcesses = this._onDidChangeHasChildProcesses.event;
D
Daniel Imms 已提交
274

275
	constructor(
276
		private readonly _terminalFocusContextKey: IContextKey<boolean>,
277
		private readonly _terminalShellTypeContextKey: IContextKey<string>,
278
		private readonly _terminalAltBufferActiveContextKey: IContextKey<boolean>,
279
		private readonly _configHelper: TerminalConfigHelper,
280
		private _shellLaunchConfig: IShellLaunchConfig,
281
		resource: URI | undefined,
282
		@ITerminalInstanceService private readonly _terminalInstanceService: ITerminalInstanceService,
D
Daniel Imms 已提交
283
		@ITerminalProfileResolverService private readonly _terminalProfileResolverService: ITerminalProfileResolverService,
M
Megan Rogge 已提交
284
		@IPathService private readonly _pathService: IPathService,
285 286
		@IContextKeyService private readonly _contextKeyService: IContextKeyService,
		@IKeybindingService private readonly _keybindingService: IKeybindingService,
287
		@INotificationService private readonly _notificationService: INotificationService,
288
		@IPreferencesService private readonly _preferencesService: IPreferencesService,
289
		@IViewsService private readonly _viewsService: IViewsService,
290 291 292
		@IInstantiationService private readonly _instantiationService: IInstantiationService,
		@IClipboardService private readonly _clipboardService: IClipboardService,
		@IThemeService private readonly _themeService: IThemeService,
D
Daniel Imms 已提交
293
		@IConfigurationService private readonly _configurationService: IConfigurationService,
294
		@ILogService private readonly _logService: ILogService,
295
		@IStorageService private readonly _storageService: IStorageService,
296
		@IAccessibilityService private readonly _accessibilityService: IAccessibilityService,
297
		@IViewDescriptorService private readonly _viewDescriptorService: IViewDescriptorService,
298
		@IProductService private readonly _productService: IProductService,
299
		@IQuickInputService private readonly _quickInputService: IQuickInputService,
300
		@IWorkbenchEnvironmentService workbenchEnvironmentService: IWorkbenchEnvironmentService,
M
meganrogge 已提交
301 302
		@IWorkspaceContextService private readonly _workspaceContextService: IWorkspaceContextService,
		@IEditorService private readonly _editorService: IEditorService
303
	) {
304 305
		super();

D
Daniel Imms 已提交
306
		this._skipTerminalCommands = [];
D
Daniel Imms 已提交
307
		this._isExiting = false;
K
Kai Wood 已提交
308
		this._hadFocusOnExit = false;
D
Daniel Imms 已提交
309
		this._isVisible = false;
310
		this._isDisposed = false;
D
Daniel Imms 已提交
311
		this._instanceId = TerminalInstance._instanceIdCounter++;
312

D
Daniel Imms 已提交
313
		this._hasHadInput = false;
314 315 316 317
		this._titleReadyPromise = new Promise<string>(c => {
			this._titleReadyComplete = c;
		});

318 319 320
		// the resource is already set when it's been moved from another window
		this._resource = resource || getTerminalUri(this._workspaceContextService.getWorkspace().id, this.instanceId, this.title);

321 322 323
		this._terminalHasTextContextKey = TerminalContextKeys.textSelected.bindTo(this._contextKeyService);
		this._terminalA11yTreeFocusContextKey = TerminalContextKeys.a11yTreeFocus.bindTo(this._contextKeyService);
		this._terminalAltBufferActiveContextKey = TerminalContextKeys.altBufferActive.bindTo(this._contextKeyService);
D
Daniel Imms 已提交
324

D
Daniel Imms 已提交
325
		this._logService.trace(`terminalInstance#ctor (instanceId: ${this.instanceId})`, this._shellLaunchConfig);
D
Daniel Imms 已提交
326

327 328 329 330 331
		// Resolve just the icon ahead of time so that it shows up immediately in the tabs. This is
		// disabled in remote because this needs to be sync and the OS may differ on the remote
		// which would result in the wrong profile being selected and the wrong icon being
		// permanently attached to the terminal.
		if (!this.shellLaunchConfig.executable && !workbenchEnvironmentService.remoteAuthority) {
D
Daniel Imms 已提交
332
			this._terminalProfileResolverService.resolveIcon(this._shellLaunchConfig, OS);
D
Daniel Imms 已提交
333 334
		}

335 336 337
		// When a custom pty is used set the name immediately so it gets passed over to the exthost
		// and is available when Pseudoterminal.open fires.
		if (this.shellLaunchConfig.customPtyImplementation) {
338
			this.refreshTabLabels(this._shellLaunchConfig.name, TitleEventSource.Api);
339 340
		}

341
		this.statusList = this._instantiationService.createInstance(TerminalStatusList);
342
		this._initDimensions();
343
		this._createProcessManager();
D
Daniel Imms 已提交
344

D
Daniel Imms 已提交
345 346
		this._register(toDisposable(() => this._dndObserver?.dispose()));

347
		this._containerReadyBarrier = new AutoOpenBarrier(Constants.WaitForContainerThreshold);
348
		this._attachBarrier = new AutoOpenBarrier(1000);
D
Daniel Imms 已提交
349
		this._xtermReadyPromise = this._createXterm();
350 351 352
		this._xtermReadyPromise.then(async () => {
			// Wait for a period to allow a container to be ready
			await this._containerReadyBarrier.wait();
353
			await this._createProcess();
354 355 356

			// Re-establish the title after reconnect
			if (this.shellLaunchConfig.attachPersistentProcess) {
357
				this.refreshTabLabels(this.shellLaunchConfig.attachPersistentProcess.title, this.shellLaunchConfig.attachPersistentProcess.titleSource);
358
			}
D
Daniel Imms 已提交
359
		});
360

361
		this.addDisposable(this._configurationService.onDidChangeConfiguration(async e => {
362
			if (e.affectsConfiguration(TerminalSettingId.GpuAcceleration)) {
363
				TerminalInstance._suggestedRendererType = undefined;
364
			}
365
			if (e.affectsConfiguration('terminal.integrated') || e.affectsConfiguration('editor.fastScrollSensitivity') || e.affectsConfiguration('editor.mouseWheelScrollSensitivity') || e.affectsConfiguration('editor.multiCursorModifier')) {
366
				this.updateConfig();
367
				this.setVisible(this._isVisible);
368
			}
369 370 371 372 373 374 375 376 377 378 379 380
			const layoutSettings: string[] = [
				TerminalSettingId.FontSize,
				TerminalSettingId.FontFamily,
				TerminalSettingId.FontWeight,
				TerminalSettingId.FontWeightBold,
				TerminalSettingId.LetterSpacing,
				TerminalSettingId.LineHeight,
				'editor.fontFamily'
			];
			if (layoutSettings.some(id => e.affectsConfiguration(id))) {
				await this._resize();
			}
D
Daniel Imms 已提交
381
			if (e.affectsConfiguration(TerminalSettingId.UnicodeVersion)) {
D
Daniel Imms 已提交
382 383
				this._updateUnicodeVersion();
			}
384 385 386
			if (e.affectsConfiguration('editor.accessibilitySupport')) {
				this.updateAccessibilitySupport();
			}
387 388 389 390 391 392
			if (
				e.affectsConfiguration(TerminalSettingId.TerminalTitle) ||
				e.affectsConfiguration(TerminalSettingId.TerminalTitleSeparator) ||
				e.affectsConfiguration(TerminalSettingId.TerminalDescription)) {
				this._labelComputer?.refreshLabel();
			}
393
		}));
394
		this._workspaceContextService.onDidChangeWorkspaceFolders(() => this._labelComputer?.refreshLabel());
395 396 397

		// Clear out initial data events after 10 seconds, hopefully extension hosts are up and
		// running at that point.
D
Daniel Imms 已提交
398
		let initialDataEventsTimeout: number | undefined = window.setTimeout(() => {
399 400 401
			initialDataEventsTimeout = undefined;
			this._initialDataEvents = undefined;
		}, 10000);
D
Daniel Imms 已提交
402 403 404
		this._register(toDisposable(() => {
			if (initialDataEventsTimeout) {
				window.clearTimeout(initialDataEventsTimeout);
405
			}
D
Daniel Imms 已提交
406
		}));
407
		this.showProfileMigrationNotification();
408 409
	}

M
Megan Rogge 已提交
410 411 412 413
	private _getIcon(): TerminalIcon | undefined {
		const icon = this._shellLaunchConfig.icon || this._shellLaunchConfig.attachPersistentProcess?.icon;
		if (!icon) {
			return this._processManager.processState >= ProcessState.Launching ? Codicon.terminal : undefined;
414
		}
M
Megan Rogge 已提交
415
		return icon;
416 417
	}

418 419 420 421 422 423 424 425 426 427 428 429 430
	private _getColor(): string | undefined {
		if (this.shellLaunchConfig.color) {
			return this.shellLaunchConfig.color;
		}
		if (this.shellLaunchConfig?.attachPersistentProcess?.color) {
			return this.shellLaunchConfig.attachPersistentProcess.color;
		}
		if (this._processManager.processState >= ProcessState.Launching) {
			return undefined;
		}
		return undefined;
	}

431
	addDisposable(disposable: IDisposable): void {
432
		this._register(disposable);
D
Daniel Imms 已提交
433 434
	}

435 436 437
	async showProfileMigrationNotification(): Promise<void> {
		const platform = this._getPlatformKey();
		const shouldMigrateToProfile = (!!this._configurationService.getValue(TerminalSettingPrefix.Shell + platform) ||
M
meganrogge 已提交
438
			!!this._configurationService.inspect(TerminalSettingPrefix.ShellArgs + platform).userValue) &&
439
			!!this._configurationService.getValue(TerminalSettingPrefix.DefaultProfile + platform);
M
meganrogge 已提交
440
		if (shouldMigrateToProfile && this._storageService.getBoolean(SHOULD_PROMPT_FOR_PROFILE_MIGRATION_KEY, StorageScope.WORKSPACE, true) && !migrationMessageShown) {
441 442 443 444 445 446 447 448 449 450
			this._notificationService.prompt(
				Severity.Info,
				nls.localize('terminalProfileMigration', "The terminal is using deprecated shell/shellArgs settings, do you want to migrate it to a profile?"),
				[
					{
						label: nls.localize('migrateToProfile', "Migrate"),
						run: async () => {
							const shell = this._configurationService.getValue(TerminalSettingPrefix.Shell + platform);
							const shellArgs = this._configurationService.getValue(TerminalSettingPrefix.ShellArgs + platform);
							const profile = await this._terminalProfileResolverService.createProfileFromShellAndShellArgs(shell, shellArgs);
451 452 453
							if (typeof profile === 'string') {
								await this._configurationService.updateValue(TerminalSettingPrefix.DefaultProfile + platform, profile);
								this._logService.trace(`migrated from shell/shellArgs, using existing profile ${profile}`);
454
							} else {
455
								const profiles = { ...this._configurationService.inspect<Readonly<{ [key: string]: ITerminalProfileObject }>>(TerminalSettingPrefix.Profiles + platform).userValue } || {};
456 457 458 459 460 461 462 463
								const profileConfig: ITerminalProfileObject = { path: profile.path };
								if (profile.args) {
									profileConfig.args = profile.args;
								}
								profiles[profile.profileName] = profileConfig;
								await this._configurationService.updateValue(TerminalSettingPrefix.Profiles + platform, profiles);
								await this._configurationService.updateValue(TerminalSettingPrefix.DefaultProfile + platform, profile.profileName);
								this._logService.trace(`migrated from shell/shellArgs, ${shell} ${shellArgs} to profile ${JSON.stringify(profile)}`);
464
							}
465 466
							await this._configurationService.updateValue(TerminalSettingPrefix.Shell + platform, undefined);
							await this._configurationService.updateValue(TerminalSettingPrefix.ShellArgs + platform, undefined);
467 468 469 470 471 472 473
						}
					} as IPromptChoice,
				],
				{
					neverShowAgain: { id: SHOULD_PROMPT_FOR_PROFILE_MIGRATION_KEY, scope: NeverShowAgainScope.WORKSPACE }
				}
			);
M
meganrogge 已提交
474
			migrationMessageShown = true;
475 476 477 478 479 480 481
		}
	}

	private _getPlatformKey(): string {
		return isWindows ? 'windows' : (isMacintosh ? 'osx' : 'linux');
	}

482 483 484 485 486 487
	private _initDimensions(): void {
		// The terminal panel needs to have been created
		if (!this._container) {
			return;
		}

D
Daniel Imms 已提交
488
		const computedStyle = window.getComputedStyle(this._wrapperElement!);
489 490 491 492 493 494 495 496 497
		const width = parseInt(computedStyle.getPropertyValue('width').replace('px', ''), 10);
		const height = parseInt(computedStyle.getPropertyValue('height').replace('px', ''), 10);
		this._evaluateColsAndRows(width, height);
	}

	/**
	 * Evaluates and sets the cols and rows of the terminal if possible.
	 * @param width The width of the container.
	 * @param height The height of the container.
D
Daniel Imms 已提交
498
	 * @return The terminal's width if it requires a layout.
499
	 */
D
Daniel Imms 已提交
500
	private _evaluateColsAndRows(width: number, height: number): number | null {
D
Daniel Imms 已提交
501 502
		// Ignore if dimensions are undefined or 0
		if (!width || !height) {
503
			this._setLastKnownColsAndRows();
D
Daniel Imms 已提交
504 505 506
			return null;
		}

507 508
		const dimension = this._getDimension(width, height);
		if (!dimension) {
509
			this._setLastKnownColsAndRows();
510 511
			return null;
		}
D
Daniel Imms 已提交
512

513
		const font = this._configHelper.getFont(this._xtermCore);
D
Daniel Imms 已提交
514
		if (!font.charWidth || !font.charHeight) {
515
			this._setLastKnownColsAndRows();
D
Daniel Imms 已提交
516 517
			return null;
		}
518

D
Daniel Imms 已提交
519 520 521 522 523
		// Because xterm.js converts from CSS pixels to actual pixels through
		// the use of canvas, window.devicePixelRatio needs to be used here in
		// order to be precise. font.charWidth/charHeight alone as insufficient
		// when window.devicePixelRatio changes.
		const scaledWidthAvailable = dimension.width * window.devicePixelRatio;
524

525
		const scaledCharWidth = font.charWidth * window.devicePixelRatio + font.letterSpacing;
526
		const newCols = Math.max(Math.floor(scaledWidthAvailable / scaledCharWidth), 1);
D
Daniel Imms 已提交
527 528

		const scaledHeightAvailable = dimension.height * window.devicePixelRatio;
529 530
		const scaledCharHeight = Math.ceil(font.charHeight * window.devicePixelRatio);
		const scaledLineHeight = Math.floor(scaledCharHeight * font.lineHeight);
531 532 533 534 535
		const newRows = Math.max(Math.floor(scaledHeightAvailable / scaledLineHeight), 1);

		if (this._cols !== newCols || this._rows !== newRows) {
			this._cols = newCols;
			this._rows = newRows;
536
			this._fireMaximumDimensionsChanged();
537
		}
538

539 540
		return dimension.width;
	}
541

542 543 544 545 546 547 548
	private _setLastKnownColsAndRows(): void {
		if (TerminalInstance._lastKnownGridDimensions) {
			this._cols = TerminalInstance._lastKnownGridDimensions.cols;
			this._rows = TerminalInstance._lastKnownGridDimensions.rows;
		}
	}

549 550 551 552
	@debounce(50)
	private _fireMaximumDimensionsChanged(): void {
		this._onMaximumDimensionsChanged.fire();
	}
553

554
	private _getDimension(width: number, height: number): ICanvasDimensions | undefined {
555
		// The font needs to have been initialized
556
		const font = this._configHelper.getFont(this._xtermCore);
557
		if (!font || !font.charWidth || !font.charHeight) {
558
			return undefined;
559 560
		}

561
		if (!this._wrapperElement) {
562
			return undefined;
563 564
		}

D
Daniel Imms 已提交
565
		TerminalInstance._lastKnownCanvasDimensions = new dom.Dimension(width, height - 2 /* bottom padding */);
566
		return TerminalInstance._lastKnownCanvasDimensions;
567 568
	}

569 570
	get persistentProcessId(): number | undefined { return this._processManager.persistentProcessId; }
	get shouldPersist(): boolean { return this._processManager.shouldPersist; }
571

572 573 574 575 576 577 578 579 580 581 582 583 584 585
	private async _getXtermConstructor(): Promise<typeof XTermTerminal> {
		if (xtermConstructor) {
			return xtermConstructor;
		}
		xtermConstructor = new Promise<typeof XTermTerminal>(async (resolve) => {
			const Terminal = await this._terminalInstanceService.getXtermConstructor();
			// Localize strings
			Terminal.strings.promptLabel = nls.localize('terminal.integrated.a11yPromptLabel', 'Terminal input');
			Terminal.strings.tooMuchOutput = nls.localize('terminal.integrated.a11yTooMuchOutput', 'Too much output to announce, navigate to rows manually to read');
			resolve(Terminal);
		});
		return xtermConstructor;
	}

586 587 588
	/**
	 * Create xterm.js instance and attach data listeners.
	 */
589
	protected async _createXterm(): Promise<XTermTerminal> {
590
		const Terminal = await this._getXtermConstructor();
591
		const font = this._configHelper.getFont(undefined, true);
D
Daniel Imms 已提交
592
		const config = this._configHelper.config;
593 594
		const editorOptions = this._configurationService.getValue<IEditorOptions>('editor');

595
		const xterm = new Terminal({
596 597
			cols: this._cols || Constants.DefaultCols,
			rows: this._rows || Constants.DefaultRows,
598
			altClickMovesCursor: config.altClickMovesCursor && editorOptions.multiCursorModifier === 'alt',
D
Daniel Imms 已提交
599
			scrollback: config.scrollback,
600
			theme: this._getXtermTheme(),
601
			drawBoldTextInBrightColors: config.drawBoldTextInBrightColors,
602
			fontFamily: font.fontFamily,
D
Daniel Imms 已提交
603 604
			fontWeight: config.fontWeight,
			fontWeightBold: config.fontWeightBold,
605
			fontSize: font.fontSize,
606
			letterSpacing: font.letterSpacing,
607
			lineHeight: font.lineHeight,
608
			minimumContrastRatio: config.minimumContrastRatio,
609
			cursorBlink: config.cursorBlinking,
610
			cursorStyle: config.cursorStyle === 'line' ? 'bar' : config.cursorStyle,
611
			cursorWidth: config.cursorWidth,
D
Daniel Imms 已提交
612
			bellStyle: 'none',
D
Daniel Imms 已提交
613
			macOptionIsMeta: config.macOptionIsMeta,
614
			macOptionClickForcesSelection: config.macOptionClickForcesSelection,
D
Daniel Imms 已提交
615
			rightClickSelectsWord: config.rightClickBehavior === 'selectWord',
D
Daniel Imms 已提交
616
			fastScrollModifier: 'alt',
617 618
			fastScrollSensitivity: editorOptions.fastScrollSensitivity,
			scrollSensitivity: editorOptions.mouseWheelScrollSensitivity,
619
			rendererType: this._getBuiltInXtermRenderer(config.gpuAcceleration, TerminalInstance._suggestedRendererType),
620
			wordSeparator: config.wordSeparators
621
		});
622
		this._xterm = xterm;
623
		this._xtermCore = (xterm as any)._core as XTermCore;
D
Daniel Imms 已提交
624
		this._updateUnicodeVersion();
625
		this.updateAccessibilitySupport();
D
Daniel Imms 已提交
626 627
		this._terminalInstanceService.getXtermSearchConstructor().then(addonCtor => {
			this._xtermSearch = new addonCtor();
628
			xterm.loadAddon(this._xtermSearch);
D
Daniel Imms 已提交
629
		});
630 631 632
		if (this._shellLaunchConfig.initialText) {
			this._xterm.writeln(this._shellLaunchConfig.initialText);
		}
633 634 635
		// Delay the creation of the bell listener to avoid showing the bell when the terminal
		// starts up or reconnects
		setTimeout(() => {
636 637
			this._xterm?.onBell(() => {
				if (this._configHelper.config.enableBell) {
D
Daniel Imms 已提交
638 639 640 641 642
					this.statusList.add({
						id: TerminalStatus.Bell,
						severity: Severity.Warning,
						icon: Codicon.bell,
						tooltip: nls.localize('bellStatus', "Bell")
M
meganrogge 已提交
643
					}, this._configHelper.config.bellDuration);
644 645
				}
			});
646
		}, 1000);
D
Daniel Imms 已提交
647 648
		this._xterm.onLineFeed(() => this._onLineFeed());
		this._xterm.onKey(e => this._onKey(e.key, e.domEvent));
649
		this._xterm.onSelectionChange(async () => this._onSelectionChange());
650
		this._xterm.buffer.onBufferChange(() => this._refreshAltBufferContextKey());
651

A
Alex Dima 已提交
652
		this._processManager.onProcessData(e => this._onProcessData(e));
653 654 655 656
		this._xterm.onData(async data => {
			await this._processManager.write(data);
			this._onDidInputData.fire(this);
		});
657
		this._xterm.onBinary(data => this._processManager.processBinary(data));
D
Daniel Imms 已提交
658
		this.processReady.then(async () => {
659 660
			if (this._linkManager) {
				this._linkManager.processCwd = await this._processManager.getInitialCwd();
661
			}
D
Daniel Imms 已提交
662 663 664
		});
		// Init winpty compat and link handler after process creation as they rely on the
		// underlying process OS
665
		this._processManager.onProcessReady((processTraits) => {
D
Daniel Imms 已提交
666
			if (this._processManager.os === OperatingSystem.Windows) {
667
				xterm.setOption('windowsMode', processTraits.requiresWindowsMode || false);
D
Daniel Imms 已提交
668 669 670
				// Force line data to be sent when the cursor is moved, the main purpose for
				// this is because ConPTY will often not do a line feed but instead move the
				// cursor, in which case we still want to send the current line's data to tasks.
D
Daniel Imms 已提交
671
				xterm.parser.registerCsiHandler({ final: 'H' }, () => {
D
Daniel Imms 已提交
672 673 674 675
					this._onCursorMove();
					return false;
				});
			}
D
Daniel Imms 已提交
676
			this._linkManager = this._instantiationService.createInstance(TerminalLinkManager, xterm, this._processManager!);
677 678
			this._areLinksReady = true;
			this._onLinksReady.fire(this);
D
Daniel Imms 已提交
679
		});
D
Daniel Imms 已提交
680

681 682
		this._commandTrackerAddon = new CommandTrackerAddon();
		this._xterm.loadAddon(this._commandTrackerAddon);
M
Martin Aeschlimann 已提交
683
		this._register(this._themeService.onDidColorThemeChange(theme => this._updateTheme(xterm, theme)));
684 685 686 687 688
		this._register(this._viewDescriptorService.onDidChangeLocation(({ views }) => {
			if (views.some(v => v.id === TERMINAL_VIEW_ID)) {
				this._updateTheme(xterm);
			}
		}));
689

690 691
		this._xtermTypeAhead = this._register(this._instantiationService.createInstance(TypeAheadAddon, this._processManager, this._configHelper));
		this._xterm.loadAddon(this._xtermTypeAhead);
M
Megan Rogge 已提交
692 693 694
		this._pathService.userHome().then(userHome => {
			this._userHome = userHome.fsPath;
		});
695
		return xterm;
696 697
	}

M
meganrogge 已提交
698
	detachFromElement(): void {
J
Jean Pierre 已提交
699
		this._wrapperElement?.remove();
M
meganrogge 已提交
700 701 702
		this._container = undefined;
	}

703

D
Daniel Imms 已提交
704
	attachToElement(container: HTMLElement): Promise<void> | void {
705 706 707 708 709
		// The container did not change, do nothing
		if (this._container === container) {
			return;
		}

710 711
		this._attachBarrier.open();

I
Ikko Ashimine 已提交
712
		// Attach has not occurred yet
713
		if (!this._wrapperElement) {
D
Daniel Imms 已提交
714
			return this._attachToElement(container);
715 716
		}

717 718 719 720 721
		// Update the theme when attaching as the terminal location could have changed
		if (this._xterm) {
			this._updateTheme(this._xterm);
		}

722 723 724
		// The container changed, reattach
		this._container = container;
		this._container.appendChild(this._wrapperElement);
D
Daniel Imms 已提交
725
		setTimeout(() => this._initDragAndDrop(container));
726 727
	}

D
Daniel Imms 已提交
728
	private async _attachToElement(container: HTMLElement): Promise<void> {
729 730 731
		if (this._wrapperElement) {
			throw new Error('The terminal instance has already been attached to a container');
		}
732

733 734
		this._container = container;
		this._wrapperElement = document.createElement('div');
735
		this._wrapperElement.classList.add('terminal-wrapper');
736
		this._xtermElement = document.createElement('div');
D
Daniel Imms 已提交
737

D
Daniel Imms 已提交
738 739 740 741 742
		this._wrapperElement.appendChild(this._xtermElement);
		this._container.appendChild(this._wrapperElement);

		const xterm = await this._xtermReadyPromise;

743
		// Attach the xterm object to the DOM, exposing it to the smoke tests
D
Daniel Imms 已提交
744
		this._wrapperElement.xterm = xterm;
745

D
Daniel Imms 已提交
746
		this._updateTheme(xterm);
747
		xterm.open(this._xtermElement);
D
Daniel Imms 已提交
748

749 750 751
		if (!xterm.element || !xterm.textarea) {
			throw new Error('xterm elements not set after open');
		}
752

D
Daniel Imms 已提交
753
		this._setAriaLabel(xterm, this._instanceId, this._title);
754

755 756 757 758
		xterm.attachCustomKeyEventHandler((event: KeyboardEvent): boolean => {
			// Disable all input if the terminal is exiting
			if (this._isExiting) {
				return false;
759 760
			}

761 762
			const standardKeyboardEvent = new StandardKeyboardEvent(event);
			const resolveResult = this._keybindingService.softDispatch(standardKeyboardEvent, standardKeyboardEvent.target);
763

764 765 766
			// Respect chords if the allowChords setting is set and it's not Escape. Escape is
			// handled specially for Zen Mode's Escape, Escape chord, plus it's important in
			// terminals generally
767 768
			const isValidChord = resolveResult?.enterChord && this._configHelper.config.allowChords && event.key !== 'Escape';
			if (this._keybindingService.inChordMode || isValidChord) {
769 770 771
				event.preventDefault();
				return false;
			}
D
Daniel Imms 已提交
772

773
			const SHOW_TERMINAL_CONFIG_PROMPT_KEY = 'terminal.integrated.showTerminalConfigPrompt';
M
Megan Rogge 已提交
774 775 776
			const EXCLUDED_KEYS = ['RightArrow', 'LeftArrow', 'UpArrow', 'DownArrow', 'Space', 'Meta', 'Control', 'Shift', 'Alt', '', 'Delete', 'Backspace', 'Tab'];

			// only keep track of input if prompt hasn't already been shown
777
			if (this._storageService.getBoolean(SHOW_TERMINAL_CONFIG_PROMPT_KEY, StorageScope.GLOBAL, true) &&
M
Megan Rogge 已提交
778 779 780 781
				!EXCLUDED_KEYS.includes(event.key) &&
				!event.ctrlKey &&
				!event.shiftKey &&
				!event.altKey) {
D
Daniel Imms 已提交
782
				this._hasHadInput = true;
M
Megan Rogge 已提交
783 784
			}

785 786
			// for keyboard events that resolve to commands described
			// within commandsToSkipShell, either alert or skip processing by xterm.js
M
Megan Rogge 已提交
787
			if (resolveResult && resolveResult.commandId && this._skipTerminalCommands.some(k => k === resolveResult.commandId) && !this._configHelper.config.sendKeybindingsToShell) {
788
				// don't alert when terminal is opened or closed
789
				if (this._storageService.getBoolean(SHOW_TERMINAL_CONFIG_PROMPT_KEY, StorageScope.GLOBAL, true) &&
D
Daniel Imms 已提交
790
					this._hasHadInput &&
M
Megan Rogge 已提交
791
					!TERMINAL_CREATION_COMMANDS.includes(resolveResult.commandId)) {
792 793
					this._notificationService.prompt(
						Severity.Info,
794
						nls.localize('keybindingHandling', "Some keybindings don't go to the terminal by default and are handled by {0} instead.", this._productService.nameLong),
795 796 797 798
						[
							{
								label: nls.localize('configureTerminalSettings', "Configure Terminal Settings"),
								run: () => {
799
									this._preferencesService.openSettings({ jsonEditor: false, query: `@id:${TerminalSettingId.CommandsToSkipShell},${TerminalSettingId.SendKeybindingsToShell},${TerminalSettingId.AllowChords}` });
800 801 802 803
								}
							} as IPromptChoice
						]
					);
804
					this._storageService.store(SHOW_TERMINAL_CONFIG_PROMPT_KEY, false, StorageScope.GLOBAL, StorageTarget.USER);
805
				}
806 807 808
				event.preventDefault();
				return false;
			}
D
Daniel Imms 已提交
809

810
			// Skip processing by xterm.js of keyboard events that match menu bar mnemonics
D
Daniel Imms 已提交
811
			if (this._configHelper.config.allowMnemonics && !isMacintosh && event.altKey) {
812 813 814
				return false;
			}

815 816 817 818
			// If tab focus mode is on, tab is not passed to the terminal
			if (TabFocus.getTabFocusMode() && event.keyCode === 9) {
				return false;
			}
819

820 821
			// Always have alt+F4 skip the terminal on Windows and allow it to be handled by the
			// system
D
Daniel Imms 已提交
822
			if (isWindows && event.altKey && event.key === 'F4' && !event.ctrlKey) {
823 824
				return false;
			}
825

826 827 828 829 830 831
			// Fallback to force ctrl+v to paste on browsers that do not support
			// navigator.clipboard.readText
			if (!BrowserFeatures.clipboard.readText && event.key === 'v' && event.ctrlKey) {
				return false;
			}

832 833 834 835 836 837 838 839 840 841
			return true;
		});
		this._register(dom.addDisposableListener(xterm.element, 'mousedown', () => {
			// We need to listen to the mouseup event on the document since the user may release
			// the mouse button anywhere outside of _xterm.element.
			const listener = dom.addDisposableListener(document, 'mouseup', () => {
				// Delay with a setTimeout to allow the mouseup to propagate through the DOM
				// before evaluating the new selection state.
				setTimeout(() => this._refreshSelectionContextKey(), 0);
				listener.dispose();
D
Daniel Imms 已提交
842
			});
843
		}));
844 845 846
		this._register(dom.addDisposableListener(xterm.element, 'touchstart', () => {
			xterm.focus();
		}));
D
Daniel Imms 已提交
847

848 849 850 851 852 853
		// xterm.js currently drops selection on keyup as we need to handle this case.
		this._register(dom.addDisposableListener(xterm.element, 'keyup', () => {
			// Wait until keyup has propagated through the DOM before evaluating
			// the new selection state.
			setTimeout(() => this._refreshSelectionContextKey(), 0);
		}));
D
Daniel Imms 已提交
854

855 856
		this._register(dom.addDisposableListener(xterm.textarea, 'focus', () => {
			this._terminalFocusContextKey.set(true);
D
Daniel Imms 已提交
857 858 859 860 861
			if (this.shellType) {
				this._terminalShellTypeContextKey.set(this.shellType.toString());
			} else {
				this._terminalShellTypeContextKey.reset();
			}
862
			this._onDidFocus.fire(this);
863
		}));
864

865 866
		this._register(dom.addDisposableListener(xterm.textarea, 'blur', () => {
			this._terminalFocusContextKey.reset();
867
			this._onDidBlur.fire(this);
868 869
			this._refreshSelectionContextKey();
		}));
870

D
Daniel Imms 已提交
871
		this._initDragAndDrop(container);
872

873
		this._widgetManager.attachToElement(xterm.element);
874 875 876
		this._processManager.onProcessReady((e) => {
			this._linkManager?.setWidgetManager(this._widgetManager);
			this._capabilities = e.capabilities;
M
meganrogge 已提交
877
			this._workspaceFolder = path.basename(e.cwd.toString());
878
		});
879

D
Daniel Imms 已提交
880
		// const computedStyle = window.getComputedStyle(this._container);
D
Daniel Imms 已提交
881 882 883 884 885 886
		// const computedStyle = window.getComputedStyle(this._container.parentElement!);
		// const width = parseInt(computedStyle.getPropertyValue('width').replace('px', ''), 10);
		// const height = parseInt(computedStyle.getPropertyValue('height').replace('px', ''), 10);
		if (this._lastLayoutDimensions) {
			this.layout(this._lastLayoutDimensions);
		}
887 888 889 890 891 892 893 894
		this.setVisible(this._isVisible);
		this.updateConfig();

		// If IShellLaunchConfig.waitOnExit was true and the process finished before the terminal
		// panel was initialized.
		if (xterm.getOption('disableStdin')) {
			this._attachPressAnyKeyToCloseListener(xterm);
		}
895 896
	}

D
Daniel Imms 已提交
897 898
	private _initDragAndDrop(container: HTMLElement) {
		this._dndObserver?.dispose();
J
jeanp413 已提交
899
		const dndController = this._instantiationService.createInstance(TerminalInstanceDragAndDropController, container);
D
Daniel Imms 已提交
900 901 902 903 904 905
		dndController.onDropTerminal(e => this._onRequestAddInstanceToGroup.fire(e));
		dndController.onDropFile(async path => {
			const preparedPath = await this._terminalInstanceService.preparePathForTerminalAsync(path, this.shellLaunchConfig.executable, this.title, this.shellType, this.isRemote);
			this.sendText(preparedPath, false);
			this.focus();
		});
J
jeanp413 已提交
906
		this._dndObserver = new DragAndDropObserver(container, dndController);
D
Daniel Imms 已提交
907 908
	}

909
	private async _measureRenderTime(): Promise<void> {
910
		await this._xtermReadyPromise;
D
Daniel Imms 已提交
911
		const frameTimes: number[] = [];
912 913 914 915 916
		if (!this._xtermCore?._renderService) {
			return;
		}
		const textRenderLayer = this._xtermCore!._renderService?._renderer._renderLayers[0];
		const originalOnGridChanged = textRenderLayer?.onGridChanged;
917 918 919 920
		const evaluateCanvasRenderer = () => {
			// Discard first frame time as it's normal to take longer
			frameTimes.shift();

D
Daniel Imms 已提交
921
			const medianTime = frameTimes.sort((a, b) => a - b)[Math.floor(frameTimes.length / 2)];
922
			if (medianTime > SLOW_CANVAS_RENDER_THRESHOLD) {
923
				if (this._configHelper.config.gpuAcceleration === 'auto') {
924
					TerminalInstance._suggestedRendererType = 'dom';
D
Daniel Imms 已提交
925 926 927 928 929
					this.updateConfig();
				} else {
					const promptChoices: IPromptChoice[] = [
						{
							label: nls.localize('yes', "Yes"),
D
Daniel Imms 已提交
930
							run: () => this._configurationService.updateValue(TerminalSettingId.GpuAcceleration, 'off', ConfigurationTarget.USER)
D
Daniel Imms 已提交
931 932 933 934 935 936 937 938
						} as IPromptChoice,
						{
							label: nls.localize('no', "No"),
							run: () => { }
						} as IPromptChoice,
						{
							label: nls.localize('dontShowAgain', "Don't Show Again"),
							isSecondary: true,
D
Daniel Imms 已提交
939
							run: () => this._storageService.store(TerminalStorageKeys.NeverMeasureRenderTime, true, StorageScope.GLOBAL, StorageTarget.MACHINE)
D
Daniel Imms 已提交
940 941 942 943
						} as IPromptChoice
					];
					this._notificationService.prompt(
						Severity.Warning,
944
						nls.localize('terminal.slowRendering', 'Terminal GPU acceleration appears to be slow on your computer. Would you like to switch to disable it which may improve performance? [Read more about terminal settings](https://code.visualstudio.com/docs/editor/integrated-terminal#_changing-how-the-terminal-is-rendered).'),
D
Daniel Imms 已提交
945 946 947
						promptChoices
					);
				}
D
Daniel Imms 已提交
948
			}
949
		};
950

951 952 953 954 955 956 957 958 959
		textRenderLayer.onGridChanged = (terminal: XTermTerminal, firstRow: number, lastRow: number) => {
			const startTime = performance.now();
			originalOnGridChanged.call(textRenderLayer, terminal, firstRow, lastRow);
			frameTimes.push(performance.now() - startTime);
			if (frameTimes.length === NUMBER_OF_FRAMES_TO_MEASURE) {
				evaluateCanvasRenderer();
				// Restore original function
				textRenderLayer.onGridChanged = originalOnGridChanged;
			}
D
Daniel Imms 已提交
960 961 962
		};
	}

963
	hasSelection(): boolean {
964
		return this._xterm ? this._xterm.hasSelection() : false;
965 966
	}

967
	async copySelection(): Promise<void> {
968
		const xterm = await this._xtermReadyPromise;
D
Daniel Imms 已提交
969
		if (this.hasSelection()) {
970
			await this._clipboardService.writeText(xterm.getSelection());
D
Daniel Imms 已提交
971
		} else {
972
			this._notificationService.warn(nls.localize('terminal.integrated.copySelection.noSelection', 'The terminal has no selection to copy'));
D
Daniel Imms 已提交
973
		}
D
Daniel Imms 已提交
974 975
	}

976
	get selection(): string | undefined {
977
		return this._xterm && this.hasSelection() ? this._xterm.getSelection() : undefined;
978 979
	}

980
	clearSelection(): void {
981
		this._xterm?.clearSelection();
982 983
	}

984
	selectAll(): void {
985
		// Focus here to ensure the terminal context key is set
986 987
		this._xterm?.focus();
		this._xterm?.selectAll();
988 989
	}

990
	findNext(term: string, searchOptions: ISearchOptions): boolean {
D
Daniel Imms 已提交
991 992 993
		if (!this._xtermSearch) {
			return false;
		}
D
Daniel Imms 已提交
994
		return this._xtermSearch.findNext(term, searchOptions);
R
rebornix 已提交
995 996
	}

997
	findPrevious(term: string, searchOptions: ISearchOptions): boolean {
D
Daniel Imms 已提交
998 999 1000
		if (!this._xtermSearch) {
			return false;
		}
D
Daniel Imms 已提交
1001
		return this._xtermSearch.findPrevious(term, searchOptions);
R
rebornix 已提交
1002 1003
	}

1004
	notifyFindWidgetFocusChanged(isFocused: boolean): void {
1005 1006 1007
		if (!this._xterm) {
			return;
		}
1008 1009
		const terminalFocused = !isFocused && (document.activeElement === this._xterm.textarea || document.activeElement === this._xterm.element);
		this._terminalFocusContextKey.set(terminalFocused);
1010
	}
R
rebornix 已提交
1011

1012 1013 1014 1015
	private _refreshAltBufferContextKey() {
		this._terminalAltBufferActiveContextKey.set(!!(this._xterm && this._xterm.buffer.active === this._xterm.buffer.alternate));
	}

1016
	override dispose(immediate?: boolean): void {
D
Daniel Imms 已提交
1017
		this._logService.trace(`terminalInstance#dispose (instanceId: ${this.instanceId})`);
1018 1019
		dispose(this._linkManager);
		this._linkManager = undefined;
1020 1021
		dispose(this._commandTrackerAddon);
		this._commandTrackerAddon = undefined;
1022
		dispose(this._widgetManager);
1023

K
Kai Wood 已提交
1024
		if (this._xterm && this._xterm.element) {
1025
			this._hadFocusOnExit = this.hasFocus;
K
Kai Wood 已提交
1026
		}
D
Daniel Imms 已提交
1027
		if (this._wrapperElement) {
D
Daniel Imms 已提交
1028 1029
			if (this._wrapperElement.xterm) {
				this._wrapperElement.xterm = undefined;
1030
			}
1031
			if (this._wrapperElement.parentElement && this._container) {
D
Daniel Imms 已提交
1032 1033
				this._container.removeChild(this._wrapperElement);
			}
D
Daniel Imms 已提交
1034
		}
D
Daniel Imms 已提交
1035
		if (this._xterm) {
1036
			const buffer = this._xterm.buffer;
D
Daniel Imms 已提交
1037
			this._sendLineData(buffer.active, buffer.active.baseY + buffer.active.cursorY);
D
Daniel Imms 已提交
1038
			this._xterm.dispose();
D
Daniel Imms 已提交
1039
		}
1040 1041 1042 1043 1044 1045

		if (this._pressAnyKeyToCloseListener) {
			this._pressAnyKeyToCloseListener.dispose();
			this._pressAnyKeyToCloseListener = undefined;
		}

1046
		this._processManager.dispose(immediate);
1047 1048 1049
		// Process manager dispose/shutdown doesn't fire process exit, trigger with undefined if it
		// hasn't happened yet
		this._onProcessExit(undefined);
1050

1051 1052 1053 1054
		if (!this._isDisposed) {
			this._isDisposed = true;
			this._onDisposed.fire(this);
		}
1055
		super.dispose();
D
Daniel Imms 已提交
1056 1057
	}

1058 1059
	async detachFromProcess(): Promise<void> {
		await this._processManager.detachFromProcess();
1060
		this.dispose();
D
Daniel Imms 已提交
1061 1062
	}

1063
	forceRedraw(): void {
1064 1065 1066
		if (!this._xterm) {
			return;
		}
1067
		this._webglAddon?.clearTextureAtlas();
D
Daniel Imms 已提交
1068
		this._xterm?.clearTextureAtlas();
1069 1070
	}

1071
	focus(force?: boolean): void {
1072
		this._refreshAltBufferContextKey();
1073 1074 1075
		if (!this._xterm) {
			return;
		}
D
Daniel Imms 已提交
1076 1077 1078 1079 1080
		const selection = window.getSelection();
		if (!selection) {
			return;
		}
		const text = selection.toString();
1081 1082 1083 1084 1085
		if (!text || force) {
			this._xterm.focus();
		}
	}

1086
	async focusWhenReady(force?: boolean): Promise<void> {
1087
		await this._xtermReadyPromise;
1088
		await this._attachBarrier.wait();
1089
		this.focus(force);
D
Daniel Imms 已提交
1090 1091
	}

1092
	async paste(): Promise<void> {
1093 1094 1095
		if (!this._xterm) {
			return;
		}
D
Daniel Imms 已提交
1096
		this.focus();
1097
		this._xterm.paste(await this._clipboardService.readText());
D
Daniel Imms 已提交
1098
	}
D
Daniel Imms 已提交
1099

1100
	async pasteSelection(): Promise<void> {
1101 1102 1103 1104
		if (!this._xterm) {
			return;
		}
		this.focus();
A
Adrian Wilkins 已提交
1105
		this._xterm.paste(await this._clipboardService.readText('selection'));
1106
	}
D
Daniel Imms 已提交
1107

1108
	async sendText(text: string, addNewLine: boolean): Promise<void> {
D
Daniel Imms 已提交
1109
		// Normalize line endings to 'enter' press.
D
Daniel Imms 已提交
1110
		text = text.replace(/\r?\n/g, '\r');
D
Daniel Imms 已提交
1111 1112 1113 1114
		if (addNewLine && text.substr(text.length - 1) !== '\r') {
			text += '\r';
		}

1115
		// Send it to the process
1116 1117
		await this._processManager.write(text);
		this._onDidInputData.fire(this);
D
Daniel Imms 已提交
1118
	}
1119

1120
	setVisible(visible: boolean): void {
D
Daniel Imms 已提交
1121 1122
		this._isVisible = visible;
		if (this._wrapperElement) {
1123
			this._wrapperElement.classList.toggle('active', visible);
1124
		}
1125
		if (visible && this._xterm && this._xtermCore) {
D
Daniel Imms 已提交
1126 1127 1128 1129 1130
			// Resize to re-evaluate dimensions, this will ensure when switching to a terminal it is
			// using the most up to date dimensions (eg. when terminal is created in the background
			// using cached dimensions of a split terminal).
			this._resize();

1131 1132 1133 1134
			// Trigger a manual scroll event which will sync the viewport and scroll bar. This is
			// necessary if the number of rows in the terminal has decreased while it was in the
			// background since scrollTop changes take no effect but the terminal's position does
			// change since the number of visible rows decreases.
D
Daniel Imms 已提交
1135 1136
			// This can likely be removed after https://github.com/xtermjs/xterm.js/issues/291 is
			// fixed upstream.
D
Daniel Imms 已提交
1137
			this._xtermCore._onScroll.fire(this._xterm.buffer.active.viewportY);
1138
		}
1139 1140
	}

1141
	scrollDownLine(): void {
1142
		this._xterm?.scrollLines(1);
D
Daniel Imms 已提交
1143 1144
	}

1145
	scrollDownPage(): void {
1146
		this._xterm?.scrollPages(1);
1147 1148
	}

1149
	scrollToBottom(): void {
1150
		this._xterm?.scrollToBottom();
1151 1152
	}

1153
	scrollUpLine(): void {
1154
		this._xterm?.scrollLines(-1);
D
Daniel Imms 已提交
1155
	}
1156

1157
	scrollUpPage(): void {
1158
		this._xterm?.scrollPages(-1);
1159 1160
	}

1161
	scrollToTop(): void {
1162
		this._xterm?.scrollToTop();
1163 1164
	}

1165
	clear(): void {
1166
		this._xterm?.clear();
D
Daniel Imms 已提交
1167 1168
	}

1169
	private _refreshSelectionContextKey() {
1170
		const isActive = !!this._viewsService.getActiveViewWithId(TERMINAL_VIEW_ID);
M
meganrogge 已提交
1171 1172 1173 1174 1175 1176
		let isEditorActive = false;
		const editor = this._editorService.activeEditor;
		if (editor) {
			isEditorActive = editor instanceof TerminalEditorInput;
		}
		this._terminalHasTextContextKey.set((isActive || isEditorActive) && this.hasSelection());
1177 1178
	}

1179
	protected _createProcessManager(): void {
D
Daniel Imms 已提交
1180
		this._processManager = this._instantiationService.createInstance(TerminalProcessManager, this._instanceId, this._configHelper);
1181
		this._processManager.onProcessReady(async (e) => {
1182
			this._onProcessIdReady.fire(this);
M
Megan Rogge 已提交
1183
			this._initialCwd = await this.getInitialCwd();
1184
			this._capabilities = e.capabilities;
1185 1186
			// Set the initial name based on the _resolved_ shell launch config, this will also
			// ensure the resolved icon gets shown
1187 1188 1189 1190 1191 1192 1193 1194
			if (!this._labelComputer) {
				this._labelComputer = this._register(new TerminalLabelComputer(this._configHelper, this, this._workspaceContextService));
				this._labelComputer.onDidChangeLabel(e => {
					this._title = e.title;
					this._description = e.description;
					this._onTitleChanged.fire(this);
				});
			}
M
Megan Rogge 已提交
1195
			this._processManager.onDidChangeProperty(e => {
1196
				if (e.type === ProcessPropertyType.Cwd) {
M
Megan Rogge 已提交
1197
					this._cwd = e.value;
1198
					this._labelComputer?.refreshLabel();
1199 1200 1201
				} else if (e.type === ProcessPropertyType.InitialCwd) {
					this._initialCwd = e.value;
					this._cwd = this._initialCwd;
1202
					this.refreshTabLabels(this.title, TitleEventSource.Api);
M
Megan Rogge 已提交
1203 1204
				}
			});
1205
			if (this._shellLaunchConfig.name) {
1206
				this.refreshTabLabels(this._shellLaunchConfig.name, TitleEventSource.Api);
1207
			} else {
M
Megan Rogge 已提交
1208 1209 1210 1211 1212
				// Listen to xterm.js' sequence title change event, trigger this async to ensure
				// _xtermReadyPromise is ready constructed since this is called from the ctor
				setTimeout(() => {
					this._xtermReadyPromise.then(xterm => {
						this._messageTitleDisposable = xterm.onTitleChange(e => this._onTitleChange(e));
1213
					});
M
Megan Rogge 已提交
1214 1215 1216
				});
				this.refreshTabLabels(this._shellLaunchConfig.executable, TitleEventSource.Process);
				this._messageTitleDisposable = this._processManager.onProcessTitle(title => this.refreshTabLabels(title ? title : '', TitleEventSource.Process));
1217
			}
1218
		});
D
Daniel Imms 已提交
1219
		this._processManager.onProcessExit(exitCode => this._onProcessExit(exitCode));
A
Alex Dima 已提交
1220 1221 1222
		this._processManager.onProcessData(ev => {
			this._initialDataEvents?.push(ev.data);
			this._onData.fire(ev.data);
1223
		});
A
Alex Dima 已提交
1224
		this._processManager.onProcessOverrideDimensions(e => this.setDimensions(e, true));
1225
		this._processManager.onProcessResolvedShellLaunchConfig(e => this._setResolvedShellLaunchConfig(e));
1226
		this._processManager.onProcessDidChangeHasChildProcesses(e => this._onDidChangeHasChildProcesses.fire(e));
D
Daniel Imms 已提交
1227
		this._processManager.onEnvironmentVariableInfoChanged(e => this._onEnvironmentVariableInfoChanged(e));
1228 1229 1230
		this._processManager.onProcessShellTypeChanged(type => this.setShellType(type));
		this._processManager.onPtyDisconnect(() => {
			this._safeSetOption('disableStdin', true);
D
Daniel Imms 已提交
1231 1232 1233 1234 1235 1236
			this.statusList.add({
				id: TerminalStatus.Disconnected,
				severity: Severity.Error,
				icon: Codicon.debugDisconnect,
				tooltip: nls.localize('disconnectStatus', "Lost connection to process")
			});
1237 1238 1239
		});
		this._processManager.onPtyReconnect(() => {
			this._safeSetOption('disableStdin', false);
1240
			this.statusList.remove(TerminalStatus.Disconnected);
1241
		});
1242 1243
	}

1244
	private async _createProcess(): Promise<void> {
1245 1246 1247
		if (this._isDisposed) {
			return;
		}
1248 1249 1250 1251 1252 1253 1254

		// Re-evaluate dimensions if the container has been set since the xterm instance was created
		if (this._container && this._cols === 0 && this._rows === 0) {
			this._initDimensions();
			this._xterm?.resize(this._cols || Constants.DefaultCols, this._rows || Constants.DefaultRows);
		}

1255
		const hadIcon = !!this.shellLaunchConfig.icon;
1256
		await this._processManager.createProcess(this._shellLaunchConfig, this._cols || Constants.DefaultCols, this._rows || Constants.DefaultRows, this._accessibilityService.isScreenReaderOptimized()).then(error => {
D
Daniel Imms 已提交
1257 1258 1259 1260
			if (error) {
				this._onProcessExit(error);
			}
		});
1261
		if (!hadIcon && this.shellLaunchConfig.icon || this.shellLaunchConfig.color) {
1262 1263
			this._onIconChanged.fire(this);
		}
D
Daniel Imms 已提交
1264 1265
	}

A
Alex Dima 已提交
1266
	private _onProcessData(ev: IProcessDataEvent): void {
1267 1268 1269 1270 1271 1272 1273 1274 1275
		const messageId = ++this._latestXtermWriteData;
		if (ev.trackCommit) {
			ev.writePromise = new Promise<void>(r => {
				this._xterm?.write(ev.data, () => {
					this._latestXtermParseData = messageId;
					this._processManager.acknowledgeDataEvent(ev.data.length);
					r();
				});
			});
A
Alex Dima 已提交
1276
		} else {
1277 1278
			this._xterm?.write(ev.data, () => {
				this._latestXtermParseData = messageId;
D
Daniel Imms 已提交
1279
				this._processManager.acknowledgeDataEvent(ev.data.length);
1280
			});
A
Alex Dima 已提交
1281
		}
1282 1283
	}

1284
	/**
D
Daniel Imms 已提交
1285
	 * Called when either a process tied to a terminal has exited or when a terminal renderer
1286
	 * simulates a process exiting (e.g. custom execution task).
D
Daniel Imms 已提交
1287 1288
	 * @param exitCode The exit code of the process, this is undefined when the terminal was exited
	 * through user action.
1289
	 */
1290
	private async _onProcessExit(exitCodeOrError?: number | ITerminalLaunchError): Promise<void> {
1291 1292 1293 1294 1295
		// Prevent dispose functions being triggered multiple times
		if (this._isExiting) {
			return;
		}

1296 1297 1298
		this._isExiting = true;

		await this._flushXtermData();
D
Daniel Imms 已提交
1299
		this._logService.debug(`Terminal process exit (instanceId: ${this.instanceId}) with code ${this._exitCode}`);
1300

1301
		let exitCodeMessage: string | undefined;
B
bpceee 已提交
1302

1303
		// Create exit code message
1304 1305
		switch (typeof exitCodeOrError) {
			case 'number':
1306
				// Only show the error if the exit code is non-zero
1307
				this._exitCode = exitCodeOrError;
1308 1309 1310
				if (this._exitCode === 0) {
					break;
				}
1311

1312 1313 1314
				let commandLine: string | undefined = undefined;
				if (this._shellLaunchConfig.executable) {
					commandLine = this._shellLaunchConfig.executable;
1315
					if (typeof this._shellLaunchConfig.args === 'string') {
1316
						commandLine += ` ${this._shellLaunchConfig.args}`;
1317
					} else if (this._shellLaunchConfig.args && this._shellLaunchConfig.args.length) {
1318
						commandLine += this._shellLaunchConfig.args.map(a => ` '${a}'`).join();
1319
					}
1320 1321
				}

D
Daniel Imms 已提交
1322
				if (this._processManager.processState === ProcessState.KilledDuringLaunch) {
1323
					if (commandLine) {
1324
						exitCodeMessage = nls.localize('launchFailed.exitCodeAndCommandLine', "The terminal process \"{0}\" failed to launch (exit code: {1}).", commandLine, this._exitCode);
1325
						break;
1326
					}
1327
					exitCodeMessage = nls.localize('launchFailed.exitCodeOnly', "The terminal process failed to launch (exit code: {0}).", this._exitCode);
1328 1329 1330
					break;
				}
				if (commandLine) {
1331
					exitCodeMessage = nls.localize('terminated.exitCodeAndCommandLine', "The terminal process \"{0}\" terminated with exit code: {1}.", commandLine, this._exitCode);
1332
					break;
1333
				}
1334
				exitCodeMessage = nls.localize('terminated.exitCodeOnly', "The terminal process terminated with exit code: {0}.", this._exitCode);
1335 1336
				break;
			case 'object':
M
meganrogge 已提交
1337 1338 1339
				if (exitCodeOrError.message.toString().includes('Could not find pty with id')) {
					break;
				}
1340
				this._exitCode = exitCodeOrError.code;
1341
				exitCodeMessage = nls.localize('launchFailed.errorMessage', "The terminal process failed to launch: {0}.", exitCodeOrError.message);
1342
				break;
1343
		}
1344

D
Daniel Imms 已提交
1345
		this._logService.debug(`Terminal process exit (instanceId: ${this.instanceId}) state ${this._processManager.processState}`);
D
Daniel Imms 已提交
1346

1347 1348
		// Only trigger wait on exit when the exit was *not* triggered by the
		// user (via the `workbench.action.terminal.kill` command).
D
Daniel Imms 已提交
1349
		if (this._shellLaunchConfig.waitOnExit && this._processManager.processState !== ProcessState.KilledByUser) {
1350 1351 1352 1353 1354
			this._xtermReadyPromise.then(xterm => {
				if (exitCodeMessage) {
					xterm.writeln(exitCodeMessage);
				}
				if (typeof this._shellLaunchConfig.waitOnExit === 'string') {
1355
					xterm.write(formatMessageForTerminal(this._shellLaunchConfig.waitOnExit));
1356 1357 1358 1359 1360 1361 1362
				}
				// Disable all input if the terminal is exiting and listen for next keypress
				xterm.setOption('disableStdin', true);
				if (xterm.textarea) {
					this._attachPressAnyKeyToCloseListener(xterm);
				}
			});
1363 1364
		} else {
			this.dispose();
1365
			if (exitCodeMessage) {
D
Daniel Imms 已提交
1366
				const failedDuringLaunch = this._processManager.processState === ProcessState.KilledDuringLaunch;
D
Daniel Imms 已提交
1367 1368 1369 1370 1371
				if (failedDuringLaunch || this._configHelper.config.showExitAlert) {
					// Always show launch failures
					this._notificationService.notify({
						message: exitCodeMessage,
						severity: Severity.Error,
1372
						actions: { primary: [this._instantiationService.createInstance(TerminalLaunchHelpAction)] }
D
Daniel Imms 已提交
1373
					});
G
Gabriel DeBacker 已提交
1374
				} else {
D
Daniel Imms 已提交
1375 1376 1377
					// Log to help surface the error in case users report issues with showExitAlert
					// disabled
					this._logService.warn(exitCodeMessage);
1378 1379
				}
			}
1380
		}
D
Daniel Imms 已提交
1381

1382
		// First onExit to consumers, this can happen after the terminal has already been disposed.
1383
		this._onExit.fire(this._exitCode);
1384 1385 1386 1387 1388

		// Dispose of the onExit event if the terminal will not be reused again
		if (this._isDisposed) {
			this._onExit.dispose();
		}
1389 1390
	}

1391 1392 1393 1394 1395
	/**
	 * Ensure write calls to xterm.js have finished before resolving.
	 */
	private _flushXtermData(): Promise<void> {
		if (this._latestXtermWriteData === this._latestXtermParseData) {
D
Daniel Imms 已提交
1396
			return Promise.resolve();
1397 1398 1399 1400 1401 1402 1403 1404 1405 1406 1407 1408
		}
		let retries = 0;
		return new Promise<void>(r => {
			const interval = setInterval(() => {
				if (this._latestXtermWriteData === this._latestXtermParseData || ++retries === 5) {
					clearInterval(interval);
					r();
				}
			}, 20);
		});
	}

1409
	private _attachPressAnyKeyToCloseListener(xterm: XTermTerminal) {
1410
		if (xterm.textarea && !this._pressAnyKeyToCloseListener) {
1411
			this._pressAnyKeyToCloseListener = dom.addDisposableListener(xterm.textarea, 'keypress', (event: KeyboardEvent) => {
1412 1413 1414
				if (this._pressAnyKeyToCloseListener) {
					this._pressAnyKeyToCloseListener.dispose();
					this._pressAnyKeyToCloseListener = undefined;
1415 1416 1417 1418 1419
					this.dispose();
					event.preventDefault();
				}
			});
		}
1420 1421
	}

1422
	async reuseTerminal(shell: IShellLaunchConfig, reset: boolean = false): Promise<void> {
1423
		// Unsubscribe any key listener we may have.
1424 1425
		this._pressAnyKeyToCloseListener?.dispose();
		this._pressAnyKeyToCloseListener = undefined;
1426

1427
		if (this._xterm) {
D
Daniel Imms 已提交
1428
			if (!reset) {
1429
				// Ensure new processes' output starts at start of new line
1430
				await new Promise<void>(r => this._xterm!.write('\n\x1b[G', r));
1431
			}
1432

1433 1434
			// Print initialText if specified
			if (shell.initialText) {
1435
				await new Promise<void>(r => this._xterm!.writeln(shell.initialText!, r));
1436
			}
1437

1438 1439 1440 1441 1442
			// Clean up waitOnExit state
			if (this._isExiting && this._shellLaunchConfig.waitOnExit) {
				this._xterm.setOption('disableStdin', false);
				this._isExiting = false;
			}
1443
		}
1444

1445
		// Dispose the environment info widget if it exists
1446
		this.statusList.remove(TerminalStatus.RelaunchNeeded);
1447
		this._environmentInfo?.disposable.dispose();
1448
		this._environmentInfo = undefined;
1449

1450 1451
		if (!reset) {
			// HACK: Force initialText to be non-falsy for reused terminals such that the
L
Ladislau Szomoru 已提交
1452 1453 1454
			// conptyInheritCursor flag is passed to the node-pty, this flag can cause a Window to stop
			// responding in Windows 10 1903 so we only want to use it when something is definitely written
			// to the terminal.
1455 1456
			shell.initialText = ' ';
		}
1457

1458
		// Set the new shell launch config
1459
		this._shellLaunchConfig = shell; // Must be done before calling _createProcess()
1460

1461
		this._processManager.relaunch(this._shellLaunchConfig, this._cols || Constants.DefaultCols, this._rows || Constants.DefaultRows, this._accessibilityService.isScreenReaderOptimized(), reset);
1462

1463
		this._xtermTypeAhead?.reset();
1464 1465
	}

1466
	@debounce(1000)
1467
	relaunch(): void {
1468 1469 1470
		this.reuseTerminal(this._shellLaunchConfig, true);
	}

1471
	private _onLineFeed(): void {
1472
		const buffer = this._xterm!.buffer;
D
Daniel Imms 已提交
1473
		const newLine = buffer.active.getLine(buffer.active.baseY + buffer.active.cursorY);
1474
		if (newLine && !newLine.isWrapped) {
D
Daniel Imms 已提交
1475
			this._sendLineData(buffer.active, buffer.active.baseY + buffer.active.cursorY - 1);
1476 1477 1478
		}
	}

1479
	private _onCursorMove(): void {
1480
		const buffer = this._xterm!.buffer;
D
Daniel Imms 已提交
1481
		this._sendLineData(buffer.active, buffer.active.baseY + buffer.active.cursorY);
1482 1483
	}

1484 1485
	private _onTitleChange(title: string): void {
		if (this.isTitleSetByProcess) {
1486
			this.refreshTabLabels(title, TitleEventSource.Sequence);
1487 1488 1489
		}
	}

1490
	private _sendLineData(buffer: IBuffer, lineIndex: number): void {
D
Daniel Imms 已提交
1491 1492 1493 1494 1495 1496 1497 1498 1499 1500 1501
		let line = buffer.getLine(lineIndex);
		if (!line) {
			return;
		}
		let lineData = line.translateToString(true);
		while (lineIndex > 0 && line.isWrapped) {
			line = buffer.getLine(--lineIndex);
			if (!line) {
				break;
			}
			lineData = line.translateToString(false) + lineData;
1502
		}
D
Daniel Imms 已提交
1503
		this._onLineData.fire(lineData);
1504 1505
	}

N
void  
Noj Vek 已提交
1506
	private _onKey(key: string, ev: KeyboardEvent): void {
1507 1508 1509 1510 1511 1512 1513
		const event = new StandardKeyboardEvent(ev);

		if (event.equals(KeyCode.Enter)) {
			this._updateProcessCwd();
		}
	}

D
Daniel Imms 已提交
1514
	private async _onSelectionChange(): Promise<void> {
D
Daniel Imms 已提交
1515
		if (this._configurationService.getValue(TerminalSettingId.CopyOnSelection)) {
1516 1517 1518 1519 1520 1521
			if (this.hasSelection()) {
				await this.copySelection();
			}
		}
	}

1522 1523 1524
	@debounce(2000)
	private async _updateProcessCwd(): Promise<string> {
		// reset cwd if it has changed, so file based url paths can be resolved
M
meganrogge 已提交
1525
		const cwd = await this.refreshProperty(ProcessPropertyType.Cwd);
1526
		if (cwd && this._linkManager) {
M
Megan Rogge 已提交
1527
			this._linkManager.processCwd = cwd;
1528 1529 1530 1531
		}
		return cwd;
	}

1532
	updateConfig(): void {
1533
		const config = this._configHelper.config;
1534
		this._safeSetOption('altClickMovesCursor', config.altClickMovesCursor);
1535 1536
		this._setCursorBlink(config.cursorBlinking);
		this._setCursorStyle(config.cursorStyle);
B
Bura Chuhadar 已提交
1537
		this._setCursorWidth(config.cursorWidth);
1538
		this._setCommandsToSkipShell(config.commandsToSkipShell);
1539
		this._safeSetOption('scrollback', config.scrollback);
1540
		this._safeSetOption('drawBoldTextInBrightColors', config.drawBoldTextInBrightColors);
1541
		this._safeSetOption('minimumContrastRatio', config.minimumContrastRatio);
1542 1543
		this._safeSetOption('fastScrollSensitivity', config.fastScrollSensitivity);
		this._safeSetOption('scrollSensitivity', config.mouseWheelScrollSensitivity);
1544
		this._safeSetOption('macOptionIsMeta', config.macOptionIsMeta);
1545 1546
		const editorOptions = this._configurationService.getValue<IEditorOptions>('editor');
		this._safeSetOption('altClickMovesCursor', config.altClickMovesCursor && editorOptions.multiCursorModifier === 'alt');
1547
		this._safeSetOption('macOptionClickForcesSelection', config.macOptionClickForcesSelection);
1548
		this._safeSetOption('rightClickSelectsWord', config.rightClickBehavior === 'selectWord');
1549
		this._safeSetOption('wordSeparator', config.wordSeparators);
1550
		this._safeSetOption('customGlyphs', config.customGlyphs);
1551
		const suggestedRendererType = TerminalInstance._suggestedRendererType;
M
meganrogge 已提交
1552
		// @meganrogge @Tyriar remove if the issue related to iPads and webgl is resolved
M
meganrogge 已提交
1553
		if ((!isSafari && config.gpuAcceleration === 'auto' && suggestedRendererType === undefined) || config.gpuAcceleration === 'on') {
D
Daniel Imms 已提交
1554 1555
			this._enableWebglRenderer();
		} else {
1556
			this._disposeOfWebglRenderer();
1557
			this._safeSetOption('rendererType', this._getBuiltInXtermRenderer(config.gpuAcceleration, suggestedRendererType));
1558
		}
1559
		this._refreshEnvironmentVariableInfoWidgetState(this._processManager.environmentVariableInfo);
1560 1561
	}

1562 1563 1564 1565 1566 1567 1568 1569
	private _getBuiltInXtermRenderer(gpuAcceleration: string, suggestedRendererType?: string): RendererType {
		let rendererType: RendererType = 'canvas';
		if (gpuAcceleration === 'off' || (gpuAcceleration === 'auto' && suggestedRendererType === 'dom')) {
			rendererType = 'dom';
		}
		return rendererType;
	}

D
Daniel Imms 已提交
1570
	private async _enableWebglRenderer(): Promise<void> {
J
Jean Pierre 已提交
1571
		if (!this._xterm?.element || this._webglAddon) {
D
Daniel Imms 已提交
1572 1573 1574 1575
			return;
		}
		const Addon = await this._terminalInstanceService.getXtermWebglConstructor();
		this._webglAddon = new Addon();
1576 1577
		try {
			this._xterm.loadAddon(this._webglAddon);
1578 1579 1580 1581 1582
			this._webglAddon.onContextLoss(() => {
				this._logService.info(`Webgl lost context, disposing of webgl renderer`);
				this._disposeOfWebglRenderer();
				this._safeSetOption('rendererType', 'dom');
			});
1583 1584
		} catch (e) {
			this._logService.warn(`Webgl could not be loaded. Falling back to the canvas renderer type.`, e);
D
Daniel Imms 已提交
1585
			const neverMeasureRenderTime = this._storageService.getBoolean(TerminalStorageKeys.NeverMeasureRenderTime, StorageScope.GLOBAL, false);
1586
			// if it's already set to dom, no need to measure render time
1587
			if (!neverMeasureRenderTime && this._configHelper.config.gpuAcceleration !== 'off') {
1588 1589
				this._measureRenderTime();
			}
M
meganrogge 已提交
1590
			this._safeSetOption('rendererType', 'canvas');
1591
			TerminalInstance._suggestedRendererType = 'canvas';
1592
			this._disposeOfWebglRenderer();
1593 1594 1595 1596
		}
	}

	private _disposeOfWebglRenderer(): void {
D
Daniel Imms 已提交
1597 1598 1599 1600 1601
		try {
			this._webglAddon?.dispose();
		} catch {
			// ignore
		}
1602
		this._webglAddon = undefined;
D
Daniel Imms 已提交
1603 1604
	}

D
Daniel Imms 已提交
1605 1606 1607 1608 1609 1610 1611 1612 1613
	private async _updateUnicodeVersion(): Promise<void> {
		if (!this._xterm) {
			throw new Error('Cannot update unicode version before xterm has been initialized');
		}
		if (!this._xtermUnicode11 && this._configHelper.config.unicodeVersion === '11') {
			const Addon = await this._terminalInstanceService.getXtermUnicode11Constructor();
			this._xtermUnicode11 = new Addon();
			this._xterm.loadAddon(this._xtermUnicode11);
		}
1614 1615 1616 1617
		if (this._xterm.unicode.activeVersion !== this._configHelper.config.unicodeVersion) {
			this._xterm.unicode.activeVersion = this._configHelper.config.unicodeVersion;
			this._processManager.setUnicodeVersion(this._configHelper.config.unicodeVersion);
		}
D
Daniel Imms 已提交
1618 1619
	}

1620
	updateAccessibilitySupport(): void {
I
isidor 已提交
1621
		const isEnabled = this._accessibilityService.isScreenReaderOptimized();
1622 1623
		if (isEnabled) {
			this._navigationModeAddon = new NavigationModeAddon(this._terminalA11yTreeFocusContextKey);
1624
			this._xterm!.loadAddon(this._navigationModeAddon);
1625
		} else {
1626 1627
			this._navigationModeAddon?.dispose();
			this._navigationModeAddon = undefined;
1628
		}
1629
		this._xterm!.setOption('screenReaderMode', isEnabled);
1630 1631
	}

1632
	private _setCursorBlink(blink: boolean): void {
D
Daniel Imms 已提交
1633
		if (this._xterm && this._xterm.getOption('cursorBlink') !== blink) {
D
Daniel Imms 已提交
1634
			this._xterm.setOption('cursorBlink', blink);
D
Daniel Imms 已提交
1635
			this._xterm.refresh(0, this._xterm.rows - 1);
D
Daniel Imms 已提交
1636 1637 1638
		}
	}

1639 1640 1641 1642 1643 1644 1645 1646
	private _setCursorStyle(style: string): void {
		if (this._xterm && this._xterm.getOption('cursorStyle') !== style) {
			// 'line' is used instead of bar in VS Code to be consistent with editor.cursorStyle
			const xtermOption = style === 'line' ? 'bar' : style;
			this._xterm.setOption('cursorStyle', xtermOption);
		}
	}

B
Bura Chuhadar 已提交
1647
	private _setCursorWidth(width: number): void {
1648 1649 1650 1651 1652
		if (this._xterm && this._xterm.getOption('cursorWidth') !== width) {
			this._xterm.setOption('cursorWidth', width);
		}
	}

1653
	private _setCommandsToSkipShell(commands: string[]): void {
1654
		const excludeCommands = commands.filter(command => command[0] === '-').map(command => command.slice(1));
D
Daniel Imms 已提交
1655
		this._skipTerminalCommands = DEFAULT_COMMANDS_TO_SKIP_SHELL.filter(defaultCommand => {
1656 1657
			return excludeCommands.indexOf(defaultCommand) === -1;
		}).concat(commands);
D
Daniel Imms 已提交
1658 1659
	}

1660 1661 1662 1663 1664 1665 1666 1667 1668 1669
	private _safeSetOption(key: string, value: any): void {
		if (!this._xterm) {
			return;
		}

		if (this._xterm.getOption(key) !== value) {
			this._xterm.setOption(key, value);
		}
	}

1670
	layout(dimension: dom.Dimension): void {
D
Daniel Imms 已提交
1671
		this._lastLayoutDimensions = dimension;
1672 1673 1674 1675
		if (this.disableLayout) {
			return;
		}

D
Daniel Imms 已提交
1676 1677 1678 1679 1680 1681
		// Don't layout if dimensions are invalid (eg. the container is not attached to the DOM or
		// if display: none
		if (dimension.width <= 0 || dimension.height <= 0) {
			return;
		}

D
Daniel Imms 已提交
1682 1683
		const terminalWidth = this._evaluateColsAndRows(dimension.width, dimension.height);
		if (!terminalWidth) {
D
Daniel Imms 已提交
1684 1685
			return;
		}
1686

1687
		if (this._xterm && this._xterm.element) {
D
Daniel Imms 已提交
1688 1689 1690 1691
			this._xterm.element.style.width = terminalWidth + 'px';
		}

		this._resize();
1692 1693 1694

		// Signal the container is ready
		this._containerReadyBarrier.open();
D
Daniel Imms 已提交
1695 1696
	}

D
Daniel Imms 已提交
1697
	@debounce(50)
D
Daniel Imms 已提交
1698
	private async _resize(): Promise<void> {
A
Alex Dima 已提交
1699 1700 1701 1702
		this._resizeNow(false);
	}

	private async _resizeNow(immediate: boolean): Promise<void> {
1703 1704
		let cols = this.cols;
		let rows = this.rows;
D
Daniel Imms 已提交
1705

1706
		if (this._xterm && this._xtermCore) {
1707 1708 1709
			// Only apply these settings when the terminal is visible so that
			// the characters are measured correctly.
			if (this._isVisible) {
1710
				const font = this._configHelper.getFont(this._xtermCore);
1711 1712 1713 1714 1715 1716 1717
				const config = this._configHelper.config;
				this._safeSetOption('letterSpacing', font.letterSpacing);
				this._safeSetOption('lineHeight', font.lineHeight);
				this._safeSetOption('fontSize', font.fontSize);
				this._safeSetOption('fontFamily', font.fontFamily);
				this._safeSetOption('fontWeight', config.fontWeight);
				this._safeSetOption('fontWeightBold', config.fontWeightBold);
1718 1719 1720 1721 1722 1723

				// Any of the above setting changes could have changed the dimensions of the
				// terminal, re-evaluate now.
				this._initDimensions();
				cols = this.cols;
				rows = this.rows;
1724
			}
D
Daniel Imms 已提交
1725

1726 1727 1728 1729
			if (isNaN(cols) || isNaN(rows)) {
				return;
			}

1730
			if (cols !== this._xterm.cols || rows !== this._xterm.rows) {
D
Daniel Imms 已提交
1731 1732
				this._onDimensionsChanged.fire();
			}
D
Daniel Imms 已提交
1733

D
Daniel Imms 已提交
1734
			this._xterm.resize(cols, rows);
1735
			TerminalInstance._lastKnownGridDimensions = { cols, rows };
1736

1737
			if (this._isVisible) {
1738
				// HACK: Force the renderer to unpause by simulating an IntersectionObserver event.
M
Megan Rogge 已提交
1739
				// This is to fix an issue where dragging the windpow to the top of the screen to
1740 1741
				// maximize on Windows/Linux would fire an event saying that the terminal was not
				// visible.
1742
				if (this._xterm.getOption('rendererType') === 'canvas') {
1743
					this._xtermCore._renderService?._onIntersectionChange({ intersectionRatio: 1 });
1744 1745 1746
					// HACK: Force a refresh of the screen to ensure links are refresh corrected.
					// This can probably be removed when the above hack is fixed in Chromium.
					this._xterm.refresh(0, this._xterm.rows - 1);
1747
				}
1748
			}
D
Daniel Imms 已提交
1749
		}
1750

A
Alex Dima 已提交
1751 1752
		if (immediate) {
			// do not await, call setDimensions synchronously
1753
			this._processManager.setDimensions(cols, rows, true);
A
Alex Dima 已提交
1754
		} else {
1755
			await this._processManager.setDimensions(cols, rows);
A
Alex Dima 已提交
1756
		}
D
Daniel Imms 已提交
1757
	}
1758

1759
	setShellType(shellType: TerminalShellType) {
1760 1761 1762
		this._shellType = shellType;
	}

1763 1764 1765 1766 1767 1768 1769 1770 1771 1772
	private _setAriaLabel(xterm: XTermTerminal | undefined, terminalId: number, title: string | undefined): void {
		if (xterm) {
			if (title && title.length > 0) {
				xterm.textarea?.setAttribute('aria-label', nls.localize('terminalTextBoxAriaLabelNumberAndTitle', "Terminal {0}, {1}", terminalId, title));
			} else {
				xterm.textarea?.setAttribute('aria-label', nls.localize('terminalTextBoxAriaLabel', "Terminal {0}", terminalId));
			}
		}
	}

1773 1774 1775 1776 1777 1778 1779 1780 1781 1782 1783 1784 1785 1786 1787 1788 1789 1790
	refreshTabLabels(title: string | undefined, eventSource: TitleEventSource): void {
		title = this._updateTitleProperties(title, eventSource);
		const titleChanged = title !== this._title;
		this._title = title;
		this._labelComputer?.refreshLabel();
		this._setAriaLabel(this._xterm, this._instanceId, this._title);

		if (this._titleReadyComplete) {
			this._titleReadyComplete(title);
			this._titleReadyComplete = undefined;
		}

		if (titleChanged) {
			this._onTitleChanged.fire(this);
		}
	}

	private _updateTitleProperties(title: string | undefined, eventSource: TitleEventSource): string {
1791
		if (!title) {
1792
			return this._processName;
1793
		}
1794 1795
		switch (eventSource) {
			case TitleEventSource.Process:
1796 1797 1798
				if (this._processManager.os === OperatingSystem.Windows) {
					// Extract the file name without extension
					title = path.win32.parse(title).name;
1799 1800 1801 1802 1803 1804 1805
				} else {
					const firstSpaceIndex = title.indexOf(' ');
					if (title.startsWith('/')) {
						title = path.basename(title);
					} else if (firstSpaceIndex > -1) {
						title = title.substring(0, firstSpaceIndex);
					}
1806
				}
1807
				this._processName = title;
1808 1809 1810 1811
				break;
			case TitleEventSource.Api:
				// If the title has not been set by the API or the rename command, unregister the handler that
				// automatically updates the terminal name
1812
				this._staticTitle = title;
1813 1814 1815
				dispose(this._messageTitleDisposable);
				this._messageTitleDisposable = undefined;
				break;
1816 1817 1818 1819
			case TitleEventSource.Sequence:
				// On Windows, some shells will fire this with the full path which we want to trim
				// to show just the file name. This should only happen if the title looks like an
				// absolute Windows file path
M
Megan Rogge 已提交
1820 1821 1822 1823 1824 1825 1826 1827
				this._sequence = title;
				if (this._processManager.os === OperatingSystem.Windows) {
					if (title.match(/^[a-zA-Z]:\\.+\.[a-zA-Z]{1,3}/)) {
						title = path.win32.parse(title).name;
						this._sequence = title;
					} else {
						this._sequence = undefined;
					}
1828 1829
				}
				break;
A
Amy Qiu 已提交
1830
		}
M
Megan Rogge 已提交
1831
		this._titleSource = eventSource;
M
meganrogge 已提交
1832 1833 1834
		return title;
	}

1835
	waitForTitle(): Promise<string> {
1836 1837 1838
		return this._titleReadyPromise;
	}

1839
	setDimensions(dimensions: ITerminalDimensionsOverride | undefined, immediate: boolean = false): void {
A
Alex Dima 已提交
1840 1841 1842 1843 1844
		if (this._dimensionsOverride && this._dimensionsOverride.forceExactSize && !dimensions && this._rows === 0 && this._cols === 0) {
			// this terminal never had a real size => keep the last dimensions override exact size
			this._cols = this._dimensionsOverride.cols;
			this._rows = this._dimensionsOverride.rows;
		}
D
Daniel Imms 已提交
1845
		this._dimensionsOverride = dimensions;
A
Alex Dima 已提交
1846 1847 1848 1849 1850
		if (immediate) {
			this._resizeNow(true);
		} else {
			this._resize();
		}
D
Daniel Imms 已提交
1851 1852
	}

1853
	private _setResolvedShellLaunchConfig(shellLaunchConfig: IShellLaunchConfig): void {
1854 1855 1856 1857 1858 1859
		this._shellLaunchConfig.args = shellLaunchConfig.args;
		this._shellLaunchConfig.cwd = shellLaunchConfig.cwd;
		this._shellLaunchConfig.executable = shellLaunchConfig.executable;
		this._shellLaunchConfig.env = shellLaunchConfig.env;
	}

1860
	showEnvironmentInfoHover(): void {
1861 1862 1863 1864 1865
		if (this._environmentInfo) {
			this._environmentInfo.widget.focus();
		}
	}

D
Daniel Imms 已提交
1866
	private _onEnvironmentVariableInfoChanged(info: IEnvironmentVariableInfo): void {
1867
		if (info.requiresAction) {
D
Daniel Imms 已提交
1868
			this._xterm?.textarea?.setAttribute('aria-label', nls.localize('terminalStaleTextBoxAriaLabel', "Terminal {0} environment is stale, run the 'Show Environment Information' command for more information", this._instanceId));
1869
		}
1870 1871 1872 1873 1874
		this._refreshEnvironmentVariableInfoWidgetState(info);
	}

	private _refreshEnvironmentVariableInfoWidgetState(info?: IEnvironmentVariableInfo): void {
		// Check if the widget should not exist
1875 1876
		if (
			!info ||
1877
			this._configHelper.config.environmentChangesIndicator === 'off' ||
1878 1879 1880
			this._configHelper.config.environmentChangesIndicator === 'warnonly' && !info.requiresAction
		) {
			this.statusList.remove(TerminalStatus.RelaunchNeeded);
1881
			this._environmentInfo?.disposable.dispose();
1882
			this._environmentInfo = undefined;
1883 1884 1885
			return;
		}

1886 1887
		// Recreate the process if the terminal has not yet been interacted with and it's not a
		// special terminal (eg. task, extension terminal)
D
Daniel Imms 已提交
1888 1889 1890 1891 1892
		if (
			info.requiresAction &&
			this._configHelper.config.environmentChangesRelaunch &&
			!this._processManager.hasWrittenData &&
			!this._shellLaunchConfig.isFeatureTerminal &&
1893
			!this._shellLaunchConfig.customPtyImplementation
D
Daniel Imms 已提交
1894 1895 1896
			&& !this._shellLaunchConfig.isExtensionOwnedTerminal &&
			!this._shellLaunchConfig.attachPersistentProcess
		) {
1897
			this.relaunch();
1898 1899 1900
			return;
		}

1901
		// (Re-)create the widget
1902
		this._environmentInfo?.disposable.dispose();
1903
		const widget = this._instantiationService.createInstance(EnvironmentVariableInfoWidget, info);
1904
		const disposable = this._widgetManager.attachWidget(widget);
1905
		if (info.requiresAction) {
1906 1907 1908 1909
			this.statusList.add({
				id: TerminalStatus.RelaunchNeeded,
				severity: Severity.Warning,
				icon: Codicon.warning,
D
Daniel Imms 已提交
1910 1911
				tooltip: info.getInfo(),
				hoverActions: info.getActions ? info.getActions() : undefined
1912
			});
1913
		}
1914 1915 1916
		if (disposable) {
			this._environmentInfo = { widget, disposable };
		}
D
Daniel Imms 已提交
1917 1918
	}

D
Daniel Imms 已提交
1919
	private _getXtermTheme(theme?: IColorTheme): ITheme {
D
Daniel Imms 已提交
1920
		if (!theme) {
M
Martin Aeschlimann 已提交
1921
			theme = this._themeService.getColorTheme();
D
Daniel Imms 已提交
1922 1923
		}

1924
		const location = this._viewDescriptorService.getViewLocationById(TERMINAL_VIEW_ID)!;
D
Daniel Imms 已提交
1925
		const foregroundColor = theme.getColor(TERMINAL_FOREGROUND_COLOR);
1926
		let backgroundColor: Color | undefined;
1927
		if (this.target === TerminalLocation.Editor) {
M
meganrogge 已提交
1928
			backgroundColor = theme.getColor(TERMINAL_BACKGROUND_COLOR) || theme.getColor(editorBackground);
1929 1930 1931
		} else {
			backgroundColor = theme.getColor(TERMINAL_BACKGROUND_COLOR) || (location === ViewContainerLocation.Sidebar ? theme.getColor(SIDE_BAR_BACKGROUND) : theme.getColor(PANEL_BACKGROUND));
		}
1932 1933 1934
		const cursorColor = theme.getColor(TERMINAL_CURSOR_FOREGROUND_COLOR) || foregroundColor;
		const cursorAccentColor = theme.getColor(TERMINAL_CURSOR_BACKGROUND_COLOR) || backgroundColor;
		const selectionColor = theme.getColor(TERMINAL_SELECTION_BACKGROUND_COLOR);
D
Daniel Imms 已提交
1935 1936

		return {
D
Daniel Imms 已提交
1937 1938 1939 1940 1941
			background: backgroundColor ? backgroundColor.toString() : undefined,
			foreground: foregroundColor ? foregroundColor.toString() : undefined,
			cursor: cursorColor ? cursorColor.toString() : undefined,
			cursorAccent: cursorAccentColor ? cursorAccentColor.toString() : undefined,
			selection: selectionColor ? selectionColor.toString() : undefined,
D
Daniel Imms 已提交
1942 1943 1944 1945 1946 1947 1948 1949 1950 1951 1952 1953 1954 1955 1956 1957
			black: theme.getColor(ansiColorIdentifiers[0])!.toString(),
			red: theme.getColor(ansiColorIdentifiers[1])!.toString(),
			green: theme.getColor(ansiColorIdentifiers[2])!.toString(),
			yellow: theme.getColor(ansiColorIdentifiers[3])!.toString(),
			blue: theme.getColor(ansiColorIdentifiers[4])!.toString(),
			magenta: theme.getColor(ansiColorIdentifiers[5])!.toString(),
			cyan: theme.getColor(ansiColorIdentifiers[6])!.toString(),
			white: theme.getColor(ansiColorIdentifiers[7])!.toString(),
			brightBlack: theme.getColor(ansiColorIdentifiers[8])!.toString(),
			brightRed: theme.getColor(ansiColorIdentifiers[9])!.toString(),
			brightGreen: theme.getColor(ansiColorIdentifiers[10])!.toString(),
			brightYellow: theme.getColor(ansiColorIdentifiers[11])!.toString(),
			brightBlue: theme.getColor(ansiColorIdentifiers[12])!.toString(),
			brightMagenta: theme.getColor(ansiColorIdentifiers[13])!.toString(),
			brightCyan: theme.getColor(ansiColorIdentifiers[14])!.toString(),
			brightWhite: theme.getColor(ansiColorIdentifiers[15])!.toString()
D
Daniel Imms 已提交
1958 1959 1960
		};
	}

M
Martin Aeschlimann 已提交
1961
	private _updateTheme(xterm: XTermTerminal, theme?: IColorTheme): void {
1962
		xterm.setOption('theme', this._getXtermTheme(theme));
D
Daniel Imms 已提交
1963
	}
1964

1965
	async toggleEscapeSequenceLogging(): Promise<void> {
1966 1967 1968
		const xterm = await this._xtermReadyPromise;
		const isDebug = xterm.getOption('logLevel') === 'debug';
		xterm.setOption('logLevel', isDebug ? 'info' : 'debug');
1969
	}
1970

M
Megan Rogge 已提交
1971 1972 1973 1974 1975
	async getInitialCwd(): Promise<string> {
		if (!this._initialCwd) {
			this._initialCwd = await this._processManager.getInitialCwd();
		}
		return this._initialCwd;
1976 1977
	}

M
meganrogge 已提交
1978 1979 1980 1981
	async getCwd(): Promise<string> {
		return await this._processManager.getInitialCwd();
	}

M
meganrogge 已提交
1982
	async refreshProperty<T extends ProcessPropertyType>(type: ProcessPropertyType): Promise<IProcessPropertyMap[T]> {
M
meganrogge 已提交
1983
		return this._processManager.refreshProperty(type);
1984
	}
1985

1986
	registerLinkProvider(provider: ITerminalExternalLinkProvider): IDisposable {
D
Daniel Imms 已提交
1987
		if (!this._linkManager) {
1988
			throw new Error('TerminalInstance.registerLinkProvider before link manager was ready');
1989
		}
D
Daniel Imms 已提交
1990
		return this._linkManager.registerExternalLinkProvider(this, provider);
1991
	}
1992

1993 1994 1995 1996 1997 1998 1999 2000
	async rename(title?: string) {
		if (!title) {
			title = await this._quickInputService.input({
				value: this.title,
				prompt: nls.localize('workbench.action.terminal.rename.prompt', "Enter terminal name"),
			});
		}
		if (title) {
2001
			this.refreshTabLabels(title, TitleEventSource.Api);
2002 2003
		}
	}
2004

2005
	async changeIcon() {
2006 2007 2008 2009 2010 2011 2012
		const items: IQuickPickItem[] = [];
		for (const icon of iconRegistry.all) {
			items.push({ label: `$(${icon.id})`, description: `${icon.id}` });
		}
		const result = await this._quickInputService.pick(items, {
			matchOnDescription: true
		});
M
Megan Rogge 已提交
2013 2014
		if (result && result.description) {
			this.shellLaunchConfig.icon = iconRegistry.get(result.description);
2015
			this._onIconChanged.fire(this);
2016 2017 2018
		}
	}

2019 2020 2021 2022 2023
	async changeColor() {
		const icon = this._getIcon();
		if (!icon) {
			return;
		}
D
Daniel Imms 已提交
2024 2025 2026 2027 2028 2029 2030 2031 2032 2033 2034 2035 2036 2037 2038

		const standardColors: string[] = [];
		const colorTheme = this._themeService.getColorTheme();
		for (const colorKey in ansiColorMap) {
			const color = colorTheme.getColor(colorKey);
			if (color && !colorKey.toLowerCase().includes('bright')) {
				standardColors.push(colorKey);
			}
		}

		const styleElement = document.createElement('style');
		let css = '';
		const items: (IQuickPickItem | IQuickPickSeparator)[] = [];
		for (const colorKey of standardColors) {
			const colorClass = getColorClass(colorKey);
2039
			items.push({
D
Daniel Imms 已提交
2040
				label: `$(${Codicon.circleFilled.id}) ${colorKey.replace('terminal.ansi', '')}`, id: colorKey, description: colorKey, iconClasses: [colorClass]
2041
			});
D
Daniel Imms 已提交
2042 2043
			const color = colorTheme.getColor(colorKey);
			if (color) {
D
Daniel Imms 已提交
2044
				css += (
D
Daniel Imms 已提交
2045
					`.monaco-workbench .${colorClass} .codicon:first-child:not(.codicon-split-horizontal):not(.codicon-trashcan):not(.file-icon)` +
D
Daniel Imms 已提交
2046 2047
					`{ color: ${color} !important; }`
				);
D
Daniel Imms 已提交
2048
			}
2049
		}
D
Daniel Imms 已提交
2050
		items.push({ type: 'separator' });
2051
		const showAllColorsItem = { label: 'Reset to default' };
D
Daniel Imms 已提交
2052 2053 2054 2055 2056 2057 2058 2059
		items.push(showAllColorsItem);
		styleElement.textContent = css;
		document.body.appendChild(styleElement);

		const quickPick = this._quickInputService.createQuickPick();
		quickPick.items = items;
		quickPick.matchOnDescription = true;
		quickPick.show();
2060
		const disposables: IDisposable[] = [];
D
Daniel Imms 已提交
2061 2062 2063
		const result = await new Promise<IQuickPickItem | undefined>(r => {
			disposables.push(quickPick.onDidHide(() => r(undefined)));
			disposables.push(quickPick.onDidAccept(() => r(quickPick.selectedItems[0])));
2064
		});
D
Daniel Imms 已提交
2065 2066
		dispose(disposables);

2067
		if (result) {
2068 2069
			this.shellLaunchConfig.color = result.id;
			this._onIconChanged.fire(this);
2070
		}
D
Daniel Imms 已提交
2071

2072
		quickPick.hide();
D
Daniel Imms 已提交
2073
		document.body.removeChild(styleElement);
2074
	}
2075
}
2076

2077
class TerminalInstanceDragAndDropController extends Disposable implements IDragAndDropObserverCallbacks {
D
Daniel Imms 已提交
2078 2079 2080 2081 2082 2083 2084 2085
	private _dropOverlay?: HTMLElement;

	private readonly _onDropFile = new Emitter<string>();
	get onDropFile(): Event<string> { return this._onDropFile.event; }
	private readonly _onDropTerminal = new Emitter<IRequestAddInstanceToGroupEvent>();
	get onDropTerminal(): Event<IRequestAddInstanceToGroupEvent> { return this._onDropTerminal.event; }

	constructor(
J
jeanp413 已提交
2086 2087 2088
		private readonly _container: HTMLElement,
		@IWorkbenchLayoutService private readonly _layoutService: IWorkbenchLayoutService,
		@IViewDescriptorService private readonly _viewDescriptorService: IViewDescriptorService,
D
Daniel Imms 已提交
2089
	) {
D
Daniel Imms 已提交
2090 2091 2092 2093 2094 2095 2096 2097 2098
		super();
		this._register(toDisposable(() => this._clearDropOverlay()));
	}

	private _clearDropOverlay() {
		if (this._dropOverlay && this._dropOverlay.parentElement) {
			this._dropOverlay.parentElement.removeChild(this._dropOverlay);
		}
		this._dropOverlay = undefined;
D
Daniel Imms 已提交
2099 2100 2101
	}

	onDragEnter(e: DragEvent) {
2102
		if (!containsDragType(e, DataTransfers.FILES, DataTransfers.RESOURCES, DataTransfers.TERMINALS, CodeDataTransfers.FILES)) {
J
jeanp413 已提交
2103 2104 2105
			return;
		}

D
Daniel Imms 已提交
2106 2107 2108 2109 2110 2111
		if (!this._dropOverlay) {
			this._dropOverlay = document.createElement('div');
			this._dropOverlay.classList.add('terminal-drop-overlay');
		}

		// Dragging terminals
2112
		if (containsDragType(e, DataTransfers.TERMINALS)) {
D
Daniel Imms 已提交
2113
			const side = this._getDropSide(e);
J
jeanp413 已提交
2114 2115
			this._dropOverlay.classList.toggle('drop-before', side === 'before');
			this._dropOverlay.classList.toggle('drop-after', side === 'after');
D
Daniel Imms 已提交
2116 2117 2118 2119 2120 2121 2122
		}

		if (!this._dropOverlay.parentElement) {
			this._container.appendChild(this._dropOverlay);
		}
	}
	onDragLeave(e: DragEvent) {
D
Daniel Imms 已提交
2123
		this._clearDropOverlay();
D
Daniel Imms 已提交
2124 2125 2126
	}

	onDragEnd(e: DragEvent) {
D
Daniel Imms 已提交
2127
		this._clearDropOverlay();
D
Daniel Imms 已提交
2128 2129 2130 2131 2132 2133 2134 2135
	}

	onDragOver(e: DragEvent) {
		if (!e.dataTransfer || !this._dropOverlay) {
			return;
		}

		// Dragging terminals
2136
		if (containsDragType(e, DataTransfers.TERMINALS)) {
D
Daniel Imms 已提交
2137
			const side = this._getDropSide(e);
J
jeanp413 已提交
2138 2139
			this._dropOverlay.classList.toggle('drop-before', side === 'before');
			this._dropOverlay.classList.toggle('drop-after', side === 'after');
D
Daniel Imms 已提交
2140 2141 2142 2143 2144 2145
		}

		this._dropOverlay.style.opacity = '1';
	}

	async onDrop(e: DragEvent) {
D
Daniel Imms 已提交
2146
		this._clearDropOverlay();
D
Daniel Imms 已提交
2147 2148 2149 2150 2151

		if (!e.dataTransfer) {
			return;
		}

D
Daniel Imms 已提交
2152
		const terminalResources = getTerminalResourcesFromDragEvent(e);
2153
		if (terminalResources) {
D
Daniel Imms 已提交
2154
			for (const uri of terminalResources) {
2155 2156 2157
				const side = this._getDropSide(e);
				this._onDropTerminal.fire({ uri, side });
			}
D
Daniel Imms 已提交
2158
			return;
2159 2160
		}

D
Daniel Imms 已提交
2161 2162
		// Check if files were dragged from the tree explorer
		let path: string | undefined;
2163 2164 2165 2166 2167 2168 2169 2170 2171 2172 2173
		const rawResources = e.dataTransfer.getData(DataTransfers.RESOURCES);
		if (rawResources) {
			path = URI.parse(JSON.parse(rawResources)[0]).fsPath;
		}

		const rawCodeFiles = e.dataTransfer.getData(CodeDataTransfers.FILES);
		if (!path && rawCodeFiles) {
			path = URI.file(JSON.parse(rawCodeFiles)[0]).fsPath;
		}

		if (!path && e.dataTransfer.files.length > 0 && e.dataTransfer.files[0].path /* Electron only */) {
D
Daniel Imms 已提交
2174 2175 2176 2177 2178 2179 2180 2181 2182 2183 2184
			// Check if the file was dragged from the filesystem
			path = URI.file(e.dataTransfer.files[0].path).fsPath;
		}

		if (!path) {
			return;
		}

		this._onDropFile.fire(path);
	}

J
jeanp413 已提交
2185
	private _getDropSide(e: DragEvent): 'before' | 'after' {
D
Daniel Imms 已提交
2186 2187
		const target = this._container;
		if (!target) {
J
jeanp413 已提交
2188
			return 'after';
D
Daniel Imms 已提交
2189
		}
J
jeanp413 已提交
2190

D
Daniel Imms 已提交
2191
		const rect = target.getBoundingClientRect();
J
jeanp413 已提交
2192 2193 2194 2195 2196 2197 2198 2199 2200 2201 2202
		return this._getViewOrientation() === Orientation.HORIZONTAL
			? (e.clientX - rect.left < rect.width / 2 ? 'before' : 'after')
			: (e.clientY - rect.top < rect.height / 2 ? 'before' : 'after');
	}

	private _getViewOrientation(): Orientation {
		const panelPosition = this._layoutService.getPanelPosition();
		const terminalLocation = this._viewDescriptorService.getViewLocationById(TERMINAL_VIEW_ID);
		return terminalLocation === ViewContainerLocation.Panel && panelPosition === Position.BOTTOM
			? Orientation.HORIZONTAL
			: Orientation.VERTICAL;
D
Daniel Imms 已提交
2203 2204 2205
	}
}

M
Martin Aeschlimann 已提交
2206
registerThemingParticipant((theme: IColorTheme, collector: ICssStyleCollector) => {
2207 2208 2209 2210
	// Border
	const border = theme.getColor(activeContrastBorder);
	if (border) {
		collector.addRule(`
2211
			.monaco-workbench.hc-black .editor-instance .xterm.focus::before,
2212
			.monaco-workbench.hc-black .pane-body.integrated-terminal .xterm.focus::before,
2213
			.monaco-workbench.hc-black .editor-instance .xterm:focus::before,
2214
			.monaco-workbench.hc-black .pane-body.integrated-terminal .xterm:focus::before { border-color: ${border}; }`
2215 2216
		);
	}
2217 2218 2219 2220 2221

	// Scrollbar
	const scrollbarSliderBackgroundColor = theme.getColor(scrollbarSliderBackground);
	if (scrollbarSliderBackgroundColor) {
		collector.addRule(`
2222
			.monaco-workbench .editor-instance .find-focused .xterm .xterm-viewport,
2223
			.monaco-workbench .pane-body.integrated-terminal .find-focused .xterm .xterm-viewport,
2224
			.monaco-workbench .editor-instance .xterm.focus .xterm-viewport,
2225
			.monaco-workbench .pane-body.integrated-terminal .xterm.focus .xterm-viewport,
2226
			.monaco-workbench .editor-instance .xterm:focus .xterm-viewport,
2227
			.monaco-workbench .pane-body.integrated-terminal .xterm:focus .xterm-viewport,
2228
			.monaco-workbench .editor-instance .xterm:hover .xterm-viewport,
D
Daniel Imms 已提交
2229
			.monaco-workbench .pane-body.integrated-terminal .xterm:hover .xterm-viewport { background-color: ${scrollbarSliderBackgroundColor} !important; }
2230
			.monaco-workbench .editor-instance .xterm-viewport,
D
Daniel Imms 已提交
2231 2232
			.monaco-workbench .pane-body.integrated-terminal .xterm-viewport { scrollbar-color: ${scrollbarSliderBackgroundColor} transparent; }
		`);
2233 2234 2235 2236
	}

	const scrollbarSliderHoverBackgroundColor = theme.getColor(scrollbarSliderHoverBackground);
	if (scrollbarSliderHoverBackgroundColor) {
D
Daniel Imms 已提交
2237
		collector.addRule(`
2238
			.monaco-workbench .editor-instance .xterm .xterm-viewport::-webkit-scrollbar-thumb:hover,
D
Daniel Imms 已提交
2239
			.monaco-workbench .pane-body.integrated-terminal .xterm .xterm-viewport::-webkit-scrollbar-thumb:hover { background-color: ${scrollbarSliderHoverBackgroundColor}; }
2240
			.monaco-workbench .editor-instance .xterm-viewport:hover,
D
Daniel Imms 已提交
2241 2242
			.monaco-workbench .pane-body.integrated-terminal .xterm-viewport:hover { scrollbar-color: ${scrollbarSliderHoverBackgroundColor} transparent; }
		`);
2243
	}
2244 2245 2246

	const scrollbarSliderActiveBackgroundColor = theme.getColor(scrollbarSliderActiveBackground);
	if (scrollbarSliderActiveBackgroundColor) {
2247 2248 2249 2250
		collector.addRule(`
			.monaco-workbench .editor-instance .xterm .xterm-viewport::-webkit-scrollbar-thumb:active,
			.monaco-workbench .pane-body.integrated-terminal .xterm .xterm-viewport::-webkit-scrollbar-thumb:active { background-color: ${scrollbarSliderActiveBackgroundColor}; }
		`);
2251
	}
2252
});
2253 2254 2255 2256 2257 2258 2259 2260 2261 2262 2263 2264 2265 2266 2267 2268 2269 2270 2271 2272 2273 2274 2275 2276 2277 2278 2279

export interface ITerminalLabelTemplateProperties {
	cwd?: string | null | undefined;
	cwdFolder?: string | null | undefined;
	workspaceFolder?: string | null | undefined;
	local?: string | null | undefined;
	process?: string | null | undefined;
	sequence?: string | null | undefined;
	task?: string | null | undefined;
	separator?: string | ISeparator | null | undefined;
}

const enum TerminalLabelType {
	Title = 'title',
	Description = 'description'
}

export class TerminalLabelComputer extends Disposable {
	private _title: string = '';
	private _description: string = '';
	get title(): string | undefined { return this._title; }
	get description(): string | undefined { return this._description; }

	private readonly _onDidChangeLabel = this._register(new Emitter<{ title: string, description: string }>());
	readonly onDidChangeLabel = this._onDidChangeLabel.event;
	constructor(
		private readonly _configHelper: TerminalConfigHelper,
M
Megan Rogge 已提交
2280
		private readonly _instance: Pick<ITerminalInstance, 'shellLaunchConfig' | 'cwd' | 'initialCwd' | 'processName' | 'sequence' | 'userHome' | 'workspaceFolder' | 'staticTitle' | 'capabilities' | 'title' | 'description'>,
2281 2282 2283 2284 2285 2286 2287 2288 2289 2290 2291 2292 2293 2294 2295 2296 2297 2298 2299 2300 2301 2302 2303 2304 2305 2306 2307 2308 2309 2310 2311 2312
		@IWorkspaceContextService private readonly _workspaceContextService: IWorkspaceContextService
	) {
		super();
	}
	refreshLabel(): void {
		this._title = this.computeLabel(this._configHelper.config.tabs.title, TerminalLabelType.Title);
		this._description = this.computeLabel(this._configHelper.config.tabs.description, TerminalLabelType.Description);
		if (this._title !== this._instance.title || this._description !== this._instance.description) {
			this._onDidChangeLabel.fire({ title: this._title, description: this._description });
		}
	}

	computeLabel(
		labelTemplate: string,
		labelType: TerminalLabelType
	) {
		const templateProperties: ITerminalLabelTemplateProperties = {
			cwd: this._instance.cwd || this._instance.initialCwd || '',
			cwdFolder: '',
			workspaceFolder: this._instance.workspaceFolder,
			local: this._instance.shellLaunchConfig.description === 'Local' ? 'Local' : undefined,
			process: this._instance.processName,
			sequence: this._instance.sequence,
			task: this._instance.shellLaunchConfig.description === 'Task' ? 'Task' : undefined,
			separator: { label: this._configHelper.config.tabs.separator }
		};
		if (!labelTemplate) {
			return '';
		}
		if (this._instance.staticTitle && labelType === TerminalLabelType.Title) {
			return this._instance.staticTitle.replace(/[\n\r\t]/g, '') || templateProperties.process?.replace(/[\n\r\t]/g, '') || '';
		}
M
Megan Rogge 已提交
2313
		const detection = this._instance.capabilities.includes(ProcessCapability.CwdDetection);
M
Megan Rogge 已提交
2314 2315
		const zeroRootWorkspace = this._workspaceContextService.getWorkspace().folders.length === 0 && this.pathsEqual(templateProperties.cwd, this._instance.userHome || this._configHelper.config.cwd);
		const singleRootWorkspace = this._workspaceContextService.getWorkspace().folders.length === 1 && this.pathsEqual(templateProperties.cwd, this._configHelper.config.cwd || this._workspaceContextService.getWorkspace().folders[0]?.uri.fsPath);
M
Megan Rogge 已提交
2316
		templateProperties.cwdFolder = (!templateProperties.cwd || !detection || zeroRootWorkspace || singleRootWorkspace) ? '' : path.basename(templateProperties.cwd);
2317 2318 2319 2320 2321

		//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, '');
		return label === '' && labelType === TerminalLabelType.Title ? (this._instance.processName || '') : label;
	}
M
Megan Rogge 已提交
2322 2323 2324 2325 2326 2327 2328 2329 2330 2331 2332 2333 2334 2335

	pathsEqual(path1?: string | null, path2?: string) {
		if (!path1 && !path2) {
			return true;
		} else if (!path1 || !path2) {
			return false;
		} else if (path1 === path2) {
			return true;
		}
		const split1 = path1.includes('/') ? path1.split('/') : path1.split('\\');
		const split2 = path2.includes('/') ? path2.split('/') : path2.split('\\');
		if (split1.length !== split2.length) {
			return false;
		}
M
Megan Rogge 已提交
2336 2337
		for (let i = 0; i < split1.length; i++) {
			if (split1[i] !== split2[i]) {
M
Megan Rogge 已提交
2338 2339 2340 2341 2342
				return false;
			}
		}
		return true;
	}
2343
}