terminalInstance.ts 65.3 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';
12
import { IDisposable, dispose, Disposable } from 'vs/base/common/lifecycle';
D
Daniel Imms 已提交
13
import * as platform from 'vs/base/common/platform';
14
import { TabFocus } from 'vs/editor/common/config/commonEditorConfig';
D
Daniel Imms 已提交
15
import * as nls from 'vs/nls';
D
Daniel Imms 已提交
16
import { IClipboardService } from 'vs/platform/clipboard/common/clipboardService';
D
Daniel Imms 已提交
17 18 19 20
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 已提交
21
import { ILogService } from 'vs/platform/log/common/log';
D
Daniel Imms 已提交
22
import { INotificationService, IPromptChoice, Severity } from 'vs/platform/notification/common/notification';
23
import { IStorageService, StorageScope, StorageTarget } from 'vs/platform/storage/common/storage';
D
Daniel Imms 已提交
24
import { activeContrastBorder, scrollbarSliderActiveBackground, scrollbarSliderBackground, scrollbarSliderHoverBackground } from 'vs/platform/theme/common/colorRegistry';
M
Martin Aeschlimann 已提交
25
import { ICssStyleCollector, IColorTheme, IThemeService, registerThemingParticipant } from 'vs/platform/theme/common/themeService';
26
import { PANEL_BACKGROUND, SIDE_BAR_BACKGROUND } from 'vs/workbench/common/theme';
D
Daniel Imms 已提交
27
import { TerminalWidgetManager } from 'vs/workbench/contrib/terminal/browser/widgets/widgetManager';
M
Megan Rogge 已提交
28
import { IShellLaunchConfig, ITerminalProcessManager, KEYBINDING_CONTEXT_TERMINAL_TEXT_SELECTED, NEVER_MEASURE_RENDER_TIME_STORAGE_KEY, ProcessState, TERMINAL_VIEW_ID, IWindowsShellHelper, KEYBINDING_CONTEXT_TERMINAL_A11Y_TREE_FOCUS, INavigationMode, TitleEventSource, DEFAULT_COMMANDS_TO_SKIP_SHELL, ITerminalLaunchError, IProcessDataEvent, ITerminalDimensionsOverride, TERMINAL_CREATION_COMMANDS } from 'vs/workbench/contrib/terminal/common/terminal';
29
import { ansiColorIdentifiers, 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';
30
import { TerminalConfigHelper } from 'vs/workbench/contrib/terminal/browser/terminalConfigHelper';
31
import { TerminalLinkManager } from 'vs/workbench/contrib/terminal/browser/links/terminalLinkManager';
I
isidor 已提交
32
import { IAccessibilityService } from 'vs/platform/accessibility/common/accessibility';
D
Daniel Imms 已提交
33
import { ITerminalInstanceService, ITerminalInstance, TerminalShellType, WindowsShellType, ITerminalExternalLinkProvider } from 'vs/workbench/contrib/terminal/browser/terminal';
34
import { TerminalProcessManager } from 'vs/workbench/contrib/terminal/browser/terminalProcessManager';
35 36 37
import type { Terminal as XTermTerminal, IBuffer, ITerminalAddon } from 'xterm';
import type { SearchAddon, ISearchOptions } from 'xterm-addon-search';
import type { Unicode11Addon } from 'xterm-addon-unicode11';
38
import type { WebglAddon } from 'xterm-addon-webgl';
39
import { CommandTrackerAddon } from 'vs/workbench/contrib/terminal/browser/addons/commandTrackerAddon';
40
import { NavigationModeAddon } from 'vs/workbench/contrib/terminal/browser/addons/navigationModeAddon';
41
import { XTermCore } from 'vs/workbench/contrib/terminal/browser/xterm-private';
D
Daniel Imms 已提交
42
import { IEditorOptions } from 'vs/editor/common/config/editorOptions';
43
import { IViewsService, IViewDescriptorService, ViewContainerLocation } from 'vs/workbench/common/views';
D
Daniel Imms 已提交
44 45
import { EnvironmentVariableInfoWidget } from 'vs/workbench/contrib/terminal/browser/widgets/environmentVariableInfoWidget';
import { IEnvironmentVariableInfo } from 'vs/workbench/contrib/terminal/common/environmentVariable';
46
import { TerminalLaunchHelpAction } from 'vs/workbench/contrib/terminal/browser/terminalActions';
C
wip  
Connor Peet 已提交
47
import { TypeAheadAddon } from 'vs/workbench/contrib/terminal/browser/terminalTypeAheadAddon';
48
import { BrowserFeatures } from 'vs/base/browser/canIUse';
49
import { IPreferencesService } from 'vs/workbench/services/preferences/common/preferences';
D
Daniel Imms 已提交
50

51 52
// How long in milliseconds should an average frame take to render for a notification to appear
// which suggests the fallback DOM-based renderer
53 54
const SLOW_CANVAS_RENDER_THRESHOLD = 50;
const NUMBER_OF_FRAMES_TO_MEASURE = 20;
55

56 57
let xtermConstructor: Promise<typeof XTermTerminal> | undefined;

58 59 60 61 62 63 64 65 66 67
interface ICanvasDimensions {
	width: number;
	height: number;
}

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

68
export class TerminalInstance extends Disposable implements ITerminalInstance {
69
	private static readonly EOL_REGEX = /\r?\n/g;
C
Christof Marti 已提交
70

71 72
	private static _lastKnownCanvasDimensions: ICanvasDimensions | undefined;
	private static _lastKnownGridDimensions: IGridDimensions | undefined;
D
Daniel Imms 已提交
73 74
	private static _idCounter = 1;

75
	private _processManager!: ITerminalProcessManager;
76
	private _pressAnyKeyToCloseListener: IDisposable | undefined;
D
Daniel Imms 已提交
77

78
	private _id: number;
79 80
	private _latestXtermWriteData: number = 0;
	private _latestXtermParseData: number = 0;
D
Daniel Imms 已提交
81
	private _isExiting: boolean;
K
Kai Wood 已提交
82
	private _hadFocusOnExit: boolean;
D
Daniel Imms 已提交
83
	private _isVisible: boolean;
84
	private _isDisposed: boolean;
D
Daniel Imms 已提交
85
	private _exitCode: number | undefined;
D
Daniel Imms 已提交
86
	private _skipTerminalCommands: string[];
87
	private _shellType: TerminalShellType;
88
	private _title: string = '';
D
Daniel Imms 已提交
89
	private _wrapperElement: (HTMLElement & { xterm?: XTermTerminal }) | undefined;
90
	private _xterm: XTermTerminal | undefined;
91
	private _xtermCore: XTermCore | undefined;
92
	private _xtermTypeAhead: TypeAheadAddon | undefined;
D
Daniel Imms 已提交
93
	private _xtermSearch: SearchAddon | undefined;
D
Daniel Imms 已提交
94
	private _xtermUnicode11: Unicode11Addon | undefined;
95
	private _xtermElement: HTMLDivElement | undefined;
96
	private _terminalHasTextContextKey: IContextKey<boolean>;
97
	private _terminalA11yTreeFocusContextKey: IContextKey<boolean>;
98 99
	private _cols: number = 0;
	private _rows: number = 0;
A
Alex Dima 已提交
100
	private _dimensionsOverride: ITerminalDimensionsOverride | undefined;
101
	private _windowsShellHelper: IWindowsShellHelper | undefined;
102
	private _xtermReadyPromise: Promise<XTermTerminal>;
103
	private _titleReadyPromise: Promise<string>;
104
	private _titleReadyComplete: ((title: string) => any) | undefined;
105
	private _areLinksReady: boolean = false;
106
	private _initialDataEvents: string[] | undefined = [];
D
Daniel Imms 已提交
107

108
	private _messageTitleDisposable: IDisposable | undefined;
109

110
	private _widgetManager: TerminalWidgetManager = this._instantiationService.createInstance(TerminalWidgetManager);
111
	private _linkManager: TerminalLinkManager | undefined;
112
	private _environmentInfo: { widget: EnvironmentVariableInfoWidget, disposable: IDisposable } | undefined;
113
	private _webglAddon: WebglAddon | undefined;
114
	private _commandTrackerAddon: CommandTrackerAddon | undefined;
115
	private _navigationModeAddon: INavigationMode & ITerminalAddon | undefined;
116

J
jeanp413 已提交
117 118
	private _timeoutDimension: dom.Dimension | undefined;

M
Megan Rogge 已提交
119 120
	private hasHadInput: boolean;

121
	public disableLayout: boolean;
122
	public get id(): number { return this._id; }
123 124
	public get cols(): number {
		if (this._dimensionsOverride && this._dimensionsOverride.cols) {
A
Alex Dima 已提交
125 126 127
			if (this._dimensionsOverride.forceExactSize) {
				return this._dimensionsOverride.cols;
			}
128 129 130 131 132 133
			return Math.min(Math.max(this._dimensionsOverride.cols, 2), this._cols);
		}
		return this._cols;
	}
	public get rows(): number {
		if (this._dimensionsOverride && this._dimensionsOverride.rows) {
A
Alex Dima 已提交
134 135 136
			if (this._dimensionsOverride.forceExactSize) {
				return this._dimensionsOverride.rows;
			}
137 138 139 140
			return Math.min(Math.max(this._dimensionsOverride.rows, 2), this._rows);
		}
		return this._rows;
	}
141 142
	public get maxCols(): number { return this._cols; }
	public get maxRows(): number { return this._rows; }
D
Daniel Imms 已提交
143
	// TODO: Ideally processId would be merged into processReady
D
Daniel Imms 已提交
144
	public get processId(): number | undefined { return this._processManager.shellProcessId; }
145
	// TODO: How does this work with detached processes?
D
Daniel Imms 已提交
146
	// TODO: Should this be an event as it can fire twice?
D
Daniel Imms 已提交
147
	public get processReady(): Promise<void> { return this._processManager.ptyProcessReady; }
148
	public get areLinksReady(): boolean { return this._areLinksReady; }
149
	public get initialDataEvents(): string[] | undefined { return this._initialDataEvents; }
D
Daniel Imms 已提交
150
	public get exitCode(): number | undefined { return this._exitCode; }
D
Daniel Imms 已提交
151
	public get title(): string { return this._title; }
K
Kai Wood 已提交
152
	public get hadFocusOnExit(): boolean { return this._hadFocusOnExit; }
153
	public get isTitleSetByProcess(): boolean { return !!this._messageTitleDisposable; }
D
Daniel Imms 已提交
154
	public get shellLaunchConfig(): IShellLaunchConfig { return this._shellLaunchConfig; }
155
	public get shellType(): TerminalShellType { return this._shellType; }
156
	public get commandTracker(): CommandTrackerAddon | undefined { return this._commandTrackerAddon; }
157
	public get navigationMode(): INavigationMode | undefined { return this._navigationModeAddon; }
158

159 160
	private readonly _onExit = new Emitter<number | undefined>();
	public get onExit(): Event<number | undefined> { return this._onExit.event; }
161
	private readonly _onDisposed = new Emitter<ITerminalInstance>();
D
Daniel Imms 已提交
162
	public get onDisposed(): Event<ITerminalInstance> { return this._onDisposed.event; }
163
	private readonly _onFocused = new Emitter<ITerminalInstance>();
D
Daniel Imms 已提交
164
	public get onFocused(): Event<ITerminalInstance> { return this._onFocused.event; }
165
	private readonly _onProcessIdReady = new Emitter<ITerminalInstance>();
D
Daniel Imms 已提交
166
	public get onProcessIdReady(): Event<ITerminalInstance> { return this._onProcessIdReady.event; }
167 168
	private readonly _onLinksReady = new Emitter<ITerminalInstance>();
	public get onLinksReady(): Event<ITerminalInstance> { return this._onLinksReady.event; }
169
	private readonly _onTitleChanged = new Emitter<ITerminalInstance>();
170
	public get onTitleChanged(): Event<ITerminalInstance> { return this._onTitleChanged.event; }
171
	private readonly _onData = new Emitter<string>();
D
Daniel Imms 已提交
172
	public get onData(): Event<string> { return this._onData.event; }
173
	private readonly _onLineData = new Emitter<string>();
D
Daniel Imms 已提交
174
	public get onLineData(): Event<string> { return this._onLineData.event; }
175
	private readonly _onRequestExtHostProcess = new Emitter<ITerminalInstance>();
176
	public get onRequestExtHostProcess(): Event<ITerminalInstance> { return this._onRequestExtHostProcess.event; }
177
	private readonly _onDimensionsChanged = new Emitter<void>();
D
Daniel Imms 已提交
178
	public get onDimensionsChanged(): Event<void> { return this._onDimensionsChanged.event; }
179 180
	private readonly _onMaximumDimensionsChanged = new Emitter<void>();
	public get onMaximumDimensionsChanged(): Event<void> { return this._onMaximumDimensionsChanged.event; }
181
	private readonly _onFocus = new Emitter<ITerminalInstance>();
182
	public get onFocus(): Event<ITerminalInstance> { return this._onFocus.event; }
D
Daniel Imms 已提交
183

184
	public constructor(
185
		private readonly _terminalFocusContextKey: IContextKey<boolean>,
186
		private readonly _terminalShellTypeContextKey: IContextKey<string>,
187
		private readonly _configHelper: TerminalConfigHelper,
188
		private _container: HTMLElement | undefined,
189
		private _shellLaunchConfig: IShellLaunchConfig,
190
		@ITerminalInstanceService private readonly _terminalInstanceService: ITerminalInstanceService,
191 192
		@IContextKeyService private readonly _contextKeyService: IContextKeyService,
		@IKeybindingService private readonly _keybindingService: IKeybindingService,
193
		@INotificationService private readonly _notificationService: INotificationService,
194
		@IPreferencesService private readonly _preferencesService: IPreferencesService,
195
		@IViewsService private readonly _viewsService: IViewsService,
196 197 198
		@IInstantiationService private readonly _instantiationService: IInstantiationService,
		@IClipboardService private readonly _clipboardService: IClipboardService,
		@IThemeService private readonly _themeService: IThemeService,
D
Daniel Imms 已提交
199
		@IConfigurationService private readonly _configurationService: IConfigurationService,
200
		@ILogService private readonly _logService: ILogService,
201
		@IStorageService private readonly _storageService: IStorageService,
202
		@IAccessibilityService private readonly _accessibilityService: IAccessibilityService,
203
		@IViewDescriptorService private readonly _viewDescriptorService: IViewDescriptorService
204
	) {
205 206
		super();

D
Daniel Imms 已提交
207
		this._skipTerminalCommands = [];
D
Daniel Imms 已提交
208
		this._isExiting = false;
K
Kai Wood 已提交
209
		this._hadFocusOnExit = false;
D
Daniel Imms 已提交
210
		this._isVisible = false;
211
		this._isDisposed = false;
D
Daniel Imms 已提交
212
		this._id = TerminalInstance._idCounter++;
213

M
Megan Rogge 已提交
214 215
		this.hasHadInput = false;

216 217 218 219
		this._titleReadyPromise = new Promise<string>(c => {
			this._titleReadyComplete = c;
		});

220
		this._terminalHasTextContextKey = KEYBINDING_CONTEXT_TERMINAL_TEXT_SELECTED.bindTo(this._contextKeyService);
221
		this._terminalA11yTreeFocusContextKey = KEYBINDING_CONTEXT_TERMINAL_A11Y_TREE_FOCUS.bindTo(this._contextKeyService);
222
		this.disableLayout = false;
D
Daniel Imms 已提交
223

D
Daniel Imms 已提交
224 225
		this._logService.trace(`terminalInstance#ctor (id: ${this.id})`, this._shellLaunchConfig);

226
		this._initDimensions();
227
		this._createProcessManager();
D
Daniel Imms 已提交
228

D
Daniel Imms 已提交
229 230 231 232
		this._xtermReadyPromise = this._createXterm();
		this._xtermReadyPromise.then(() => {
			// Only attach xterm.js to the DOM if the terminal panel has been opened before.
			if (_container) {
233
				this._attachToElement(_container);
D
Daniel Imms 已提交
234
			}
D
Daniel Imms 已提交
235
			this._createProcess();
D
Daniel Imms 已提交
236
		});
237

238
		this.addDisposable(this._configurationService.onDidChangeConfiguration(e => {
239
			if (e.affectsConfiguration('terminal.integrated') || e.affectsConfiguration('editor.fastScrollSensitivity') || e.affectsConfiguration('editor.mouseWheelScrollSensitivity')) {
240
				this.updateConfig();
241 242 243 244
				// HACK: Trigger another async layout to ensure xterm's CharMeasure is ready to use,
				// this hack can be removed when https://github.com/xtermjs/xterm.js/issues/702 is
				// supported.
				this.setVisible(this._isVisible);
245
			}
D
Daniel Imms 已提交
246 247 248
			if (e.affectsConfiguration('terminal.integrated.unicodeVersion')) {
				this._updateUnicodeVersion();
			}
249 250 251
			if (e.affectsConfiguration('editor.accessibilitySupport')) {
				this.updateAccessibilitySupport();
			}
252
		}));
253 254 255

		// Clear out initial data events after 10 seconds, hopefully extension hosts are up and
		// running at that point.
D
Daniel Imms 已提交
256
		let initialDataEventsTimeout: number | undefined = window.setTimeout(() => {
257 258 259 260 261 262
			initialDataEventsTimeout = undefined;
			this._initialDataEvents = undefined;
		}, 10000);
		this._register({
			dispose: () => {
				if (initialDataEventsTimeout) {
D
Daniel Imms 已提交
263
					window.clearTimeout(initialDataEventsTimeout);
264 265 266
				}
			}
		});
267 268
	}

269 270
	public addDisposable(disposable: IDisposable): void {
		this._register(disposable);
D
Daniel Imms 已提交
271 272
	}

273 274 275 276 277 278
	private _initDimensions(): void {
		// The terminal panel needs to have been created
		if (!this._container) {
			return;
		}

D
Daniel Imms 已提交
279
		const computedStyle = window.getComputedStyle(this._container.parentElement!);
280 281 282 283 284 285 286 287 288
		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 已提交
289
	 * @return The terminal's width if it requires a layout.
290
	 */
D
Daniel Imms 已提交
291
	private _evaluateColsAndRows(width: number, height: number): number | null {
D
Daniel Imms 已提交
292 293
		// Ignore if dimensions are undefined or 0
		if (!width || !height) {
294
			this._setLastKnownColsAndRows();
D
Daniel Imms 已提交
295 296 297
			return null;
		}

298 299
		const dimension = this._getDimension(width, height);
		if (!dimension) {
300
			this._setLastKnownColsAndRows();
301 302
			return null;
		}
D
Daniel Imms 已提交
303

304
		const font = this._configHelper.getFont(this._xtermCore);
D
Daniel Imms 已提交
305
		if (!font.charWidth || !font.charHeight) {
306
			this._setLastKnownColsAndRows();
D
Daniel Imms 已提交
307 308
			return null;
		}
309

D
Daniel Imms 已提交
310 311 312 313 314
		// 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;
315

316
		const scaledCharWidth = font.charWidth * window.devicePixelRatio + font.letterSpacing;
317
		const newCols = Math.max(Math.floor(scaledWidthAvailable / scaledCharWidth), 1);
D
Daniel Imms 已提交
318 319

		const scaledHeightAvailable = dimension.height * window.devicePixelRatio;
320 321
		const scaledCharHeight = Math.ceil(font.charHeight * window.devicePixelRatio);
		const scaledLineHeight = Math.floor(scaledCharHeight * font.lineHeight);
322 323 324 325 326
		const newRows = Math.max(Math.floor(scaledHeightAvailable / scaledLineHeight), 1);

		if (this._cols !== newCols || this._rows !== newRows) {
			this._cols = newCols;
			this._rows = newRows;
327
			this._fireMaximumDimensionsChanged();
328
		}
329

330 331
		return dimension.width;
	}
332

333 334 335 336 337 338 339
	private _setLastKnownColsAndRows(): void {
		if (TerminalInstance._lastKnownGridDimensions) {
			this._cols = TerminalInstance._lastKnownGridDimensions.cols;
			this._rows = TerminalInstance._lastKnownGridDimensions.rows;
		}
	}

340 341 342 343
	@debounce(50)
	private _fireMaximumDimensionsChanged(): void {
		this._onMaximumDimensionsChanged.fire();
	}
344

345
	private _getDimension(width: number, height: number): ICanvasDimensions | undefined {
346
		// The font needs to have been initialized
347
		const font = this._configHelper.getFont(this._xtermCore);
348
		if (!font || !font.charWidth || !font.charHeight) {
349
			return undefined;
350 351 352
		}

		// The panel is minimized
353
		if (!this._isVisible) {
354
			return TerminalInstance._lastKnownCanvasDimensions;
355 356
		}

357
		if (!this._wrapperElement) {
358
			return undefined;
359 360 361
		}

		const wrapperElementStyle = getComputedStyle(this._wrapperElement);
D
Daniel Imms 已提交
362 363 364
		const marginLeft = parseInt(wrapperElementStyle.marginLeft!.split('px')[0], 10);
		const marginRight = parseInt(wrapperElementStyle.marginRight!.split('px')[0], 10);
		const bottom = parseInt(wrapperElementStyle.bottom!.split('px')[0], 10);
365

D
Daniel Imms 已提交
366
		const innerWidth = width - marginLeft - marginRight;
367
		const innerHeight = height - bottom - 1;
368

369 370
		TerminalInstance._lastKnownCanvasDimensions = new dom.Dimension(innerWidth, innerHeight);
		return TerminalInstance._lastKnownCanvasDimensions;
371 372
	}

373 374
	public get remoteTerminalId(): number | undefined { return this._processManager.remoteTerminalId; }

375 376 377 378 379 380 381 382 383 384 385 386 387 388
	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;
	}

389 390 391
	/**
	 * Create xterm.js instance and attach data listeners.
	 */
392
	protected async _createXterm(): Promise<XTermTerminal> {
393
		const Terminal = await this._getXtermConstructor();
394
		const font = this._configHelper.getFont(undefined, true);
D
Daniel Imms 已提交
395
		const config = this._configHelper.config;
396 397
		const editorOptions = this._configurationService.getValue<IEditorOptions>('editor');

398
		const xterm = new Terminal({
399
			altClickMovesCursor: config.altClickMovesCursor,
D
Daniel Imms 已提交
400
			scrollback: config.scrollback,
401
			theme: this._getXtermTheme(),
402
			drawBoldTextInBrightColors: config.drawBoldTextInBrightColors,
403
			fontFamily: font.fontFamily,
D
Daniel Imms 已提交
404 405
			fontWeight: config.fontWeight,
			fontWeightBold: config.fontWeightBold,
406
			fontSize: font.fontSize,
407
			letterSpacing: font.letterSpacing,
408
			lineHeight: font.lineHeight,
409
			minimumContrastRatio: config.minimumContrastRatio,
D
Daniel Imms 已提交
410 411
			bellStyle: config.enableBell ? 'sound' : 'none',
			macOptionIsMeta: config.macOptionIsMeta,
412
			macOptionClickForcesSelection: config.macOptionClickForcesSelection,
D
Daniel Imms 已提交
413
			rightClickSelectsWord: config.rightClickBehavior === 'selectWord',
D
Daniel Imms 已提交
414
			fastScrollModifier: 'alt',
415 416
			fastScrollSensitivity: editorOptions.fastScrollSensitivity,
			scrollSensitivity: editorOptions.mouseWheelScrollSensitivity,
417
			rendererType: config.rendererType === 'auto' || config.rendererType === 'experimentalWebgl' ? 'canvas' : config.rendererType,
418
			wordSeparator: config.wordSeparators
419
		});
420
		this._xterm = xterm;
421
		this._xtermCore = (xterm as any)._core as XTermCore;
D
Daniel Imms 已提交
422
		this._updateUnicodeVersion();
423
		this.updateAccessibilitySupport();
D
Daniel Imms 已提交
424 425
		this._terminalInstanceService.getXtermSearchConstructor().then(Addon => {
			this._xtermSearch = new Addon();
426
			xterm.loadAddon(this._xtermSearch);
D
Daniel Imms 已提交
427
		});
428 429 430
		if (this._shellLaunchConfig.initialText) {
			this._xterm.writeln(this._shellLaunchConfig.initialText);
		}
D
Daniel Imms 已提交
431 432
		this._xterm.onLineFeed(() => this._onLineFeed());
		this._xterm.onKey(e => this._onKey(e.key, e.domEvent));
433
		this._xterm.onSelectionChange(async () => this._onSelectionChange());
434

A
Alex Dima 已提交
435
		this._processManager.onProcessData(e => this._onProcessData(e));
436
		this._xterm.onData(data => this._processManager.write(data));
D
Daniel Imms 已提交
437
		this.processReady.then(async () => {
438 439
			if (this._linkManager) {
				this._linkManager.processCwd = await this._processManager.getInitialCwd();
440
			}
D
Daniel Imms 已提交
441 442 443 444 445 446 447 448 449
		});
		// Init winpty compat and link handler after process creation as they rely on the
		// underlying process OS
		this._processManager.onProcessReady(() => {
			if (this._processManager.os === platform.OperatingSystem.Windows) {
				xterm.setOption('windowsMode', true);
				// 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 已提交
450
				xterm.parser.registerCsiHandler({ final: 'H' }, () => {
D
Daniel Imms 已提交
451 452 453 454
					this._onCursorMove();
					return false;
				});
			}
D
Daniel Imms 已提交
455
			this._linkManager = this._instantiationService.createInstance(TerminalLinkManager, xterm, this._processManager!);
456 457
			this._areLinksReady = true;
			this._onLinksReady.fire(this);
D
Daniel Imms 已提交
458
		});
D
Daniel Imms 已提交
459

460 461
		this._commandTrackerAddon = new CommandTrackerAddon();
		this._xterm.loadAddon(this._commandTrackerAddon);
M
Martin Aeschlimann 已提交
462
		this._register(this._themeService.onDidColorThemeChange(theme => this._updateTheme(xterm, theme)));
463 464 465 466 467
		this._register(this._viewDescriptorService.onDidChangeLocation(({ views }) => {
			if (views.some(v => v.id === TERMINAL_VIEW_ID)) {
				this._updateTheme(xterm);
			}
		}));
468

469 470
		this._xtermTypeAhead = this._register(this._instantiationService.createInstance(TypeAheadAddon, this._processManager, this._configHelper));
		this._xterm.loadAddon(this._xtermTypeAhead);
C
wip  
Connor Peet 已提交
471

472
		return xterm;
473 474
	}

D
Daniel Imms 已提交
475 476 477 478 479
	public reattachToElement(container: HTMLElement): void {
		if (!this._wrapperElement) {
			throw new Error('The terminal instance has not been attached to a container yet');
		}

480
		this._wrapperElement.parentNode?.removeChild(this._wrapperElement);
D
Daniel Imms 已提交
481 482 483 484
		this._container = container;
		this._container.appendChild(this._wrapperElement);
	}

485
	public attachToElement(container: HTMLElement): void {
486 487 488 489 490 491 492 493 494 495 496 497
		// The container did not change, do nothing
		if (this._container === container) {
			return;
		}

		// Attach has not occured yet
		if (!this._wrapperElement) {
			this._attachToElement(container);
			return;
		}

		// The container changed, reattach
498
		this._container?.removeChild(this._wrapperElement);
499 500 501 502
		this._container = container;
		this._container.appendChild(this._wrapperElement);
	}

503 504
	public async _attachToElement(container: HTMLElement): Promise<void> {
		const xterm = await this._xtermReadyPromise;
505

506 507 508
		if (this._wrapperElement) {
			throw new Error('The terminal instance has already been attached to a container');
		}
509

510 511
		this._container = container;
		this._wrapperElement = document.createElement('div');
512
		this._wrapperElement.classList.add('terminal-wrapper');
513
		this._xtermElement = document.createElement('div');
D
Daniel Imms 已提交
514

515 516 517 518 519 520 521
		// Attach the xterm object to the DOM, exposing it to the smoke tests
		this._wrapperElement.xterm = this._xterm;

		this._wrapperElement.appendChild(this._xtermElement);
		this._container.appendChild(this._wrapperElement);
		xterm.open(this._xtermElement);
		if (this._configHelper.config.rendererType === 'experimentalWebgl') {
D
Daniel Imms 已提交
522
			this._enableWebglRenderer();
523 524 525 526 527
		}

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

529 530
		// Check if custom Terminal title exists and set same
		if (this._title.length > 0) {
D
Daniel Imms 已提交
531
			xterm.textarea.setAttribute('aria-label', nls.localize('terminalTextBoxAriaLabelNumberAndTitle', "Terminal {0}, {1}", this._id, this._title));
532 533 534
		} else {
			xterm.textarea.setAttribute('aria-label', nls.localize('terminalTextBoxAriaLabel', "Terminal {0}", this._id));
		}
535

536 537 538 539 540
		xterm.textarea.addEventListener('focus', () => this._onFocus.fire(this));
		xterm.attachCustomKeyEventHandler((event: KeyboardEvent): boolean => {
			// Disable all input if the terminal is exiting
			if (this._isExiting) {
				return false;
541 542
			}

543 544
			const standardKeyboardEvent = new StandardKeyboardEvent(event);
			const resolveResult = this._keybindingService.softDispatch(standardKeyboardEvent, standardKeyboardEvent.target);
545

546 547 548
			// 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
549 550
			const isValidChord = resolveResult?.enterChord && this._configHelper.config.allowChords && event.key !== 'Escape';
			if (this._keybindingService.inChordMode || isValidChord) {
551 552 553
				event.preventDefault();
				return false;
			}
D
Daniel Imms 已提交
554

M
Megan Rogge 已提交
555 556 557 558 559 560 561 562 563 564 565 566
			const SHOW_TERMINAL_CONFIG_PROMPT = 'terminal.integrated.showTerminalConfigPrompt';
			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
			if (this._storageService.getBoolean(SHOW_TERMINAL_CONFIG_PROMPT, StorageScope.GLOBAL, true) &&
				!EXCLUDED_KEYS.includes(event.key) &&
				!event.ctrlKey &&
				!event.shiftKey &&
				!event.altKey) {
				this.hasHadInput = true;
			}

567 568
			// for keyboard events that resolve to commands described
			// within commandsToSkipShell, either alert or skip processing by xterm.js
M
Megan Rogge 已提交
569
			if (resolveResult && resolveResult.commandId && this._skipTerminalCommands.some(k => k === resolveResult.commandId) && !this._configHelper.config.sendKeybindingsToShell) {
570 571
				// don't alert when terminal is opened or closed
				if (this._storageService.getBoolean(SHOW_TERMINAL_CONFIG_PROMPT, StorageScope.GLOBAL, true) &&
M
Megan Rogge 已提交
572 573
					this.hasHadInput &&
					!TERMINAL_CREATION_COMMANDS.includes(resolveResult.commandId)) {
574 575 576 577 578 579 580 581 582 583 584 585 586 587
					this._notificationService.prompt(
						Severity.Info,
						nls.localize('configure terminal settings', "Some keybindings are dispatched to the workbench by default."),
						[
							{
								label: nls.localize('configureTerminalSettings', "Configure Terminal Settings"),
								run: () => {
									this._preferencesService.openSettings(false, '@id:terminal.integrated.commandsToSkipShell,terminal.integrated.sendKeybindingsToShell,terminal.integrated.allowChords');
								}
							} as IPromptChoice
						]
					);
					this._storageService.store(SHOW_TERMINAL_CONFIG_PROMPT, false, StorageScope.GLOBAL, StorageTarget.USER);
				}
588 589 590
				event.preventDefault();
				return false;
			}
D
Daniel Imms 已提交
591

592
			// Skip processing by xterm.js of keyboard events that match menu bar mnemonics
D
Daniel Imms 已提交
593
			if (this._configHelper.config.allowMnemonics && !platform.isMacintosh && event.altKey) {
594 595 596
				return false;
			}

597 598 599 600
			// If tab focus mode is on, tab is not passed to the terminal
			if (TabFocus.getTabFocusMode() && event.keyCode === 9) {
				return false;
			}
601

602 603 604 605 606
			// Always have alt+F4 skip the terminal on Windows and allow it to be handled by the
			// system
			if (platform.isWindows && event.altKey && event.key === 'F4' && !event.ctrlKey) {
				return false;
			}
607

608 609 610 611 612 613
			// 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;
			}

614 615 616 617 618 619 620 621 622 623
			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 已提交
624
			});
625
		}));
