terminalInstance.ts 59.9 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';
D
Daniel Imms 已提交
23
import { IStorageService, StorageScope } 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';
28
import { IShellLaunchConfig, ITerminalDimensions, ITerminalProcessManager, KEYBINDING_CONTEXT_TERMINAL_TEXT_SELECTED, NEVER_MEASURE_RENDER_TIME_STORAGE_KEY, ProcessState, TERMINAL_VIEW_ID, IWindowsShellHelper, SHELL_PATH_INVALID_EXIT_CODE, SHELL_PATH_DIRECTORY_EXIT_CODE, SHELL_CWD_INVALID_EXIT_CODE, KEYBINDING_CONTEXT_TERMINAL_A11Y_TREE_FOCUS, INavigationMode, TitleEventSource, LEGACY_CONSOLE_MODE_EXIT_CODE, DEFAULT_COMMANDS_TO_SKIP_SHELL } 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';
33
import { ITerminalInstanceService, ITerminalInstance, TerminalShellType, WindowsShellType, ITerminalBeforeHandleLinkEvent } from 'vs/workbench/contrib/terminal/browser/terminal';
34
import { TerminalProcessManager } from 'vs/workbench/contrib/terminal/browser/terminalProcessManager';
35
import { Terminal as XTermTerminal, IBuffer, ITerminalAddon } from 'xterm';
D
Daniel Imms 已提交
36
import { SearchAddon, ISearchOptions } from 'xterm-addon-search';
D
Daniel Imms 已提交
37
import { Unicode11Addon } from 'xterm-addon-unicode11';
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 44
import { EnvironmentVariableInfoWidget } from 'vs/workbench/contrib/terminal/browser/widgets/environmentVariableInfoWidget';
import { IEnvironmentVariableInfo } from 'vs/workbench/contrib/terminal/common/environmentVariable';
D
Daniel Imms 已提交
45

46 47
// How long in milliseconds should an average frame take to render for a notification to appear
// which suggests the fallback DOM-based renderer
48 49
const SLOW_CANVAS_RENDER_THRESHOLD = 50;
const NUMBER_OF_FRAMES_TO_MEASURE = 20;
50

51 52
let xtermConstructor: Promise<typeof XTermTerminal> | undefined;

53 54 55 56 57 58 59 60 61 62
interface ICanvasDimensions {
	width: number;
	height: number;
}

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

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

66 67
	private static _lastKnownCanvasDimensions: ICanvasDimensions | undefined;
	private static _lastKnownGridDimensions: IGridDimensions | undefined;
D
Daniel Imms 已提交
68 69
	private static _idCounter = 1;

70
	private _processManager!: ITerminalProcessManager;
71
	private _pressAnyKeyToCloseListener: IDisposable | undefined;
D
Daniel Imms 已提交
72

73
	private _id: number;
D
Daniel Imms 已提交
74
	private _isExiting: boolean;
K
Kai Wood 已提交
75
	private _hadFocusOnExit: boolean;
D
Daniel Imms 已提交
76
	private _isVisible: boolean;
77
	private _isDisposed: boolean;
D
Daniel Imms 已提交
78
	private _exitCode: number | undefined;
D
Daniel Imms 已提交
79
	private _skipTerminalCommands: string[];
80
	private _shellType: TerminalShellType;
81
	private _title: string = '';
D
Daniel Imms 已提交
82
	private _wrapperElement: (HTMLElement & { xterm?: XTermTerminal }) | undefined;
83
	private _xterm: XTermTerminal | undefined;
84
	private _xtermCore: XTermCore | undefined;
D
Daniel Imms 已提交
85
	private _xtermSearch: SearchAddon | undefined;
D
Daniel Imms 已提交
86
	private _xtermUnicode11: Unicode11Addon | undefined;
87
	private _xtermElement: HTMLDivElement | undefined;
88
	private _terminalHasTextContextKey: IContextKey<boolean>;
89
	private _terminalA11yTreeFocusContextKey: IContextKey<boolean>;
90 91
	private _cols: number = 0;
	private _rows: number = 0;
D
Daniel Imms 已提交
92
	private _dimensionsOverride: ITerminalDimensions | undefined;
93
	private _windowsShellHelper: IWindowsShellHelper | undefined;
94
	private _xtermReadyPromise: Promise<XTermTerminal>;
95
	private _titleReadyPromise: Promise<string>;
96
	private _titleReadyComplete: ((title: string) => any) | undefined;
D
Daniel Imms 已提交
97

98
	private _messageTitleDisposable: IDisposable | undefined;
99

100
	private _widgetManager: TerminalWidgetManager = this._instantiationService.createInstance(TerminalWidgetManager);
101
	private _linkManager: TerminalLinkManager | undefined;
102
	private _environmentInfo: { widget: EnvironmentVariableInfoWidget, disposable: IDisposable } | undefined;
103
	private _commandTrackerAddon: CommandTrackerAddon | undefined;
104
	private _navigationModeAddon: INavigationMode & ITerminalAddon | undefined;
105

106
	public disableLayout: boolean;
107
	public get id(): number { return this._id; }
108 109 110 111 112 113 114 115 116 117 118 119
	public get cols(): number {
		if (this._dimensionsOverride && this._dimensionsOverride.cols) {
			return Math.min(Math.max(this._dimensionsOverride.cols, 2), this._cols);
		}
		return this._cols;
	}
	public get rows(): number {
		if (this._dimensionsOverride && this._dimensionsOverride.rows) {
			return Math.min(Math.max(this._dimensionsOverride.rows, 2), this._rows);
		}
		return this._rows;
	}
120 121
	public get maxCols(): number { return this._cols; }
	public get maxRows(): number { return this._rows; }
D
Daniel Imms 已提交
122
	// TODO: Ideally processId would be merged into processReady
D
Daniel Imms 已提交
123
	public get processId(): number | undefined { return this._processManager.shellProcessId; }
124
	// TODO: How does this work with detached processes?
D
Daniel Imms 已提交
125
	// TODO: Should this be an event as it can fire twice?
D
Daniel Imms 已提交
126
	public get processReady(): Promise<void> { return this._processManager.ptyProcessReady; }
D
Daniel Imms 已提交
127
	public get exitCode(): number | undefined { return this._exitCode; }
D
Daniel Imms 已提交
128
	public get title(): string { return this._title; }
K
Kai Wood 已提交
129
	public get hadFocusOnExit(): boolean { return this._hadFocusOnExit; }
130
	public get isTitleSetByProcess(): boolean { return !!this._messageTitleDisposable; }
D
Daniel Imms 已提交
131
	public get shellLaunchConfig(): IShellLaunchConfig { return this._shellLaunchConfig; }
132
	public get shellType(): TerminalShellType { return this._shellType; }
133
	public get commandTracker(): CommandTrackerAddon | undefined { return this._commandTrackerAddon; }
134
	public get navigationMode(): INavigationMode | undefined { return this._navigationModeAddon; }
135

136 137
	private readonly _onExit = new Emitter<number | undefined>();
	public get onExit(): Event<number | undefined> { return this._onExit.event; }
138
	private readonly _onDisposed = new Emitter<ITerminalInstance>();
D
Daniel Imms 已提交
139
	public get onDisposed(): Event<ITerminalInstance> { return this._onDisposed.event; }
140
	private readonly _onFocused = new Emitter<ITerminalInstance>();
D
Daniel Imms 已提交
141
	public get onFocused(): Event<ITerminalInstance> { return this._onFocused.event; }
142
	private readonly _onProcessIdReady = new Emitter<ITerminalInstance>();
D
Daniel Imms 已提交
143
	public get onProcessIdReady(): Event<ITerminalInstance> { return this._onProcessIdReady.event; }
144
	private readonly _onTitleChanged = new Emitter<ITerminalInstance>();
145
	public get onTitleChanged(): Event<ITerminalInstance> { return this._onTitleChanged.event; }
146
	private readonly _onData = new Emitter<string>();
D
Daniel Imms 已提交
147
	public get onData(): Event<string> { return this._onData.event; }
148
	private readonly _onLineData = new Emitter<string>();
D
Daniel Imms 已提交
149
	public get onLineData(): Event<string> { return this._onLineData.event; }
150
	private readonly _onRequestExtHostProcess = new Emitter<ITerminalInstance>();
151
	public get onRequestExtHostProcess(): Event<ITerminalInstance> { return this._onRequestExtHostProcess.event; }
152
	private readonly _onDimensionsChanged = new Emitter<void>();
D
Daniel Imms 已提交
153
	public get onDimensionsChanged(): Event<void> { return this._onDimensionsChanged.event; }
154 155
	private readonly _onMaximumDimensionsChanged = new Emitter<void>();
	public get onMaximumDimensionsChanged(): Event<void> { return this._onMaximumDimensionsChanged.event; }
156
	private readonly _onFocus = new Emitter<ITerminalInstance>();
157
	public get onFocus(): Event<ITerminalInstance> { return this._onFocus.event; }
D
Daniel Imms 已提交
158
	private readonly _onBeforeHandleLink = new Emitter<ITerminalBeforeHandleLinkEvent>();
159
	public get onBeforeHandleLink(): Event<ITerminalBeforeHandleLinkEvent> { return this._onBeforeHandleLink.event; }
D
Daniel Imms 已提交
160

161
	public constructor(
162
		private readonly _terminalFocusContextKey: IContextKey<boolean>,
163
		private readonly _terminalShellTypeContextKey: IContextKey<string>,
164
		private readonly _configHelper: TerminalConfigHelper,
165
		private _container: HTMLElement | undefined,
166
		private _shellLaunchConfig: IShellLaunchConfig,
167
		@ITerminalInstanceService private readonly _terminalInstanceService: ITerminalInstanceService,
168 169
		@IContextKeyService private readonly _contextKeyService: IContextKeyService,
		@IKeybindingService private readonly _keybindingService: IKeybindingService,
170
		@INotificationService private readonly _notificationService: INotificationService,
171
		@IViewsService private readonly _viewsService: IViewsService,
172 173 174
		@IInstantiationService private readonly _instantiationService: IInstantiationService,
		@IClipboardService private readonly _clipboardService: IClipboardService,
		@IThemeService private readonly _themeService: IThemeService,
D
Daniel Imms 已提交
175
		@IConfigurationService private readonly _configurationService: IConfigurationService,
176
		@ILogService private readonly _logService: ILogService,
177
		@IStorageService private readonly _storageService: IStorageService,
178
		@IAccessibilityService private readonly _accessibilityService: IAccessibilityService,
179
		@IViewDescriptorService private readonly _viewDescriptorService: IViewDescriptorService
180
	) {
181 182
		super();

D
Daniel Imms 已提交
183
		this._skipTerminalCommands = [];
D
Daniel Imms 已提交
184
		this._isExiting = false;
K
Kai Wood 已提交
185
		this._hadFocusOnExit = false;
D
Daniel Imms 已提交
186
		this._isVisible = false;
187
		this._isDisposed = false;
D
Daniel Imms 已提交
188
		this._id = TerminalInstance._idCounter++;
189 190 191 192 193

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

194
		this._terminalHasTextContextKey = KEYBINDING_CONTEXT_TERMINAL_TEXT_SELECTED.bindTo(this._contextKeyService);
195
		this._terminalA11yTreeFocusContextKey = KEYBINDING_CONTEXT_TERMINAL_A11Y_TREE_FOCUS.bindTo(this._contextKeyService);
196
		this.disableLayout = false;
D
Daniel Imms 已提交
197

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

200
		this._initDimensions();
D
Daniel Imms 已提交
201
		this._createProcess();
D
Daniel Imms 已提交
202

D
Daniel Imms 已提交
203 204 205 206
		this._xtermReadyPromise = this._createXterm();
		this._xtermReadyPromise.then(() => {
			// Only attach xterm.js to the DOM if the terminal panel has been opened before.
			if (_container) {
207
				this._attachToElement(_container);
D
Daniel Imms 已提交
208 209
			}
		});
210

211
		this.addDisposable(this._configurationService.onDidChangeConfiguration(e => {
212
			if (e.affectsConfiguration('terminal.integrated') || e.affectsConfiguration('editor.fastScrollSensitivity') || e.affectsConfiguration('editor.mouseWheelScrollSensitivity')) {
213
				this.updateConfig();
214 215 216 217
				// 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);
218
			}
D
Daniel Imms 已提交
219 220 221
			if (e.affectsConfiguration('terminal.integrated.unicodeVersion')) {
				this._updateUnicodeVersion();
			}
222 223 224
			if (e.affectsConfiguration('editor.accessibilitySupport')) {
				this.updateAccessibilitySupport();
			}
225
		}));
226 227
	}

228 229
	public addDisposable(disposable: IDisposable): void {
		this._register(disposable);
D
Daniel Imms 已提交
230 231
	}

232 233 234 235 236 237
	private _initDimensions(): void {
		// The terminal panel needs to have been created
		if (!this._container) {
			return;
		}

D
Daniel Imms 已提交
238
		const computedStyle = window.getComputedStyle(this._container.parentElement!);
239 240 241 242 243 244 245 246 247
		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 已提交
248
	 * @return The terminal's width if it requires a layout.
249
	 */
D
Daniel Imms 已提交
250
	private _evaluateColsAndRows(width: number, height: number): number | null {
D
Daniel Imms 已提交
251 252
		// Ignore if dimensions are undefined or 0
		if (!width || !height) {
253
			this._setLastKnownColsAndRows();
D
Daniel Imms 已提交
254 255 256
			return null;
		}

257 258
		const dimension = this._getDimension(width, height);
		if (!dimension) {
259
			this._setLastKnownColsAndRows();
260 261
			return null;
		}
D
Daniel Imms 已提交
262

263
		const font = this._configHelper.getFont(this._xtermCore);
D
Daniel Imms 已提交
264
		if (!font.charWidth || !font.charHeight) {
265
			this._setLastKnownColsAndRows();
D
Daniel Imms 已提交
266 267
			return null;
		}
268

D
Daniel Imms 已提交
269 270 271 272 273
		// 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;
274

275
		const scaledCharWidth = font.charWidth * window.devicePixelRatio + font.letterSpacing;
276
		const newCols = Math.max(Math.floor(scaledWidthAvailable / scaledCharWidth), 1);
D
Daniel Imms 已提交
277 278

		const scaledHeightAvailable = dimension.height * window.devicePixelRatio;
279 280
		const scaledCharHeight = Math.ceil(font.charHeight * window.devicePixelRatio);
		const scaledLineHeight = Math.floor(scaledCharHeight * font.lineHeight);
281 282 283 284 285
		const newRows = Math.max(Math.floor(scaledHeightAvailable / scaledLineHeight), 1);

		if (this._cols !== newCols || this._rows !== newRows) {
			this._cols = newCols;
			this._rows = newRows;
286
			this._fireMaximumDimensionsChanged();
287
		}
288

289 290
		return dimension.width;
	}
291

292 293 294 295 296 297 298
	private _setLastKnownColsAndRows(): void {
		if (TerminalInstance._lastKnownGridDimensions) {
			this._cols = TerminalInstance._lastKnownGridDimensions.cols;
			this._rows = TerminalInstance._lastKnownGridDimensions.rows;
		}
	}

299 300 301 302
	@debounce(50)
	private _fireMaximumDimensionsChanged(): void {
		this._onMaximumDimensionsChanged.fire();
	}
303

304
	private _getDimension(width: number, height: number): ICanvasDimensions | undefined {
305
		// The font needs to have been initialized
306
		const font = this._configHelper.getFont(this._xtermCore);
307
		if (!font || !font.charWidth || !font.charHeight) {
308
			return undefined;
309 310 311
		}

		// The panel is minimized
312
		if (!this._isVisible) {
313
			return TerminalInstance._lastKnownCanvasDimensions;
314 315
		}

316
		if (!this._wrapperElement) {
317
			return undefined;
318 319 320
		}

		const wrapperElementStyle = getComputedStyle(this._wrapperElement);
D
Daniel Imms 已提交
321 322 323
		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);
324

D
Daniel Imms 已提交
325
		const innerWidth = width - marginLeft - marginRight;
326
		const innerHeight = height - bottom - 1;
327

328 329
		TerminalInstance._lastKnownCanvasDimensions = new dom.Dimension(innerWidth, innerHeight);
		return TerminalInstance._lastKnownCanvasDimensions;
330 331
	}