D
Daniel Imms 已提交
626

627 628 629 630 631 632
		// 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 已提交
633

634 635
		this._register(dom.addDisposableListener(xterm.textarea, 'focus', () => {
			this._terminalFocusContextKey.set(true);
D
Daniel Imms 已提交
636 637 638 639 640
			if (this.shellType) {
				this._terminalShellTypeContextKey.set(this.shellType.toString());
			} else {
				this._terminalShellTypeContextKey.reset();
			}
641 642 643 644 645 646 647 648
			this._onFocused.fire(this);
		}));
		this._register(dom.addDisposableListener(xterm.textarea, 'blur', () => {
			this._terminalFocusContextKey.reset();
			this._refreshSelectionContextKey();
		}));
		this._register(dom.addDisposableListener(xterm.element, 'focus', () => {
			this._terminalFocusContextKey.set(true);
D
Daniel Imms 已提交
649 650 651 652 653
			if (this.shellType) {
				this._terminalShellTypeContextKey.set(this.shellType.toString());
			} else {
				this._terminalShellTypeContextKey.reset();
			}
654 655 656 657 658 659
		}));
		this._register(dom.addDisposableListener(xterm.element, 'blur', () => {
			this._terminalFocusContextKey.reset();
			this._refreshSelectionContextKey();
		}));

660
		this._widgetManager.attachToElement(xterm.element);
661
		this._processManager.onProcessReady(() => this._linkManager?.setWidgetManager(this._widgetManager));
662 663 664 665 666 667 668 669 670 671 672 673 674 675 676 677 678 679

		const computedStyle = window.getComputedStyle(this._container);
		const width = parseInt(computedStyle.getPropertyValue('width').replace('px', ''), 10);
		const height = parseInt(computedStyle.getPropertyValue('height').replace('px', ''), 10);
		this.layout(new dom.Dimension(width, height));
		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);
		}

		const neverMeasureRenderTime = this._storageService.getBoolean(NEVER_MEASURE_RENDER_TIME_STORAGE_KEY, StorageScope.GLOBAL, false);
		if (!neverMeasureRenderTime && this._configHelper.config.rendererType === 'auto') {
			this._measureRenderTime();
		}
680 681
	}

682
	private async _measureRenderTime(): Promise<void> {
683
		await this._xtermReadyPromise;
D
Daniel Imms 已提交
684
		const frameTimes: number[] = [];
685
		const textRenderLayer = this._xtermCore!._renderService._renderer._renderLayers[0];
D
Daniel Imms 已提交
686
		const originalOnGridChanged = textRenderLayer.onGridChanged;
687 688 689 690 691

		const evaluateCanvasRenderer = () => {
			// Discard first frame time as it's normal to take longer
			frameTimes.shift();

D
Daniel Imms 已提交
692
			const medianTime = frameTimes.sort((a, b) => a - b)[Math.floor(frameTimes.length / 2)];
693
			if (medianTime > SLOW_CANVAS_RENDER_THRESHOLD) {
D
Daniel Imms 已提交
694 695 696
				const promptChoices: IPromptChoice[] = [
					{
						label: nls.localize('yes', "Yes"),
697
						run: () => this._configurationService.updateValue('terminal.integrated.rendererType', 'dom', ConfigurationTarget.USER)
D
Daniel Imms 已提交
698 699
					} as IPromptChoice,
					{
700 701
						label: nls.localize('no', "No"),
						run: () => { }
D
Daniel Imms 已提交
702 703 704 705
					} as IPromptChoice,
					{
						label: nls.localize('dontShowAgain', "Don't Show Again"),
						isSecondary: true,
706
						run: () => this._storageService.store(NEVER_MEASURE_RENDER_TIME_STORAGE_KEY, true, StorageScope.GLOBAL, StorageTarget.MACHINE)
D
Daniel Imms 已提交
707 708 709 710
					} as IPromptChoice
				];
				this._notificationService.prompt(
					Severity.Warning,
D
Daniel Imms 已提交
711
					nls.localize('terminal.slowRendering', 'The standard renderer for the integrated terminal appears to be slow on your computer. Would you like to switch to the alternative DOM-based renderer 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 已提交
712 713 714
					promptChoices
				);
			}
715
		};
716

717 718 719 720 721 722 723 724 725
		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 已提交
726 727 728
		};
	}