332 333 334 335 336 337 338 339 340 341 342 343 344 345
	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;
	}

346 347 348
	/**
	 * Create xterm.js instance and attach data listeners.
	 */
349
	protected async _createXterm(): Promise<XTermTerminal> {
350
		const Terminal = await this._getXtermConstructor();
351
		const font = this._configHelper.getFont(undefined, true);
D
Daniel Imms 已提交
352
		const config = this._configHelper.config;
353 354
		const editorOptions = this._configurationService.getValue<IEditorOptions>('editor');

355
		const xterm = new Terminal({
D
Daniel Imms 已提交
356
			scrollback: config.scrollback,
357
			theme: this._getXtermTheme(),
358
			drawBoldTextInBrightColors: config.drawBoldTextInBrightColors,
359
			fontFamily: font.fontFamily,
D
Daniel Imms 已提交
360 361
			fontWeight: config.fontWeight,
			fontWeightBold: config.fontWeightBold,
362
			fontSize: font.fontSize,
363
			letterSpacing: font.letterSpacing,
364
			lineHeight: font.lineHeight,
365
			minimumContrastRatio: config.minimumContrastRatio,
D
Daniel Imms 已提交
366 367
			bellStyle: config.enableBell ? 'sound' : 'none',
			macOptionIsMeta: config.macOptionIsMeta,
368
			macOptionClickForcesSelection: config.macOptionClickForcesSelection,
D
Daniel Imms 已提交
369
			rightClickSelectsWord: config.rightClickBehavior === 'selectWord',
D
Daniel Imms 已提交
370
			fastScrollModifier: 'alt',
371 372
			fastScrollSensitivity: editorOptions.fastScrollSensitivity,
			scrollSensitivity: editorOptions.mouseWheelScrollSensitivity,
373
			rendererType: config.rendererType === 'auto' || config.rendererType === 'experimentalWebgl' ? 'canvas' : config.rendererType,
374
			wordSeparator: config.wordSeparators
375
		});
376
		this._xterm = xterm;
377
		this._xtermCore = (xterm as any)._core as XTermCore;
D
Daniel Imms 已提交
378
		this._updateUnicodeVersion();
379
		this.updateAccessibilitySupport();
D
Daniel Imms 已提交
380 381
		this._terminalInstanceService.getXtermSearchConstructor().then(Addon => {
			this._xtermSearch = new Addon();
382
			xterm.loadAddon(this._xtermSearch);
D
Daniel Imms 已提交
383
		});
384 385 386
		if (this._shellLaunchConfig.initialText) {
			this._xterm.writeln(this._shellLaunchConfig.initialText);
		}
D
Daniel Imms 已提交
387 388
		this._xterm.onLineFeed(() => this._onLineFeed());
		this._xterm.onKey(e => this._onKey(e.key, e.domEvent));
389
		this._xterm.onSelectionChange(async () => this._onSelectionChange());
390

D
Daniel Imms 已提交
391
		this._processManager.onProcessData(data => this._onProcessData(data));
392
		this._xterm.onData(data => this._processManager.write(data));
D
Daniel Imms 已提交
393
		this.processReady.then(async () => {
394 395
			if (this._linkManager) {
				this._linkManager.processCwd = await this._processManager.getInitialCwd();
396
			}
D
Daniel Imms 已提交
397 398 399 400 401 402 403 404 405
		});
		// 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 已提交
406
				xterm.parser.registerCsiHandler({ final: 'H' }, () => {
D
Daniel Imms 已提交
407 408 409 410
					this._onCursorMove();
					return false;
				});
			}
411
			this._linkManager = this._instantiationService.createInstance(TerminalLinkManager, xterm, this._processManager!, this._configHelper);
412
			this._linkManager.onBeforeHandleLink(e => {
413 414 415
				e.terminal = this;
				this._onBeforeHandleLink.fire(e);
			});
D
Daniel Imms 已提交
416
		});
D
Daniel Imms 已提交
417

418 419
		this._commandTrackerAddon = new CommandTrackerAddon();
		this._xterm.loadAddon(this._commandTrackerAddon);
M
Martin Aeschlimann 已提交
420
		this._register(this._themeService.onDidColorThemeChange(theme => this._updateTheme(xterm, theme)));
421 422 423 424 425
		this._register(this._viewDescriptorService.onDidChangeLocation(({ views }) => {
			if (views.some(v => v.id === TERMINAL_VIEW_ID)) {
				this._updateTheme(xterm);
			}
		}));
426 427

		return xterm;
428 429
	}

D
Daniel Imms 已提交
430 431 432 433 434
	public reattachToElement(container: HTMLElement): void {
		if (!this._wrapperElement) {
			throw new Error('The terminal instance has not been attached to a container yet');
		}

435
		this._wrapperElement.parentNode?.removeChild(this._wrapperElement);
D
Daniel Imms 已提交
436 437 438 439
		this._container = container;
		this._container.appendChild(this._wrapperElement);
	}

440
	public attachToElement(container: HTMLElement): void {
441 442 443 444 445 446 447 448 449 450 451 452
		// 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
453
		this._container?.removeChild(this._wrapperElement);
454 455 456 457
		this._container = container;
		this._container.appendChild(this._wrapperElement);
	}

458 459
	public async _attachToElement(container: HTMLElement): Promise<void> {
		const xterm = await this._xtermReadyPromise;
460

461 462 463
		if (this._wrapperElement) {
			throw new Error('The terminal instance has already been attached to a container');
		}
464

465 466 467 468
		this._container = container;
		this._wrapperElement = document.createElement('div');
		dom.addClass(this._wrapperElement, 'terminal-wrapper');
		this._xtermElement = document.createElement('div');
D
Daniel Imms 已提交
469

470 471 472 473 474 475 476 477 478 479 480 481 482 483 484
		// 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') {
			this._terminalInstanceService.getXtermWebglConstructor().then(Addon => {
				xterm.loadAddon(new Addon());
			});
		}

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

486 487

		xterm.textarea.setAttribute('aria-label', nls.localize('terminalTextBoxAriaLabel', "Terminal {0}", this._id));
488 489 490 491 492
		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;
493 494
			}

495 496
			const standardKeyboardEvent = new StandardKeyboardEvent(event);
			const resolveResult = this._keybindingService.softDispatch(standardKeyboardEvent, standardKeyboardEvent.target);
497

498 499 500
			// 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
501 502
			const isValidChord = resolveResult?.enterChord && this._configHelper.config.allowChords && event.key !== 'Escape';
			if (this._keybindingService.inChordMode || isValidChord) {
503 504 505
				event.preventDefault();
				return false;
			}
D
Daniel Imms 已提交
506