729
	public hasSelection(): boolean {
730
		return this._xterm ? this._xterm.hasSelection() : false;
731 732
	}

733
	public async copySelection(): Promise<void> {
734
		const xterm = await this._xtermReadyPromise;
D
Daniel Imms 已提交
735
		if (this.hasSelection()) {
736
			await this._clipboardService.writeText(xterm.getSelection());
D
Daniel Imms 已提交
737
		} else {
738
			this._notificationService.warn(nls.localize('terminal.integrated.copySelection.noSelection', 'The terminal has no selection to copy'));
D
Daniel Imms 已提交
739
		}
D
Daniel Imms 已提交
740 741
	}

D
Daniel Imms 已提交
742
	public get selection(): string | undefined {
743
		return this._xterm && this.hasSelection() ? this._xterm.getSelection() : undefined;
744 745
	}

746
	public clearSelection(): void {
747
		this._xterm?.clearSelection();
748 749 750
	}

	public selectAll(): void {
751
		// Focus here to ensure the terminal context key is set
752 753
		this._xterm?.focus();
		this._xterm?.selectAll();
754 755
	}

756
	public findNext(term: string, searchOptions: ISearchOptions): boolean {
D
Daniel Imms 已提交
757 758 759
		if (!this._xtermSearch) {
			return false;
		}
D
Daniel Imms 已提交
760
		return this._xtermSearch.findNext(term, searchOptions);
R
rebornix 已提交
761 762
	}

763
	public findPrevious(term: string, searchOptions: ISearchOptions): boolean {
D
Daniel Imms 已提交
764 765 766
		if (!this._xtermSearch) {
			return false;
		}
D
Daniel Imms 已提交
767
		return this._xtermSearch.findPrevious(term, searchOptions);
R
rebornix 已提交
768 769
	}

770
	public notifyFindWidgetFocusChanged(isFocused: boolean): void {
771 772 773
		if (!this._xterm) {
			return;
		}
774 775
		const terminalFocused = !isFocused && (document.activeElement === this._xterm.textarea || document.activeElement === this._xterm.element);
		this._terminalFocusContextKey.set(terminalFocused);
776
	}
R
rebornix 已提交
777 778 779 780

	public refreshFocusState() {
		this.notifyFindWidgetFocusChanged(false);
	}
781

782
	public dispose(immediate?: boolean): void {
D
Daniel Imms 已提交
783 784
		this._logService.trace(`terminalInstance#dispose (id: ${this.id})`);

785
		dispose(this._windowsShellHelper);
786
		this._windowsShellHelper = undefined;
787 788
		dispose(this._linkManager);
		this._linkManager = undefined;
789 790
		dispose(this._commandTrackerAddon);
		this._commandTrackerAddon = undefined;
791
		dispose(this._widgetManager);
792

K
Kai Wood 已提交
793
		if (this._xterm && this._xterm.element) {
794
			this._hadFocusOnExit = this._xterm.element.classList.contains('focus');
K
Kai Wood 已提交
795
		}
D
Daniel Imms 已提交
796
		if (this._wrapperElement) {
D
Daniel Imms 已提交
797 798
			if (this._wrapperElement.xterm) {
				this._wrapperElement.xterm = undefined;
799
			}
800
			if (this._wrapperElement.parentElement && this._container) {
D
Daniel Imms 已提交
801 802
				this._container.removeChild(this._wrapperElement);
			}
D
Daniel Imms 已提交
803
		}
D
Daniel Imms 已提交
804
		if (this._xterm) {
805
			const buffer = this._xterm.buffer;
D
Daniel Imms 已提交
806
			this._sendLineData(buffer.active, buffer.active.baseY + buffer.active.cursorY);
D
Daniel Imms 已提交
807
			this._xterm.dispose();
D
Daniel Imms 已提交
808
		}
809 810 811 812 813 814

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

815
		this._processManager.dispose(immediate);
816 817 818
		// Process manager dispose/shutdown doesn't fire process exit, trigger with undefined if it
		// hasn't happened yet
		this._onProcessExit(undefined);
819

820 821 822 823
		if (!this._isDisposed) {
			this._isDisposed = true;
			this._onDisposed.fire(this);
		}
824
		super.dispose();
D
Daniel Imms 已提交
825 826
	}

827
	public forceRedraw(): void {
828 829 830
		if (!this._xterm) {
			return;
		}
831
		this._webglAddon?.clearTextureAtlas();
D
Daniel Imms 已提交
832
		// TODO: Do canvas renderer too?
833 834
	}

D
Daniel Imms 已提交
835
	public focus(force?: boolean): void {
836 837 838
		if (!this._xterm) {
			return;
		}
D
Daniel Imms 已提交
839 840 841 842 843
		const selection = window.getSelection();
		if (!selection) {
			return;
		}
		const text = selection.toString();
844 845 846 847 848
		if (!text || force) {
			this._xterm.focus();
		}
	}

849 850 851
	public async focusWhenReady(force?: boolean): Promise<void> {
		await this._xtermReadyPromise;
		this.focus(force);
D
Daniel Imms 已提交
852 853
	}

854
	public async paste(): Promise<void> {
855 856 857
		if (!this._xterm) {
			return;
		}
D
Daniel Imms 已提交
858
		this.focus();
859
		this._xterm.paste(await this._clipboardService.readText());
D
Daniel Imms 已提交
860
	}
D
Daniel Imms 已提交
861

862
	public async sendText(text: string, addNewLine: boolean): Promise<void> {
D
Daniel Imms 已提交
863 864 865 866 867 868
		// Normalize line endings to 'enter' press.
		text = text.replace(TerminalInstance.EOL_REGEX, '\r');
		if (addNewLine && text.substr(text.length - 1) !== '\r') {
			text += '\r';
		}

869
		// Send it to the process
870 871
		await this._processManager.ptyProcessReady;
		this._processManager.write(text);
D
Daniel Imms 已提交
872
	}
873 874

	public setVisible(visible: boolean): void {
D
Daniel Imms 已提交
875 876
		this._isVisible = visible;
		if (this._wrapperElement) {
877
			this._wrapperElement.classList.toggle('active', visible);
878
		}
879
		if (visible && this._xterm && this._xtermCore) {
880 881 882 883
			// 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 已提交
884 885
			// This can likely be removed after https://github.com/xtermjs/xterm.js/issues/291 is
			// fixed upstream.
D
Daniel Imms 已提交
886
			this._xtermCore._onScroll.fire(this._xterm.buffer.active.viewportY);
887
			if (this._container && this._container.parentElement) {
B
Benjamin Pasero 已提交
888 889 890 891
				// Force a layout when the instance becomes invisible. This is particularly important
				// for ensuring that terminals that are created in the background by an extension will
				// correctly get correct character measurements in order to render to the screen (see
				// #34554).
D
Daniel Imms 已提交
892
				const computedStyle = window.getComputedStyle(this._container.parentElement);
B
Benjamin Pasero 已提交
893 894
				const width = parseInt(computedStyle.getPropertyValue('width').replace('px', ''), 10);
				const height = parseInt(computedStyle.getPropertyValue('height').replace('px', ''), 10);
895
				this.layout(new dom.Dimension(width, height));
896 897 898
				// HACK: Trigger another async layout to ensure xterm's CharMeasure is ready to use,
				// this hack can be removed when https://github.com/xtermjs/xterm.js/issues/702 is
				// supported.
J
jeanp413 已提交
899 900
				this._timeoutDimension = new dom.Dimension(width, height);
				setTimeout(() => this.layout(this._timeoutDimension!), 0);
B
Benjamin Pasero 已提交
901
			}
902
		}
903 904
	}

905
	public scrollDownLine(): void {
906
		this._xterm?.scrollLines(1);
D
Daniel Imms 已提交
907 908
	}

909
	public scrollDownPage(): void {
910
		this._xterm?.scrollPages(1);
911 912
	}

913
	public scrollToBottom(): void {
914
		this._xterm?.scrollToBottom();
915 916
	}

917
	public scrollUpLine(): void {
918
		this._xterm?.scrollLines(-1);
D
Daniel Imms 已提交
919
	}
920

921
	public scrollUpPage(): void {
922
		this._xterm?.scrollPages(-1);
923 924
	}

925
	public scrollToTop(): void {
926
		this._xterm?.scrollToTop();
927 928
	}

D
Daniel Imms 已提交
929
	public clear(): void {
930
		this._xterm?.clear();
D
Daniel Imms 已提交
931 932
	}

933
	private _refreshSelectionContextKey() {
934
		const isActive = !!this._viewsService.getActiveViewWithId(TERMINAL_VIEW_ID);
D
Daniel Imms 已提交
935
		this._terminalHasTextContextKey.set(isActive && this.hasSelection());
936 937
	}

938
	protected _createProcessManager(): void {
939
		this._processManager = this._instantiationService.createInstance(TerminalProcessManager, this._id, this._configHelper);
940
		this._processManager.onProcessReady(() => this._onProcessIdReady.fire(this));
D
Daniel Imms 已提交
941
		this._processManager.onProcessExit(exitCode => this._onProcessExit(exitCode));
A
Alex Dima 已提交
942 943 944
		this._processManager.onProcessData(ev => {
			this._initialDataEvents?.push(ev.data);
			this._onData.fire(ev.data);
945
		});
A
Alex Dima 已提交
946
		this._processManager.onProcessOverrideDimensions(e => this.setDimensions(e, true));
947
		this._processManager.onProcessResolvedShellLaunchConfig(e => this._setResolvedShellLaunchConfig(e));
D
Daniel Imms 已提交
948
		this._processManager.onEnvironmentVariableInfoChanged(e => this._onEnvironmentVariableInfoChanged(e));
D
Daniel Imms 已提交
949

950
		if (this._shellLaunchConfig.name) {
951
			this.setTitle(this._shellLaunchConfig.name, TitleEventSource.Api);
A
Amy Qiu 已提交
952
		} else {
953
			// Only listen for process title changes when a name is not provided
954 955 956 957 958 959 960 961 962 963 964 965 966
			if (this._configHelper.config.experimentalUseTitleEvent) {
				this._processManager.ptyProcessReady.then(() => {
					this._terminalInstanceService.getDefaultShellAndArgs(false).then(e => {
						this.setTitle(e.shell, TitleEventSource.Sequence);
					});
					this._xtermReadyPromise.then(xterm => {
						this._messageTitleDisposable = xterm.onTitleChange(e => this._onTitleChange(e));
					});
				});
			} else {
				this.setTitle(this._shellLaunchConfig.executable, TitleEventSource.Process);
				this._messageTitleDisposable = this._processManager.onProcessTitle(title => this.setTitle(title ? title : '', TitleEventSource.Process));
			}
967
		}
D
Daniel Imms 已提交
968 969 970

		if (platform.isWindows) {
			this._processManager.ptyProcessReady.then(() => {
971
				if (this._processManager.remoteAuthority) {
D
Daniel Imms 已提交
972 973
					return;
				}
974
				this._xtermReadyPromise.then(xterm => {
D
Daniel Imms 已提交
975
					if (!this._isDisposed && this._processManager && this._processManager.shellProcessId) {
976 977 978
						this._windowsShellHelper = this._terminalInstanceService.createWindowsShellHelper(this._processManager.shellProcessId, xterm);
						this._windowsShellHelper.onShellNameChange(title => {
							this.setShellType(this.getShellType(title));
979
							if (this.isTitleSetByProcess && !this._configHelper.config.experimentalUseTitleEvent) {
980 981 982
								this.setTitle(title, TitleEventSource.Process);
							}
						});
D
Daniel Imms 已提交
983 984 985 986
					}
				});
			});
		}
987 988
	}

D
Daniel Imms 已提交
989 990 991 992 993 994 995 996
	private _createProcess(): void {
		this._processManager.createProcess(this._shellLaunchConfig, this._cols, this._rows, this._accessibilityService.isScreenReaderOptimized()).then(error => {
			if (error) {
				this._onProcessExit(error);
			}
		});
	}