507 508 509
			// Skip processing by xterm.js of keyboard events that resolve to commands described
			// within commandsToSkipShell
			if (resolveResult && this._skipTerminalCommands.some(k => k === resolveResult.commandId)) {
510 511 512
				event.preventDefault();
				return false;
			}
D
Daniel Imms 已提交
513

514
			// Skip processing by xterm.js of keyboard events that match menu bar mnemonics
D
Daniel Imms 已提交
515
			if (this._configHelper.config.allowMnemonics && !platform.isMacintosh && event.altKey) {
516 517 518
				return false;
			}

519 520 521 522
			// If tab focus mode is on, tab is not passed to the terminal
			if (TabFocus.getTabFocusMode() && event.keyCode === 9) {
				return false;
			}
523

524 525 526 527 528
			// 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;
			}
529

530 531 532 533 534 535 536 537 538 539
			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 已提交
540
			});
541
		}));
D
Daniel Imms 已提交
542

543 544 545 546 547 548
		// 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 已提交
549

550 551 552 553 554 555 556 557
		const xtermHelper: HTMLElement = <HTMLElement>xterm.element.querySelector('.xterm-helpers');
		const focusTrap: HTMLElement = document.createElement('div');
		focusTrap.setAttribute('tabindex', '0');
		dom.addClass(focusTrap, 'focus-trap');
		this._register(dom.addDisposableListener(focusTrap, 'focus', () => {
			let currentElement = focusTrap;
			while (!dom.hasClass(currentElement, 'part')) {
				currentElement = currentElement.parentElement!;
D
Daniel Imms 已提交
558
			}
D
Fix NPE  
Daniel Imms 已提交
559 560
			const hidePanelElement = currentElement.querySelector<HTMLElement>('.hide-panel-action');
			hidePanelElement?.focus();
561 562 563 564 565
		}));
		xtermHelper.insertBefore(focusTrap, xterm.textarea);

		this._register(dom.addDisposableListener(xterm.textarea, 'focus', () => {
			this._terminalFocusContextKey.set(true);
D
Daniel Imms 已提交
566 567 568 569 570
			if (this.shellType) {
				this._terminalShellTypeContextKey.set(this.shellType.toString());
			} else {
				this._terminalShellTypeContextKey.reset();
			}
571 572 573 574 575 576 577 578
			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 已提交
579 580 581 582 583
			if (this.shellType) {
				this._terminalShellTypeContextKey.set(this.shellType.toString());
			} else {
				this._terminalShellTypeContextKey.reset();
			}
584 585 586 587 588 589
		}));
		this._register(dom.addDisposableListener(xterm.element, 'blur', () => {
			this._terminalFocusContextKey.reset();
			this._refreshSelectionContextKey();
		}));

590
		this._widgetManager.attachToElement(xterm.element);
591
		this._processManager.onProcessReady(() => this._linkManager?.setWidgetManager(this._widgetManager));
592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609

		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();
		}
610 611
	}

612
	private async _measureRenderTime(): Promise<void> {
613
		await this._xtermReadyPromise;
D
Daniel Imms 已提交
614
		const frameTimes: number[] = [];
615
		const textRenderLayer = this._xtermCore!._renderService._renderer._renderLayers[0];
D
Daniel Imms 已提交
616
		const originalOnGridChanged = textRenderLayer.onGridChanged;
617 618 619 620 621

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

D
Daniel Imms 已提交
622
			const medianTime = frameTimes.sort((a, b) => a - b)[Math.floor(frameTimes.length / 2)];
623
			if (medianTime > SLOW_CANVAS_RENDER_THRESHOLD) {
D
Daniel Imms 已提交
624 625 626
				const promptChoices: IPromptChoice[] = [
					{
						label: nls.localize('yes', "Yes"),
627
						run: () => this._configurationService.updateValue('terminal.integrated.rendererType', 'dom', ConfigurationTarget.USER)
D
Daniel Imms 已提交
628 629
					} as IPromptChoice,
					{
630 631
						label: nls.localize('no', "No"),
						run: () => { }
D
Daniel Imms 已提交
632 633 634 635
					} as IPromptChoice,
					{
						label: nls.localize('dontShowAgain', "Don't Show Again"),
						isSecondary: true,
B
Benjamin Pasero 已提交
636
						run: () => this._storageService.store(NEVER_MEASURE_RENDER_TIME_STORAGE_KEY, true, StorageScope.GLOBAL)
D
Daniel Imms 已提交
637 638 639 640
					} as IPromptChoice
				];
				this._notificationService.prompt(
					Severity.Warning,
D
Daniel Imms 已提交
641
					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 已提交
642 643 644
					promptChoices
				);
			}
645
		};
646

647 648 649 650 651 652 653 654 655
		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 已提交
656 657 658
		};
	}

D
Daniel Imms 已提交
659
	public registerLinkMatcher(regex: RegExp, handler: (url: string) => void, matchIndex?: number, validationCallback?: (uri: string, callback: (isValid: boolean) => void) => void): number {
660
		return this._linkManager!.registerCustomLinkHandler(regex, (_, url) => handler(url), matchIndex, validationCallback);
661 662 663
	}

	public deregisterLinkMatcher(linkMatcherId: number): void {
664
		this._xtermReadyPromise.then(xterm => xterm.deregisterLinkMatcher(linkMatcherId));
665 666
	}

667
	public hasSelection(): boolean {
668
		return this._xterm ? this._xterm.hasSelection() : false;
669 670
	}

671
	public async copySelection(): Promise<void> {
672
		const xterm = await this._xtermReadyPromise;
D
Daniel Imms 已提交
673
		if (this.hasSelection()) {
674
			await this._clipboardService.writeText(xterm.getSelection());
D
Daniel Imms 已提交
675
		} else {
676
			this._notificationService.warn(nls.localize('terminal.integrated.copySelection.noSelection', 'The terminal has no selection to copy'));
D
Daniel Imms 已提交
677
		}
D
Daniel Imms 已提交
678 679
	}

D
Daniel Imms 已提交
680
	public get selection(): string | undefined {
681
		return this._xterm && this.hasSelection() ? this._xterm.getSelection() : undefined;
682 683
	}

684
	public clearSelection(): void {
685
		this._xterm?.clearSelection();
686 687 688
	}

	public selectAll(): void {
689
		// Focus here to ensure the terminal context key is set
690 691
		this._xterm?.focus();
		this._xterm?.selectAll();
692 693
	}

694
	public findNext(term: string, searchOptions: ISearchOptions): boolean {
D
Daniel Imms 已提交
695 696 697
		if (!this._xtermSearch) {
			return false;
		}
D
Daniel Imms 已提交
698
		return this._xtermSearch.findNext(term, searchOptions);
R
rebornix 已提交
699 700
	}

701
	public findPrevious(term: string, searchOptions: ISearchOptions): boolean {
D
Daniel Imms 已提交
702 703 704
		if (!this._xtermSearch) {
			return false;
		}
D
Daniel Imms 已提交
705
		return this._xtermSearch.findPrevious(term, searchOptions);
R
rebornix 已提交
706 707
	}

708
	public notifyFindWidgetFocusChanged(isFocused: boolean): void {
709 710 711
		if (!this._xterm) {
			return;
		}
712 713
		const terminalFocused = !isFocused && (document.activeElement === this._xterm.textarea || document.activeElement === this._xterm.element);
		this._terminalFocusContextKey.set(terminalFocused);
714 715
	}

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

719
		dispose(this._windowsShellHelper);
720
		this._windowsShellHelper = undefined;
721 722
		dispose(this._linkManager);
		this._linkManager = undefined;
723 724
		dispose(this._commandTrackerAddon);
		this._commandTrackerAddon = undefined;
725
		dispose(this._widgetManager);