997 998 999 1000 1001 1002 1003 1004 1005 1006 1007 1008 1009 1010 1011 1012 1013 1014 1015 1016 1017 1018
	private getShellType(executable: string): TerminalShellType {
		switch (executable.toLowerCase()) {
			case 'cmd.exe':
				return WindowsShellType.CommandPrompt;
			case 'powershell.exe':
			case 'pwsh.exe':
				return WindowsShellType.PowerShell;
			case 'bash.exe':
				return WindowsShellType.GitBash;
			case 'wsl.exe':
			case 'ubuntu.exe':
			case 'ubuntu1804.exe':
			case 'kali.exe':
			case 'debian.exe':
			case 'opensuse-42.exe':
			case 'sles-12.exe':
				return WindowsShellType.Wsl;
			default:
				return undefined;
		}
	}

A
Alex Dima 已提交
1019 1020 1021 1022 1023
	private _onProcessData(ev: IProcessDataEvent): void {
		if (ev.sync) {
			this._xtermCore?.writeSync(ev.data);
		} else {
			const messageId = ++this._latestXtermWriteData;
1024 1025
			this._xterm?.write(ev.data, () => {
				this._latestXtermParseData = messageId;
1026 1027 1028
				if (this._shellLaunchConfig.flowControl) {
					this._processManager.acknowledgeDataEvent(ev.data.length);
				}
1029
			});
A
Alex Dima 已提交
1030
		}
1031 1032
	}

1033
	/**
D
Daniel Imms 已提交
1034
	 * Called when either a process tied to a terminal has exited or when a terminal renderer
1035
	 * simulates a process exiting (e.g. custom execution task).
D
Daniel Imms 已提交
1036 1037
	 * @param exitCode The exit code of the process, this is undefined when the terminal was exited
	 * through user action.
1038
	 */
1039
	private async _onProcessExit(exitCodeOrError?: number | ITerminalLaunchError): Promise<void> {
1040 1041 1042 1043 1044
		// Prevent dispose functions being triggered multiple times
		if (this._isExiting) {
			return;
		}

1045 1046 1047
		this._isExiting = true;

		await this._flushXtermData();
1048
		this._logService.debug(`Terminal process exit (id: ${this.id}) with code ${this._exitCode}`);
1049

1050
		let exitCodeMessage: string | undefined;
B
bpceee 已提交
1051

1052
		// Create exit code message
1053 1054
		switch (typeof exitCodeOrError) {
			case 'number':
1055
				// Only show the error if the exit code is non-zero
1056
				this._exitCode = exitCodeOrError;
1057 1058 1059
				if (this._exitCode === 0) {
					break;
				}
1060

1061 1062 1063
				let commandLine: string | undefined = undefined;
				if (this._shellLaunchConfig.executable) {
					commandLine = this._shellLaunchConfig.executable;
1064
					if (typeof this._shellLaunchConfig.args === 'string') {
1065
						commandLine += ` ${this._shellLaunchConfig.args}`;
1066
					} else if (this._shellLaunchConfig.args && this._shellLaunchConfig.args.length) {
1067
						commandLine += this._shellLaunchConfig.args.map(a => ` '${a}'`).join();
1068
					}
1069 1070 1071 1072
				}

				if (this._processManager.processState === ProcessState.KILLED_DURING_LAUNCH) {
					if (commandLine) {
1073
						exitCodeMessage = nls.localize('launchFailed.exitCodeAndCommandLine', "The terminal process \"{0}\" failed to launch (exit code: {1}).", commandLine, this._exitCode);
1074
						break;
1075
					}
1076
					exitCodeMessage = nls.localize('launchFailed.exitCodeOnly', "The terminal process failed to launch (exit code: {0}).", this._exitCode);
1077 1078 1079
					break;
				}
				if (commandLine) {
1080
					exitCodeMessage = nls.localize('terminated.exitCodeAndCommandLine', "The terminal process \"{0}\" terminated with exit code: {1}.", commandLine, this._exitCode);
1081
					break;
1082
				}
1083
				exitCodeMessage = nls.localize('terminated.exitCodeOnly', "The terminal process terminated with exit code: {0}.", this._exitCode);
1084 1085 1086
				break;
			case 'object':
				this._exitCode = exitCodeOrError.code;
1087
				exitCodeMessage = nls.localize('launchFailed.errorMessage', "The terminal process failed to launch: {0}.", exitCodeOrError.message);
1088
				break;
1089
		}
1090

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

1093 1094
		// Only trigger wait on exit when the exit was *not* triggered by the
		// user (via the `workbench.action.terminal.kill` command).
1095
		if (this._shellLaunchConfig.waitOnExit && this._processManager.processState !== ProcessState.KILLED_BY_USER) {
1096 1097 1098 1099 1100 1101 1102 1103 1104 1105 1106 1107 1108 1109 1110 1111
			this._xtermReadyPromise.then(xterm => {
				if (exitCodeMessage) {
					xterm.writeln(exitCodeMessage);
				}
				if (typeof this._shellLaunchConfig.waitOnExit === 'string') {
					let message = this._shellLaunchConfig.waitOnExit;
					// Bold the message and add an extra new line to make it stand out from the rest of the output
					message = `\r\n\x1b[1m${message}\x1b[0m`;
					xterm.writeln(message);
				}
				// Disable all input if the terminal is exiting and listen for next keypress
				xterm.setOption('disableStdin', true);
				if (xterm.textarea) {
					this._attachPressAnyKeyToCloseListener(xterm);
				}
			});
1112 1113
		} else {
			this.dispose();
1114
			if (exitCodeMessage) {
D
Daniel Imms 已提交
1115 1116 1117 1118 1119 1120
				const failedDuringLaunch = this._processManager.processState === ProcessState.KILLED_DURING_LAUNCH;
				if (failedDuringLaunch || this._configHelper.config.showExitAlert) {
					// Always show launch failures
					this._notificationService.notify({
						message: exitCodeMessage,
						severity: Severity.Error,
1121
						actions: { primary: [this._instantiationService.createInstance(TerminalLaunchHelpAction)] }
D
Daniel Imms 已提交
1122
					});
G
Gabriel DeBacker 已提交
1123
				} else {
D
Daniel Imms 已提交
1124 1125 1126
					// Log to help surface the error in case users report issues with showExitAlert
					// disabled
					this._logService.warn(exitCodeMessage);
1127 1128
				}
			}
1129
		}
D
Daniel Imms 已提交
1130

1131
		this._onExit.fire(this._exitCode);
1132 1133
	}

1134 1135 1136 1137 1138
	/**
	 * Ensure write calls to xterm.js have finished before resolving.
	 */
	private _flushXtermData(): Promise<void> {
		if (this._latestXtermWriteData === this._latestXtermParseData) {
D
Daniel Imms 已提交
1139
			return Promise.resolve();
1140 1141 1142 1143 1144 1145 1146 1147 1148 1149 1150 1151
		}
		let retries = 0;
		return new Promise<void>(r => {
			const interval = setInterval(() => {
				if (this._latestXtermWriteData === this._latestXtermParseData || ++retries === 5) {
					clearInterval(interval);
					r();
				}
			}, 20);
		});
	}

1152
	private _attachPressAnyKeyToCloseListener(xterm: XTermTerminal) {
1153
		if (xterm.textarea && !this._pressAnyKeyToCloseListener) {
1154
			this._pressAnyKeyToCloseListener = dom.addDisposableListener(xterm.textarea, 'keypress', (event: KeyboardEvent) => {
1155 1156 1157
				if (this._pressAnyKeyToCloseListener) {
					this._pressAnyKeyToCloseListener.dispose();
					this._pressAnyKeyToCloseListener = undefined;
1158 1159 1160 1161 1162
					this.dispose();
					event.preventDefault();
				}
			});
		}
1163 1164
	}

1165
	public reuseTerminal(shell: IShellLaunchConfig, reset: boolean = false): void {
1166
		// Unsubscribe any key listener we may have.
1167 1168
		this._pressAnyKeyToCloseListener?.dispose();
		this._pressAnyKeyToCloseListener = undefined;
1169

1170
		if (this._xterm) {
1171 1172 1173 1174 1175 1176
			if (reset) {
				this._xterm.reset();
			} else {
				// Ensure new processes' output starts at start of new line
				this._xterm.write('\n\x1b[G');
			}
1177

1178 1179 1180 1181
			// Print initialText if specified
			if (shell.initialText) {
				this._xterm.writeln(shell.initialText);
			}
1182

1183 1184 1185 1186 1187
			// Clean up waitOnExit state
			if (this._isExiting && this._shellLaunchConfig.waitOnExit) {
				this._xterm.setOption('disableStdin', false);
				this._isExiting = false;
			}
1188
		}
1189

1190 1191
		// Dispose the environment info widget if it exists
		this._environmentInfo?.disposable.dispose();
1192
		this._environmentInfo = undefined;
1193

1194 1195
		if (!reset) {
			// HACK: Force initialText to be non-falsy for reused terminals such that the
L
Ladislau Szomoru 已提交
1196 1197 1198
			// 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.
1199 1200
			shell.initialText = ' ';
		}
1201

1202
		// Set the new shell launch config
1203
		this._shellLaunchConfig = shell; // Must be done before calling _createProcess()
1204

1205 1206 1207
		// Kill and clear up the process, making the process manager ready for a new process
		this._processManager.dispose();

G
Gabriel DeBacker 已提交
1208
		// Launch the process unless this is only a renderer.
G
Gabriel DeBacker 已提交
1209
		// In the renderer only cases, we still need to set the title correctly.
1210
		const oldTitle = this._title;
1211
		this._createProcessManager();
1212

1213
		if (oldTitle !== this._title) {
1214
			this.setTitle(this._title, TitleEventSource.Process);
1215
		}
1216

1217
		this._processManager.onProcessData(data => this._onProcessData(data));
D
Daniel Imms 已提交
1218
		this._createProcess();
1219 1220

		this._xtermTypeAhead?.reset(this._processManager);
1221 1222
	}

1223 1224 1225 1226
	public relaunch(): void {
		this.reuseTerminal(this._shellLaunchConfig, true);
	}

1227
	private _onLineFeed(): void {
1228
		const buffer = this._xterm!.buffer;
D
Daniel Imms 已提交
1229
		const newLine = buffer.active.getLine(buffer.active.baseY + buffer.active.cursorY);
1230
		if (newLine && !newLine.isWrapped) {
D
Daniel Imms 已提交
1231
			this._sendLineData(buffer.active, buffer.active.baseY + buffer.active.cursorY - 1);
1232 1233 1234
		}
	}

1235
	private _onCursorMove(): void {
1236
		const buffer = this._xterm!.buffer;
D
Daniel Imms 已提交
1237
		this._sendLineData(buffer.active, buffer.active.baseY + buffer.active.cursorY);
1238 1239
	}

1240 1241 1242 1243 1244 1245
	private _onTitleChange(title: string): void {
		if (this.isTitleSetByProcess) {
			this.setTitle(title, TitleEventSource.Sequence);
		}
	}

1246
	private _sendLineData(buffer: IBuffer, lineIndex: number): void {
D
Daniel Imms 已提交
1247 1248 1249 1250 1251 1252 1253 1254 1255 1256 1257
		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;
1258
		}
D
Daniel Imms 已提交
1259
		this._onLineData.fire(lineData);
1260 1261
	}

N
void  
Noj Vek 已提交
1262
	private _onKey(key: string, ev: KeyboardEvent): void {
1263 1264 1265 1266 1267 1268 1269
		const event = new StandardKeyboardEvent(ev);

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

D
Daniel Imms 已提交
1270
	private async _onSelectionChange(): Promise<void> {
1271 1272 1273 1274 1275 1276 1277
		if (this._configurationService.getValue('terminal.integrated.copyOnSelection')) {
			if (this.hasSelection()) {
				await this.copySelection();
			}
		}
	}

1278 1279 1280 1281
	@debounce(2000)
	private async _updateProcessCwd(): Promise<string> {
		// reset cwd if it has changed, so file based url paths can be resolved
		const cwd = await this.getCwd();
1282 1283
		if (cwd && this._linkManager) {
			this._linkManager.processCwd = cwd;
1284 1285 1286 1287
		}
		return cwd;
	}

1288
	public updateConfig(): void {
1289
		const config = this._configHelper.config;
1290
		this._safeSetOption('altClickMovesCursor', config.altClickMovesCursor);
1291 1292
		this._setCursorBlink(config.cursorBlinking);
		this._setCursorStyle(config.cursorStyle);
B
Bura Chuhadar 已提交
1293
		this._setCursorWidth(config.cursorWidth);
1294 1295
		this._setCommandsToSkipShell(config.commandsToSkipShell);
		this._setEnableBell(config.enableBell);
1296
		this._safeSetOption('scrollback', config.scrollback);
1297
		this._safeSetOption('minimumContrastRatio', config.minimumContrastRatio);
1298 1299
		this._safeSetOption('fastScrollSensitivity', config.fastScrollSensitivity);
		this._safeSetOption('scrollSensitivity', config.mouseWheelScrollSensitivity);
1300
		this._safeSetOption('macOptionIsMeta', config.macOptionIsMeta);
1301
		this._safeSetOption('macOptionClickForcesSelection', config.macOptionClickForcesSelection);
1302
		this._safeSetOption('rightClickSelectsWord', config.rightClickBehavior === 'selectWord');
1303
		this._safeSetOption('wordSeparator', config.wordSeparators);
D
Daniel Imms 已提交
1304 1305 1306 1307 1308
		if (config.rendererType === 'experimentalWebgl') {
			this._enableWebglRenderer();
		} else {
			this._webglAddon?.dispose();
			this._webglAddon = undefined;
1309 1310 1311
			// Never set webgl as it's an addon not a rendererType
			this._safeSetOption('rendererType', config.rendererType === 'auto' ? 'canvas' : config.rendererType);
		}
1312
		this._refreshEnvironmentVariableInfoWidgetState(this._processManager.environmentVariableInfo);
1313 1314
	}

D
Daniel Imms 已提交
1315 1316 1317 1318 1319 1320 1321 1322 1323
	private async _enableWebglRenderer(): Promise<void> {
		if (!this._xterm || this._webglAddon) {
			return;
		}
		const Addon = await this._terminalInstanceService.getXtermWebglConstructor();
		this._webglAddon = new Addon();
		this._xterm.loadAddon(this._webglAddon);
	}

D
Daniel Imms 已提交
1324 1325 1326 1327 1328 1329 1330 1331 1332 1333 1334 1335
	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);
		}
		this._xterm.unicode.activeVersion = this._configHelper.config.unicodeVersion;
	}

1336
	public updateAccessibilitySupport(): void {
I
isidor 已提交
1337
		const isEnabled = this._accessibilityService.isScreenReaderOptimized();
1338 1339
		if (isEnabled) {
			this._navigationModeAddon = new NavigationModeAddon(this._terminalA11yTreeFocusContextKey);
1340
			this._xterm!.loadAddon(this._navigationModeAddon);
1341
		} else {
1342 1343
			this._navigationModeAddon?.dispose();
			this._navigationModeAddon = undefined;
1344
		}
1345
		this._xterm!.setOption('screenReaderMode', isEnabled);
1346 1347
	}

1348
	private _setCursorBlink(blink: boolean): void {
D
Daniel Imms 已提交
1349
		if (this._xterm && this._xterm.getOption('cursorBlink') !== blink) {
D
Daniel Imms 已提交
1350
			this._xterm.setOption('cursorBlink', blink);
D
Daniel Imms 已提交
1351
			this._xterm.refresh(0, this._xterm.rows - 1);
D
Daniel Imms 已提交
1352 1353 1354
		}
	}

1355 1356 1357 1358 1359 1360 1361 1362
	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 已提交
1363
	private _setCursorWidth(width: number): void {
1364 1365 1366 1367 1368
		if (this._xterm && this._xterm.getOption('cursorWidth') !== width) {
			this._xterm.setOption('cursorWidth', width);
		}
	}

1369
	private _setCommandsToSkipShell(commands: string[]): void {
1370
		const excludeCommands = commands.filter(command => command[0] === '-').map(command => command.slice(1));
D
Daniel Imms 已提交
1371
		this._skipTerminalCommands = DEFAULT_COMMANDS_TO_SKIP_SHELL.filter(defaultCommand => {
1372 1373
			return excludeCommands.indexOf(defaultCommand) === -1;
		}).concat(commands);
D
Daniel Imms 已提交
1374 1375
	}

D
Daniel Imms 已提交
1376 1377 1378 1379 1380 1381 1382 1383 1384 1385 1386 1387 1388 1389
	private _setEnableBell(isEnabled: boolean): void {
		if (this._xterm) {
			if (this._xterm.getOption('bellStyle') === 'sound') {
				if (!this._configHelper.config.enableBell) {
					this._xterm.setOption('bellStyle', 'none');
				}
			} else {
				if (this._configHelper.config.enableBell) {
					this._xterm.setOption('bellStyle', 'sound');
				}
			}
		}
	}

1390 1391 1392 1393 1394 1395 1396 1397 1398 1399
	private _safeSetOption(key: string, value: any): void {
		if (!this._xterm) {
			return;
		}

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

1400
	public layout(dimension: dom.Dimension): void {
1401 1402 1403 1404
		if (this.disableLayout) {
			return;
		}

D
Daniel Imms 已提交
1405 1406
		const terminalWidth = this._evaluateColsAndRows(dimension.width, dimension.height);
		if (!terminalWidth) {
D
Daniel Imms 已提交
1407 1408
			return;
		}
1409

J
jeanp413 已提交
1410 1411
		this._timeoutDimension = new dom.Dimension(dimension.width, dimension.height);

1412
		if (this._xterm && this._xterm.element) {
D
Daniel Imms 已提交
1413 1414 1415 1416 1417 1418
			this._xterm.element.style.width = terminalWidth + 'px';
		}

		this._resize();
	}

D
Daniel Imms 已提交
1419
	@debounce(50)
D
Daniel Imms 已提交
1420
	private async _resize(): Promise<void> {
A
Alex Dima 已提交
1421 1422 1423 1424
		this._resizeNow(false);
	}

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

1428
		if (this._xterm && this._xtermCore) {
1429 1430 1431
			// Only apply these settings when the terminal is visible so that
			// the characters are measured correctly.
			if (this._isVisible) {
1432
				const font = this._configHelper.getFont(this._xtermCore);
1433 1434 1435 1436 1437 1438 1439 1440
				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);
				this._safeSetOption('drawBoldTextInBrightColors', config.drawBoldTextInBrightColors);
1441 1442 1443 1444 1445 1446

				// 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;
1447
			}
D
Daniel Imms 已提交
1448

1449 1450 1451 1452
			if (isNaN(cols) || isNaN(rows)) {
				return;
			}

1453
			if (cols !== this._xterm.cols || rows !== this._xterm.rows) {
D
Daniel Imms 已提交
1454 1455
				this._onDimensionsChanged.fire();
			}
D
Daniel Imms 已提交
1456

D
Daniel Imms 已提交
1457
			this._xterm.resize(cols, rows);
1458
			TerminalInstance._lastKnownGridDimensions = { cols, rows };
1459

1460
			if (this._isVisible) {
1461 1462 1463 1464
				// HACK: Force the renderer to unpause by simulating an IntersectionObserver event.
				// This is to fix an issue where dragging the window to the top of the screen to
				// maximize on Windows/Linux would fire an event saying that the terminal was not
				// visible.
1465
				if (this._xterm.getOption('rendererType') === 'canvas') {
1466
					this._xtermCore._renderService?._onIntersectionChange({ intersectionRatio: 1 });
1467 1468 1469
					// 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);
1470
				}
1471
			}
D
Daniel Imms 已提交
1472
		}