726

K
Kai Wood 已提交
727
		if (this._xterm && this._xterm.element) {
D
Daniel Imms 已提交
728
			this._hadFocusOnExit = dom.hasClass(this._xterm.element, 'focus');
K
Kai Wood 已提交
729
		}
D
Daniel Imms 已提交
730
		if (this._wrapperElement) {
D
Daniel Imms 已提交
731 732
			if (this._wrapperElement.xterm) {
				this._wrapperElement.xterm = undefined;
733
			}
734
			if (this._wrapperElement.parentElement && this._container) {
D
Daniel Imms 已提交
735 736
				this._container.removeChild(this._wrapperElement);
			}
D
Daniel Imms 已提交
737
		}
D
Daniel Imms 已提交
738
		if (this._xterm) {
739
			const buffer = this._xterm.buffer;
D
Daniel Imms 已提交
740
			this._sendLineData(buffer.active, buffer.active.baseY + buffer.active.cursorY);
D
Daniel Imms 已提交
741
			this._xterm.dispose();
D
Daniel Imms 已提交
742
		}
743 744 745 746 747 748

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

749
		this._processManager.dispose(immediate);
750 751 752
		// Process manager dispose/shutdown doesn't fire process exit, trigger with undefined if it
		// hasn't happened yet
		this._onProcessExit(undefined);
753

754 755 756 757
		if (!this._isDisposed) {
			this._isDisposed = true;
			this._onDisposed.fire(this);
		}
758
		super.dispose();
D
Daniel Imms 已提交
759 760
	}

761
	public forceRedraw(): void {
762 763 764
		if (!this._xterm) {
			return;
		}
765 766 767
		this._xterm.refresh(0, this._xterm.rows - 1);
	}

D
Daniel Imms 已提交
768
	public focus(force?: boolean): void {
769 770 771
		if (!this._xterm) {
			return;
		}
D
Daniel Imms 已提交
772 773 774 775 776
		const selection = window.getSelection();
		if (!selection) {
			return;
		}
		const text = selection.toString();
777 778 779 780 781
		if (!text || force) {
			this._xterm.focus();
		}
	}

782 783 784
	public async focusWhenReady(force?: boolean): Promise<void> {
		await this._xtermReadyPromise;
		this.focus(force);
D
Daniel Imms 已提交
785 786
	}

787
	public async paste(): Promise<void> {
788 789 790
		if (!this._xterm) {
			return;
		}
D
Daniel Imms 已提交
791
		this.focus();
792
		this._xterm.paste(await this._clipboardService.readText());
D
Daniel Imms 已提交
793
	}
D
Daniel Imms 已提交
794

795
	public async sendText(text: string, addNewLine: boolean): Promise<void> {
D
Daniel Imms 已提交
796 797 798 799 800 801
		// Normalize line endings to 'enter' press.
		text = text.replace(TerminalInstance.EOL_REGEX, '\r');
		if (addNewLine && text.substr(text.length - 1) !== '\r') {
			text += '\r';
		}

802
		// Send it to the process
803 804
		await this._processManager.ptyProcessReady;
		this._processManager.write(text);
D
Daniel Imms 已提交
805
	}
806 807

	public setVisible(visible: boolean): void {
D
Daniel Imms 已提交
808 809
		this._isVisible = visible;
		if (this._wrapperElement) {
D
Daniel Imms 已提交
810
			dom.toggleClass(this._wrapperElement, 'active', visible);
811
		}
812
		if (visible && this._xterm && this._xtermCore) {
813 814 815 816
			// 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 已提交
817 818
			// This can likely be removed after https://github.com/xtermjs/xterm.js/issues/291 is
			// fixed upstream.
D
Daniel Imms 已提交
819
			this._xtermCore._onScroll.fire(this._xterm.buffer.active.viewportY);
820
			if (this._container && this._container.parentElement) {
B
Benjamin Pasero 已提交
821 822 823 824
				// 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 已提交
825
				const computedStyle = window.getComputedStyle(this._container.parentElement);
B
Benjamin Pasero 已提交
826 827
				const width = parseInt(computedStyle.getPropertyValue('width').replace('px', ''), 10);
				const height = parseInt(computedStyle.getPropertyValue('height').replace('px', ''), 10);
828
				this.layout(new dom.Dimension(width, height));
829 830 831 832
				// 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.
				setTimeout(() => this.layout(new dom.Dimension(width, height)), 0);
B
Benjamin Pasero 已提交
833
			}
834
		}
835 836
	}

837
	public scrollDownLine(): void {
838
		this._xterm?.scrollLines(1);
D
Daniel Imms 已提交
839 840
	}

841
	public scrollDownPage(): void {
842
		this._xterm?.scrollPages(1);
843 844
	}

845
	public scrollToBottom(): void {
846
		this._xterm?.scrollToBottom();
847 848
	}

849
	public scrollUpLine(): void {
850
		this._xterm?.scrollLines(-1);
D
Daniel Imms 已提交
851
	}
852

853
	public scrollUpPage(): void {
854
		this._xterm?.scrollPages(-1);
855 856
	}

857
	public scrollToTop(): void {
858
		this._xterm?.scrollToTop();
859 860
	}

D
Daniel Imms 已提交
861
	public clear(): void {
862
		this._xterm?.clear();
D
Daniel Imms 已提交
863 864
	}

865
	private _refreshSelectionContextKey() {
866
		const isActive = !!this._viewsService.getActiveViewWithId(TERMINAL_VIEW_ID);
D
Daniel Imms 已提交
867
		this._terminalHasTextContextKey.set(isActive && this.hasSelection());
868 869
	}

870
	protected _createProcess(): void {
871
		this._processManager = this._instantiationService.createInstance(TerminalProcessManager, this._id, this._configHelper);
D
Daniel Imms 已提交
872
		this._processManager.onProcessReady(() => this._onProcessIdReady.fire(this));
D
Daniel Imms 已提交
873
		this._processManager.onProcessExit(exitCode => this._onProcessExit(exitCode));
D
Daniel Imms 已提交
874
		this._processManager.onProcessData(data => this._onData.fire(data));
D
Daniel Imms 已提交
875
		this._processManager.onProcessOverrideDimensions(e => this.setDimensions(e));
876
		this._processManager.onProcessResolvedShellLaunchConfig(e => this._setResolvedShellLaunchConfig(e));
D
Daniel Imms 已提交
877
		this._processManager.onEnvironmentVariableInfoChanged(e => this._onEnvironmentVariableInfoChanged(e));
D
Daniel Imms 已提交
878

879
		if (this._shellLaunchConfig.name) {
880
			this.setTitle(this._shellLaunchConfig.name, TitleEventSource.Api);
A
Amy Qiu 已提交
881
		} else {
882
			// Only listen for process title changes when a name is not provided
883 884 885 886 887 888 889 890 891 892 893 894 895
			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));
			}
896
		}
D
Daniel Imms 已提交
897 898 899

		if (platform.isWindows) {
			this._processManager.ptyProcessReady.then(() => {
900
				if (this._processManager.remoteAuthority) {
D
Daniel Imms 已提交
901 902
					return;
				}
903
				this._xtermReadyPromise.then(xterm => {
D
Daniel Imms 已提交
904
					if (!this._isDisposed && this._processManager && this._processManager.shellProcessId) {
905 906 907 908 909 910 911
						this._windowsShellHelper = this._terminalInstanceService.createWindowsShellHelper(this._processManager.shellProcessId, xterm);
						this._windowsShellHelper.onShellNameChange(title => {
							this.setShellType(this.getShellType(title));
							if (this.isTitleSetByProcess) {
								this.setTitle(title, TitleEventSource.Process);
							}
						});
D
Daniel Imms 已提交
912 913 914 915
					}
				});
			});
		}
916 917 918 919

		// Create the process asynchronously to allow the terminal's container
		// to be created so dimensions are accurate
		setTimeout(() => {
I
isidor 已提交
920
			this._processManager.createProcess(this._shellLaunchConfig, this._cols, this._rows, this._accessibilityService.isScreenReaderOptimized());
921
		}, 0);
922 923
	}

924 925 926 927 928 929 930 931 932 933 934 935 936 937 938 939 940 941 942 943 944 945
	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;
		}
	}

946
	private _onProcessData(data: string): void {
947
		this._xterm?.write(data);
948 949
	}

950
	/**
D
Daniel Imms 已提交
951
	 * Called when either a process tied to a terminal has exited or when a terminal renderer
952
	 * simulates a process exiting (e.g. custom execution task).
D
Daniel Imms 已提交
953 954
	 * @param exitCode The exit code of the process, this is undefined when the terminal was exited
	 * through user action.
955
	 */
D
Daniel Imms 已提交
956
	private _onProcessExit(exitCode?: number): void {
957 958 959 960 961
		// Prevent dispose functions being triggered multiple times
		if (this._isExiting) {
			return;
		}

962 963
		this._logService.debug(`Terminal process exit (id: ${this.id}) with code ${exitCode}`);

D
Daniel Imms 已提交
964
		this._exitCode = exitCode;
965
		this._isExiting = true;
966
		let exitCodeMessage: string | undefined;
B
bpceee 已提交
967

968
		// Create exit code message
969
		if (exitCode) {
970
			if (exitCode === SHELL_PATH_INVALID_EXIT_CODE) {
971 972 973
				exitCodeMessage = nls.localize('terminal.integrated.exitedWithInvalidPath', 'The terminal shell path "{0}" does not exist', this._shellLaunchConfig.executable);
			} else if (exitCode === SHELL_PATH_DIRECTORY_EXIT_CODE) {
				exitCodeMessage = nls.localize('terminal.integrated.exitedWithInvalidPathDirectory', 'The terminal shell path "{0}" is a directory', this._shellLaunchConfig.executable);
974 975
			} else if (exitCode === SHELL_CWD_INVALID_EXIT_CODE && this._shellLaunchConfig.cwd) {
				exitCodeMessage = nls.localize('terminal.integrated.exitedWithInvalidCWD', 'The terminal shell CWD "{0}" does not exist', this._shellLaunchConfig.cwd.toString());
D
Daniel Imms 已提交
976 977
			} else if (exitCode === LEGACY_CONSOLE_MODE_EXIT_CODE) {
				exitCodeMessage = nls.localize('terminal.integrated.legacyConsoleModeError', 'The terminal failed to launch properly because your system has legacy console mode enabled, uncheck "Use legacy console" cmd.exe\'s properties to fix this.');
978
			} else if (this._processManager.processState === ProcessState.KILLED_DURING_LAUNCH) {
979 980 981 982 983 984 985 986 987 988 989 990 991 992 993 994 995 996 997
				let args = '';
				if (typeof this._shellLaunchConfig.args === 'string') {
					args = ` ${this._shellLaunchConfig.args}`;
				} else if (this._shellLaunchConfig.args && this._shellLaunchConfig.args.length) {
					args = ' ' + this._shellLaunchConfig.args.map(a => {
						if (typeof a === 'string' && a.indexOf(' ') !== -1) {
							return `'${a}'`;
						}
						return a;
					}).join(' ');
				}
				if (this._shellLaunchConfig.executable) {
					exitCodeMessage = nls.localize('terminal.integrated.launchFailed', 'The terminal process command \'{0}{1}\' failed to launch (exit code: {2})', this._shellLaunchConfig.executable, args, exitCode);
				} else {
					exitCodeMessage = nls.localize('terminal.integrated.launchFailedExtHost', 'The terminal process failed to launch (exit code: {0})', exitCode);
				}
			} else {
				exitCodeMessage = nls.localize('terminal.integrated.exitedWithCode', 'The terminal process terminated with exit code: {0}', exitCode);
			}
998
		}
999

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

1002 1003
		// Only trigger wait on exit when the exit was *not* triggered by the
		// user (via the `workbench.action.terminal.kill` command).
1004
		if (this._shellLaunchConfig.waitOnExit && this._processManager.processState !== ProcessState.KILLED_BY_USER) {
1005 1006 1007 1008 1009 1010 1011 1012 1013 1014 1015 1016 1017 1018 1019 1020
			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);
				}
			});
1021 1022
		} else {
			this.dispose();
1023
			if (exitCodeMessage) {
1024
				if (this._processManager.processState === ProcessState.KILLED_DURING_LAUNCH) {
1025
					this._notificationService.error(exitCodeMessage);
G
Gabriel DeBacker 已提交
1026
				} else {
1027
					if (this._configHelper.config.showExitAlert) {
1028
						this._notificationService.error(exitCodeMessage);
1029
					} else {
1030
						console.warn(exitCodeMessage);
1031
					}
1032 1033
				}
			}
1034
		}
D
Daniel Imms 已提交
1035

1036
		this._onExit.fire(exitCode);
1037 1038
	}

1039
	private _attachPressAnyKeyToCloseListener(xterm: XTermTerminal) {
1040
		if (xterm.textarea && !this._pressAnyKeyToCloseListener) {
1041
			this._pressAnyKeyToCloseListener = dom.addDisposableListener(xterm.textarea, 'keypress', (event: KeyboardEvent) => {
1042 1043 1044
				if (this._pressAnyKeyToCloseListener) {
					this._pressAnyKeyToCloseListener.dispose();
					this._pressAnyKeyToCloseListener = undefined;
1045 1046 1047 1048 1049
					this.dispose();
					event.preventDefault();
				}
			});
		}
1050 1051
	}

1052
	public reuseTerminal(shell: IShellLaunchConfig, reset: boolean = false): void {
1053
		// Unsubscribe any key listener we may have.
1054 1055
		this._pressAnyKeyToCloseListener?.dispose();
		this._pressAnyKeyToCloseListener = undefined;
1056

1057
		// Kill and clear up the process, making the process manager ready for a new process
1058
		this._processManager.dispose();
1059

1060
		if (this._xterm) {
1061 1062 1063 1064 1065 1066
			if (reset) {
				this._xterm.reset();
			} else {
				// Ensure new processes' output starts at start of new line
				this._xterm.write('\n\x1b[G');
			}
1067

1068 1069 1070 1071
			// Print initialText if specified
			if (shell.initialText) {
				this._xterm.writeln(shell.initialText);
			}
1072

1073 1074 1075 1076 1077
			// Clean up waitOnExit state
			if (this._isExiting && this._shellLaunchConfig.waitOnExit) {
				this._xterm.setOption('disableStdin', false);
				this._isExiting = false;
			}
1078
		}
1079

1080 1081
		// Dispose the environment info widget if it exists
		this._environmentInfo?.disposable.dispose();
1082
		this._environmentInfo = undefined;
1083

1084 1085 1086 1087 1088 1089 1090
		if (!reset) {
			// HACK: Force initialText to be non-falsy for reused terminals such that the
			// conptyInheritCursor flag is passed to the node-pty, this flag can cause a Window to hang
			// in Windows 10 1903 so we only want to use it when something is definitely written to the
			// terminal.
			shell.initialText = ' ';
		}
1091

1092
		// Set the new shell launch config
1093
		this._shellLaunchConfig = shell; // Must be done before calling _createProcess()
1094

G
Gabriel DeBacker 已提交
1095
		// Launch the process unless this is only a renderer.
G
Gabriel DeBacker 已提交
1096
		// In the renderer only cases, we still need to set the title correctly.
1097
		const oldTitle = this._title;
D
Daniel Imms 已提交
1098
		this._createProcess();
1099

1100
		if (oldTitle !== this._title) {
1101
			this.setTitle(this._title, TitleEventSource.Process);
1102
		}
1103

1104
		this._processManager.onProcessData(data => this._onProcessData(data));
1105 1106
	}