1473

A
Alex Dima 已提交
1474 1475 1476 1477 1478 1479 1480
		if (immediate) {
			// do not await, call setDimensions synchronously
			this._processManager.setDimensions(cols, rows);
		} else {
			await this._processManager.ptyProcessReady;
			this._processManager.setDimensions(cols, rows);
		}
D
Daniel Imms 已提交
1481
	}
1482

1483 1484 1485 1486
	public setShellType(shellType: TerminalShellType) {
		this._shellType = shellType;
	}

1487
	public setTitle(title: string | undefined, eventSource: TitleEventSource): void {
1488 1489 1490
		if (!title) {
			return;
		}
1491 1492 1493 1494 1495 1496 1497 1498 1499 1500 1501 1502 1503 1504 1505 1506
		switch (eventSource) {
			case TitleEventSource.Process:
				title = path.basename(title);
				if (platform.isWindows) {
					// Remove the .exe extension
					title = title.split('.exe')[0];
				}
				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
				dispose(this._messageTitleDisposable);
				this._messageTitleDisposable = undefined;
				dispose(this._windowsShellHelper);
				this._windowsShellHelper = undefined;
				break;
A
Amy Qiu 已提交
1507
		}
D
Daniel Imms 已提交
1508
		const didTitleChange = title !== this._title;
D
Daniel Imms 已提交
1509
		this._title = title;
D
Daniel Imms 已提交
1510
		if (didTitleChange) {
1511
			if (this._titleReadyComplete) {
1512
				this._titleReadyComplete(title);
1513
				this._titleReadyComplete = undefined;
1514 1515
			}
			this._onTitleChanged.fire(this);
B
Ben Stein 已提交
1516 1517
		}
	}
D
Daniel Imms 已提交
1518

1519 1520 1521 1522
	public waitForTitle(): Promise<string> {
		return this._titleReadyPromise;
	}

A
Alex Dima 已提交
1523 1524 1525 1526 1527 1528
	public setDimensions(dimensions: ITerminalDimensionsOverride | undefined, immediate: boolean = false): void {
		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 已提交
1529
		this._dimensionsOverride = dimensions;
A
Alex Dima 已提交
1530 1531 1532 1533 1534
		if (immediate) {
			this._resizeNow(true);
		} else {
			this._resize();
		}
D
Daniel Imms 已提交
1535 1536
	}

1537
	private _setResolvedShellLaunchConfig(shellLaunchConfig: IShellLaunchConfig): void {
1538 1539 1540 1541 1542 1543
		this._shellLaunchConfig.args = shellLaunchConfig.args;
		this._shellLaunchConfig.cwd = shellLaunchConfig.cwd;
		this._shellLaunchConfig.executable = shellLaunchConfig.executable;
		this._shellLaunchConfig.env = shellLaunchConfig.env;
	}

1544 1545 1546 1547 1548 1549
	public showEnvironmentInfoHover(): void {
		if (this._environmentInfo) {
			this._environmentInfo.widget.focus();
		}
	}

D
Daniel Imms 已提交
1550
	private _onEnvironmentVariableInfoChanged(info: IEnvironmentVariableInfo): void {
1551
		if (info.requiresAction) {
D
Daniel Imms 已提交
1552
			this._xterm?.textarea?.setAttribute('aria-label', nls.localize('terminalStaleTextBoxAriaLabel', "Terminal {0} environment is stale, run the 'Show Environment Information' command for more information", this._id));
1553
		}
1554 1555 1556 1557
		this._refreshEnvironmentVariableInfoWidgetState(info);
	}

	private _refreshEnvironmentVariableInfoWidgetState(info?: IEnvironmentVariableInfo): void {
1558
		this._environmentInfo?.disposable.dispose();
1559 1560 1561 1562 1563

		// Check if the widget should not exist
		if (!info ||
			this._configHelper.config.environmentChangesIndicator === 'off' ||
			this._configHelper.config.environmentChangesIndicator === 'warnonly' && !info.requiresAction) {
1564
			this._environmentInfo = undefined;
1565 1566 1567 1568 1569
			return;
		}

		// (Re-)create the widget
		const widget = this._instantiationService.createInstance(EnvironmentVariableInfoWidget, info);
1570 1571 1572 1573
		const disposable = this._widgetManager.attachWidget(widget);
		if (disposable) {
			this._environmentInfo = { widget, disposable };
		}
D
Daniel Imms 已提交
1574 1575
	}

M
Martin Aeschlimann 已提交
1576
	private _getXtermTheme(theme?: IColorTheme): any {
D
Daniel Imms 已提交
1577
		if (!theme) {
M
Martin Aeschlimann 已提交
1578
			theme = this._themeService.getColorTheme();
D
Daniel Imms 已提交
1579 1580
		}

1581
		const location = this._viewDescriptorService.getViewLocationById(TERMINAL_VIEW_ID)!;
D
Daniel Imms 已提交
1582
		const foregroundColor = theme.getColor(TERMINAL_FOREGROUND_COLOR);
1583
		const backgroundColor = theme.getColor(TERMINAL_BACKGROUND_COLOR) || (location === ViewContainerLocation.Sidebar ? theme.getColor(SIDE_BAR_BACKGROUND) : theme.getColor(PANEL_BACKGROUND));
1584 1585 1586
		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 已提交
1587 1588 1589 1590 1591 1592

		return {
			background: backgroundColor ? backgroundColor.toString() : null,
			foreground: foregroundColor ? foregroundColor.toString() : null,
			cursor: cursorColor ? cursorColor.toString() : null,
			cursorAccent: cursorAccentColor ? cursorAccentColor.toString() : null,
1593
			selection: selectionColor ? selectionColor.toString() : null,
D
Daniel Imms 已提交
1594 1595 1596 1597 1598 1599 1600 1601 1602 1603 1604 1605 1606 1607 1608 1609
			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 已提交
1610 1611 1612
		};
	}

M
Martin Aeschlimann 已提交
1613
	private _updateTheme(xterm: XTermTerminal, theme?: IColorTheme): void {
1614
		xterm.setOption('theme', this._getXtermTheme(theme));
D
Daniel Imms 已提交
1615
	}
1616

1617 1618 1619 1620
	public async toggleEscapeSequenceLogging(): Promise<void> {
		const xterm = await this._xtermReadyPromise;
		const isDebug = xterm.getOption('logLevel') === 'debug';
		xterm.setOption('logLevel', isDebug ? 'info' : 'debug');
1621
	}
1622

1623 1624
	public getInitialCwd(): Promise<string> {
		return this._processManager.getInitialCwd();
1625 1626 1627
	}

	public getCwd(): Promise<string> {
1628
		return this._processManager.getCwd();
1629
	}
1630 1631

	public registerLinkProvider(provider: ITerminalExternalLinkProvider): IDisposable {
D
Daniel Imms 已提交
1632
		if (!this._linkManager) {
1633
			throw new Error('TerminalInstance.registerLinkProvider before link manager was ready');
1634
		}
D
Daniel Imms 已提交
1635
		return this._linkManager.registerExternalLinkProvider(this, provider);
1636
	}
1637
}
1638

M
Martin Aeschlimann 已提交
1639
registerThemingParticipant((theme: IColorTheme, collector: ICssStyleCollector) => {
1640 1641 1642 1643
	// Border
	const border = theme.getColor(activeContrastBorder);
	if (border) {
		collector.addRule(`
1644 1645
			.monaco-workbench.hc-black .pane-body.integrated-terminal .xterm.focus::before,
			.monaco-workbench.hc-black .pane-body.integrated-terminal .xterm:focus::before { border-color: ${border}; }`
1646 1647
		);
	}
1648 1649 1650 1651 1652

	// Scrollbar
	const scrollbarSliderBackgroundColor = theme.getColor(scrollbarSliderBackground);
	if (scrollbarSliderBackgroundColor) {
		collector.addRule(`
1653 1654 1655
			.monaco-workbench .pane-body.integrated-terminal .find-focused .xterm .xterm-viewport,
			.monaco-workbench .pane-body.integrated-terminal .xterm.focus .xterm-viewport,
			.monaco-workbench .pane-body.integrated-terminal .xterm:focus .xterm-viewport,
D
Daniel Imms 已提交
1656 1657 1658
			.monaco-workbench .pane-body.integrated-terminal .xterm:hover .xterm-viewport { background-color: ${scrollbarSliderBackgroundColor} !important; }
			.monaco-workbench .pane-body.integrated-terminal .xterm-viewport { scrollbar-color: ${scrollbarSliderBackgroundColor} transparent; }
		`);
1659 1660 1661 1662
	}

	const scrollbarSliderHoverBackgroundColor = theme.getColor(scrollbarSliderHoverBackground);
	if (scrollbarSliderHoverBackgroundColor) {
D
Daniel Imms 已提交
1663 1664 1665 1666
		collector.addRule(`
			.monaco-workbench .pane-body.integrated-terminal .xterm .xterm-viewport::-webkit-scrollbar-thumb:hover { background-color: ${scrollbarSliderHoverBackgroundColor}; }
			.monaco-workbench .pane-body.integrated-terminal .xterm-viewport:hover { scrollbar-color: ${scrollbarSliderHoverBackgroundColor} transparent; }
		`);
1667
	}
1668 1669 1670

	const scrollbarSliderActiveBackgroundColor = theme.getColor(scrollbarSliderActiveBackground);
	if (scrollbarSliderActiveBackgroundColor) {
1671
		collector.addRule(`.monaco-workbench .pane-body.integrated-terminal .xterm .xterm-viewport::-webkit-scrollbar-thumb:active { background-color: ${scrollbarSliderActiveBackgroundColor}; }`);
1672
	}
1673
});