1107 1108 1109 1110
	public relaunch(): void {
		this.reuseTerminal(this._shellLaunchConfig, true);
	}

1111
	private _onLineFeed(): void {
1112
		const buffer = this._xterm!.buffer;
D
Daniel Imms 已提交
1113
		const newLine = buffer.active.getLine(buffer.active.baseY + buffer.active.cursorY);
1114
		if (newLine && !newLine.isWrapped) {
D
Daniel Imms 已提交
1115
			this._sendLineData(buffer.active, buffer.active.baseY + buffer.active.cursorY - 1);
1116 1117 1118
		}
	}

1119
	private _onCursorMove(): void {
1120
		const buffer = this._xterm!.buffer;
D
Daniel Imms 已提交
1121
		this._sendLineData(buffer.active, buffer.active.baseY + buffer.active.cursorY);
1122 1123
	}

1124 1125 1126 1127 1128 1129
	private _onTitleChange(title: string): void {
		if (this.isTitleSetByProcess) {
			this.setTitle(title, TitleEventSource.Sequence);
		}
	}

1130
	private _sendLineData(buffer: IBuffer, lineIndex: number): void {
D
Daniel Imms 已提交
1131 1132 1133 1134 1135 1136 1137 1138 1139 1140 1141
		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;
1142
		}
D
Daniel Imms 已提交
1143
		this._onLineData.fire(lineData);
1144 1145
	}

N
void  
Noj Vek 已提交
1146
	private _onKey(key: string, ev: KeyboardEvent): void {
1147 1148 1149 1150 1151 1152 1153
		const event = new StandardKeyboardEvent(ev);

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

D
Daniel Imms 已提交
1154
	private async _onSelectionChange(): Promise<void> {
1155 1156 1157 1158 1159 1160 1161
		if (this._configurationService.getValue('terminal.integrated.copyOnSelection')) {
			if (this.hasSelection()) {
				await this.copySelection();
			}
		}
	}

1162 1163 1164 1165
	@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();
1166 1167
		if (cwd && this._linkManager) {
			this._linkManager.processCwd = cwd;
1168 1169 1170 1171
		}
		return cwd;
	}

1172
	public updateConfig(): void {
1173 1174 1175
		const config = this._configHelper.config;
		this._setCursorBlink(config.cursorBlinking);
		this._setCursorStyle(config.cursorStyle);
B
Bura Chuhadar 已提交
1176
		this._setCursorWidth(config.cursorWidth);
1177 1178
		this._setCommandsToSkipShell(config.commandsToSkipShell);
		this._setEnableBell(config.enableBell);
1179
		this._safeSetOption('scrollback', config.scrollback);
1180
		this._safeSetOption('minimumContrastRatio', config.minimumContrastRatio);
1181 1182
		this._safeSetOption('fastScrollSensitivity', config.fastScrollSensitivity);
		this._safeSetOption('scrollSensitivity', config.mouseWheelScrollSensitivity);
1183
		this._safeSetOption('macOptionIsMeta', config.macOptionIsMeta);
1184
		this._safeSetOption('macOptionClickForcesSelection', config.macOptionClickForcesSelection);
1185
		this._safeSetOption('rightClickSelectsWord', config.rightClickBehavior === 'selectWord');
1186
		this._safeSetOption('wordSeparator', config.wordSeparators);
1187 1188 1189 1190
		if (config.rendererType !== 'experimentalWebgl') {
			// Never set webgl as it's an addon not a rendererType
			this._safeSetOption('rendererType', config.rendererType === 'auto' ? 'canvas' : config.rendererType);
		}
1191
		this._refreshEnvironmentVariableInfoWidgetState(this._processManager.environmentVariableInfo);
1192 1193
	}

D
Daniel Imms 已提交
1194 1195 1196 1197 1198 1199 1200 1201 1202 1203 1204 1205
	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;
	}

1206
	public updateAccessibilitySupport(): void {
I
isidor 已提交
1207
		const isEnabled = this._accessibilityService.isScreenReaderOptimized();
1208 1209
		if (isEnabled) {
			this._navigationModeAddon = new NavigationModeAddon(this._terminalA11yTreeFocusContextKey);
1210
			this._xterm!.loadAddon(this._navigationModeAddon);
1211
		} else {
1212 1213
			this._navigationModeAddon?.dispose();
			this._navigationModeAddon = undefined;
1214
		}
1215
		this._xterm!.setOption('screenReaderMode', isEnabled);
1216 1217
	}

1218
	private _setCursorBlink(blink: boolean): void {
D
Daniel Imms 已提交
1219
		if (this._xterm && this._xterm.getOption('cursorBlink') !== blink) {
D
Daniel Imms 已提交
1220
			this._xterm.setOption('cursorBlink', blink);
D
Daniel Imms 已提交
1221
			this._xterm.refresh(0, this._xterm.rows - 1);
D
Daniel Imms 已提交
1222 1223 1224
		}
	}

1225 1226 1227 1228 1229 1230 1231 1232
	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 已提交
1233
	private _setCursorWidth(width: number): void {
1234 1235 1236 1237 1238
		if (this._xterm && this._xterm.getOption('cursorWidth') !== width) {
			this._xterm.setOption('cursorWidth', width);
		}
	}

1239
	private _setCommandsToSkipShell(commands: string[]): void {
1240
		const excludeCommands = commands.filter(command => command[0] === '-').map(command => command.slice(1));
D
Daniel Imms 已提交
1241
		this._skipTerminalCommands = DEFAULT_COMMANDS_TO_SKIP_SHELL.filter(defaultCommand => {
1242 1243
			return excludeCommands.indexOf(defaultCommand) === -1;
		}).concat(commands);
D
Daniel Imms 已提交
1244 1245
	}

D
Daniel Imms 已提交
1246 1247 1248 1249 1250 1251 1252 1253 1254 1255 1256 1257 1258 1259
	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');
				}
			}
		}
	}

1260 1261 1262 1263 1264 1265 1266 1267 1268 1269
	private _safeSetOption(key: string, value: any): void {
		if (!this._xterm) {
			return;
		}

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

1270
	public layout(dimension: dom.Dimension): void {
1271 1272 1273 1274
		if (this.disableLayout) {
			return;
		}

D
Daniel Imms 已提交
1275 1276
		const terminalWidth = this._evaluateColsAndRows(dimension.width, dimension.height);
		if (!terminalWidth) {
D
Daniel Imms 已提交
1277 1278
			return;
		}
1279

1280
		if (this._xterm && this._xterm.element) {
D
Daniel Imms 已提交
1281 1282 1283 1284 1285 1286
			this._xterm.element.style.width = terminalWidth + 'px';
		}

		this._resize();
	}

D
Daniel Imms 已提交
1287
	@debounce(50)
D
Daniel Imms 已提交
1288
	private async _resize(): Promise<void> {
1289 1290
		let cols = this.cols;
		let rows = this.rows;
D
Daniel Imms 已提交
1291

1292
		if (this._xterm && this._xtermCore) {
1293 1294 1295
			// Only apply these settings when the terminal is visible so that
			// the characters are measured correctly.
			if (this._isVisible) {
1296
				const font = this._configHelper.getFont(this._xtermCore);
1297 1298 1299 1300 1301 1302 1303 1304
				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);
1305 1306 1307 1308 1309 1310

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

1313 1314 1315 1316
			if (isNaN(cols) || isNaN(rows)) {
				return;
			}

1317
			if (cols !== this._xterm.cols || rows !== this._xterm.rows) {
D
Daniel Imms 已提交
1318 1319
				this._onDimensionsChanged.fire();
			}
D
Daniel Imms 已提交
1320

D
Daniel Imms 已提交
1321
			this._xterm.resize(cols, rows);
1322
			TerminalInstance._lastKnownGridDimensions = { cols, rows };
1323

1324
			if (this._isVisible) {
1325 1326 1327 1328
				// 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.
1329
				if (this._xterm.getOption('rendererType') === 'canvas') {
1330
					this._xtermCore._renderService._onIntersectionChange({ intersectionRatio: 1 });
1331 1332 1333
					// 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);
1334
				}
1335
			}
D
Daniel Imms 已提交
1336
		}
1337

D
Daniel Imms 已提交
1338 1339
		await this._processManager.ptyProcessReady;
		this._processManager.setDimensions(cols, rows);
D
Daniel Imms 已提交
1340
	}
1341

1342 1343 1344 1345
	public setShellType(shellType: TerminalShellType) {
		this._shellType = shellType;
	}

1346
	public setTitle(title: string | undefined, eventSource: TitleEventSource): void {
1347 1348 1349
		if (!title) {
			return;
		}
1350 1351 1352 1353 1354 1355 1356 1357 1358 1359 1360 1361 1362 1363 1364 1365
		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 已提交
1366
		}
D
Daniel Imms 已提交
1367
		const didTitleChange = title !== this._title;
D
Daniel Imms 已提交
1368
		this._title = title;
D
Daniel Imms 已提交
1369
		if (didTitleChange) {
1370
			if (this._titleReadyComplete) {
1371
				this._titleReadyComplete(title);
1372
				this._titleReadyComplete = undefined;
1373 1374
			}
			this._onTitleChanged.fire(this);
B
Ben Stein 已提交
1375 1376
		}
	}
D
Daniel Imms 已提交
1377

1378 1379 1380 1381
	public waitForTitle(): Promise<string> {
		return this._titleReadyPromise;
	}

D
Daniel Imms 已提交
1382
	public setDimensions(dimensions: ITerminalDimensions | undefined): void {
D
Daniel Imms 已提交
1383 1384 1385 1386
		this._dimensionsOverride = dimensions;
		this._resize();
	}

1387
	private _setResolvedShellLaunchConfig(shellLaunchConfig: IShellLaunchConfig): void {
1388 1389 1390 1391 1392 1393
		this._shellLaunchConfig.args = shellLaunchConfig.args;
		this._shellLaunchConfig.cwd = shellLaunchConfig.cwd;
		this._shellLaunchConfig.executable = shellLaunchConfig.executable;
		this._shellLaunchConfig.env = shellLaunchConfig.env;
	}

1394 1395 1396 1397 1398 1399
	public showEnvironmentInfoHover(): void {
		if (this._environmentInfo) {
			this._environmentInfo.widget.focus();
		}
	}

D
Daniel Imms 已提交
1400
	private _onEnvironmentVariableInfoChanged(info: IEnvironmentVariableInfo): void {
1401
		if (info.requiresAction) {
D
Daniel Imms 已提交
1402
			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));
1403
		}
1404 1405 1406 1407
		this._refreshEnvironmentVariableInfoWidgetState(info);
	}

	private _refreshEnvironmentVariableInfoWidgetState(info?: IEnvironmentVariableInfo): void {
1408
		this._environmentInfo?.disposable.dispose();
1409 1410 1411 1412 1413

		// Check if the widget should not exist
		if (!info ||
			this._configHelper.config.environmentChangesIndicator === 'off' ||
			this._configHelper.config.environmentChangesIndicator === 'warnonly' && !info.requiresAction) {
1414
			this._environmentInfo = undefined;
1415 1416 1417 1418 1419
			return;
		}

		// (Re-)create the widget
		const widget = this._instantiationService.createInstance(EnvironmentVariableInfoWidget, info);
1420 1421 1422 1423
		const disposable = this._widgetManager.attachWidget(widget);
		if (disposable) {
			this._environmentInfo = { widget, disposable };
		}
D
Daniel Imms 已提交
1424 1425
	}

M
Martin Aeschlimann 已提交
1426
	private _getXtermTheme(theme?: IColorTheme): any {
D
Daniel Imms 已提交
1427
		if (!theme) {
M
Martin Aeschlimann 已提交
1428
			theme = this._themeService.getColorTheme();
D
Daniel Imms 已提交
1429 1430
		}

1431
		const location = this._viewDescriptorService.getViewLocationById(TERMINAL_VIEW_ID)!;
D
Daniel Imms 已提交
1432
		const foregroundColor = theme.getColor(TERMINAL_FOREGROUND_COLOR);
1433
		const backgroundColor = theme.getColor(TERMINAL_BACKGROUND_COLOR) || (location === ViewContainerLocation.Sidebar ? theme.getColor(SIDE_BAR_BACKGROUND) : theme.getColor(PANEL_BACKGROUND));
1434 1435 1436
		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 已提交
1437 1438 1439 1440 1441 1442

		return {
			background: backgroundColor ? backgroundColor.toString() : null,
			foreground: foregroundColor ? foregroundColor.toString() : null,
			cursor: cursorColor ? cursorColor.toString() : null,
			cursorAccent: cursorAccentColor ? cursorAccentColor.toString() : null,
1443
			selection: selectionColor ? selectionColor.toString() : null,
D
Daniel Imms 已提交
1444 1445 1446 1447 1448 1449 1450 1451 1452 1453 1454 1455 1456 1457 1458 1459
			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 已提交
1460 1461 1462
		};
	}

M
Martin Aeschlimann 已提交
1463
	private _updateTheme(xterm: XTermTerminal, theme?: IColorTheme): void {
1464
		xterm.setOption('theme', this._getXtermTheme(theme));
D
Daniel Imms 已提交
1465
	}
1466

1467 1468 1469 1470
	public async toggleEscapeSequenceLogging(): Promise<void> {
		const xterm = await this._xtermReadyPromise;
		const isDebug = xterm.getOption('logLevel') === 'debug';
		xterm.setOption('logLevel', isDebug ? 'info' : 'debug');
1471
	}
1472

1473 1474
	public getInitialCwd(): Promise<string> {
		return this._processManager.getInitialCwd();
1475 1476 1477
	}

	public getCwd(): Promise<string> {
1478
		return this._processManager.getCwd();
1479
	}
1480
}
1481

M
Martin Aeschlimann 已提交
1482
registerThemingParticipant((theme: IColorTheme, collector: ICssStyleCollector) => {
1483 1484 1485 1486
	// Border
	const border = theme.getColor(activeContrastBorder);
	if (border) {
		collector.addRule(`
1487 1488
			.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}; }`
1489 1490
		);
	}
1491 1492 1493 1494 1495

	// Scrollbar
	const scrollbarSliderBackgroundColor = theme.getColor(scrollbarSliderBackground);
	if (scrollbarSliderBackgroundColor) {
		collector.addRule(`
1496 1497 1498 1499
			.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,
			.monaco-workbench .pane-body.integrated-terminal .xterm:hover .xterm-viewport { background-color: ${scrollbarSliderBackgroundColor} !important; }`
1500 1501 1502 1503 1504
		);
	}

	const scrollbarSliderHoverBackgroundColor = theme.getColor(scrollbarSliderHoverBackground);
	if (scrollbarSliderHoverBackgroundColor) {
1505
		collector.addRule(`.monaco-workbench .pane-body.integrated-terminal .xterm .xterm-viewport::-webkit-scrollbar-thumb:hover { background-color: ${scrollbarSliderHoverBackgroundColor}; }`);
1506
	}
1507 1508 1509

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