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

import DOM = require('vs/base/browser/dom');
J
Johannes Rieken 已提交
7
import Event, { Emitter } from 'vs/base/common/event';
8 9
import URI from 'vs/base/common/uri';
import cp = require('child_process');
10
import lifecycle = require('vs/base/common/lifecycle');
C
Christof Marti 已提交
11
import nls = require('vs/nls');
C
Christof Marti 已提交
12
import os = require('os');
13 14
import path = require('path');
import platform = require('vs/base/common/platform');
15
import xterm = require('xterm');
16
import { Dimension } from 'vs/base/browser/builder';
17
import { IContextKeyService, IContextKey } from 'vs/platform/contextkey/common/contextkey';
18 19
import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding';
import { IMessageService, Severity } from 'vs/platform/message/common/message';
20
import { IPanelService } from 'vs/workbench/services/panel/common/panelService';
21
import { IStringDictionary } from 'vs/base/common/collections';
22
import { ITerminalInstance, KEYBINDING_CONTEXT_TERMINAL_TEXT_SELECTED, TERMINAL_PANEL_ID, IShellLaunchConfig } from 'vs/workbench/parts/terminal/common/terminal';
23
import { IWorkspace, IWorkspaceContextService } from 'vs/platform/workspace/common/workspace';
24 25
import { StandardKeyboardEvent } from 'vs/base/browser/keyboardEvent';
import { TabFocus } from 'vs/editor/common/config/commonEditorConfig';
26
import { TerminalConfigHelper } from 'vs/workbench/parts/terminal/electron-browser/terminalConfigHelper';
27

D
Daniel Imms 已提交
28 29 30
/** The amount of time to consider terminal errors to be related to the launch */
const LAUNCHING_DURATION = 500;

31
export class TerminalInstance implements ITerminalInstance {
32
	private static readonly EOL_REGEX = /\r?\n/g;
C
Christof Marti 已提交
33

D
Daniel Imms 已提交
34 35
	private static _idCounter = 1;

36
	private _id: number;
D
Daniel Imms 已提交
37
	private _isExiting: boolean;
K
Kai Wood 已提交
38
	private _hadFocusOnExit: boolean;
39
	private _isLaunching: boolean;
D
Daniel Imms 已提交
40
	private _isVisible: boolean;
41
	private _onDisposed: Emitter<TerminalInstance>;
42
	private _onProcessIdReady: Emitter<TerminalInstance>;
D
Daniel Imms 已提交
43
	private _onTitleChanged: Emitter<string>;
D
Daniel Imms 已提交
44
	private _process: cp.ChildProcess;
45
	private _processId: number;
D
Daniel Imms 已提交
46
	private _skipTerminalCommands: string[];
D
Daniel Imms 已提交
47 48 49 50 51
	private _title: string;
	private _toDispose: lifecycle.IDisposable[];
	private _wrapperElement: HTMLDivElement;
	private _xterm: any;
	private _xtermElement: HTMLDivElement;
52
	private _terminalHasTextContextKey: IContextKey<boolean>;
D
Daniel Imms 已提交
53

54
	public get id(): number { return this._id; }
55
	public get processId(): number { return this._processId; }
56
	public get onClosed(): Event<TerminalInstance> { return this._onDisposed.event; }
57
	public get onProcessIdReady(): Event<TerminalInstance> { return this._onProcessIdReady.event; }
D
Daniel Imms 已提交
58
	public get onTitleChanged(): Event<string> { return this._onTitleChanged.event; }
D
Daniel Imms 已提交
59
	public get title(): string { return this._title; }
K
Kai Wood 已提交
60
	public get hadFocusOnExit(): boolean { return this._hadFocusOnExit; }
61 62

	public constructor(
D
Daniel Imms 已提交
63 64 65
		private _terminalFocusContextKey: IContextKey<boolean>,
		private _configHelper: TerminalConfigHelper,
		private _container: HTMLElement,
66
		name: string,
67
		private _shellLaunchConfig: IShellLaunchConfig,
68
		@IContextKeyService private _contextKeyService: IContextKeyService,
D
Daniel Imms 已提交
69
		@IKeybindingService private _keybindingService: IKeybindingService,
70
		@IMessageService private _messageService: IMessageService,
71 72
		@IPanelService private _panelService: IPanelService,
		@IWorkspaceContextService private _contextService: IWorkspaceContextService
73
	) {
D
Daniel Imms 已提交
74
		this._toDispose = [];
D
Daniel Imms 已提交
75
		this._skipTerminalCommands = [];
D
Daniel Imms 已提交
76
		this._isExiting = false;
K
Kai Wood 已提交
77
		this._hadFocusOnExit = false;
78
		this._isLaunching = true;
D
Daniel Imms 已提交
79 80
		this._isVisible = false;
		this._id = TerminalInstance._idCounter++;
81
		this._terminalHasTextContextKey = KEYBINDING_CONTEXT_TERMINAL_TEXT_SELECTED.bindTo(this._contextKeyService);
D
Daniel Imms 已提交
82

83
		this._onDisposed = new Emitter<TerminalInstance>();
84 85
		this._onProcessIdReady = new Emitter<TerminalInstance>();
		this._onTitleChanged = new Emitter<string>();
86

87
		this._createProcess(this._contextService.getWorkspace(), name, this._shellLaunchConfig);
D
Daniel Imms 已提交
88 89 90

		if (_container) {
			this.attachToElement(_container);
91 92 93
		}
	}

D
Daniel Imms 已提交
94
	public addDisposable(disposable: lifecycle.IDisposable): void {
D
Daniel Imms 已提交
95
		this._toDispose.push(disposable);
D
Daniel Imms 已提交
96 97
	}

98
	public attachToElement(container: HTMLElement): void {
D
Daniel Imms 已提交
99
		if (this._wrapperElement) {
100 101 102
			throw new Error('The terminal instance has already been attached to a container');
		}

D
Daniel Imms 已提交
103 104 105 106
		this._container = container;
		this._wrapperElement = document.createElement('div');
		DOM.addClass(this._wrapperElement, 'terminal-wrapper');
		this._xtermElement = document.createElement('div');
107

D
Daniel Imms 已提交
108 109
		this._xterm = xterm();
		this._xterm.open(this._xtermElement);
110

D
Daniel Imms 已提交
111
		this._process.on('message', (message) => {
D
Daniel Imms 已提交
112 113 114
			if (!this._xterm) {
				return;
			}
D
Daniel Imms 已提交
115
			if (message.type === 'data') {
D
Daniel Imms 已提交
116
				this._xterm.write(message.content);
D
Daniel Imms 已提交
117 118
			}
		});
D
Daniel Imms 已提交
119 120
		this._xterm.on('data', (data) => {
			this._process.send({
D
Daniel Imms 已提交
121
				event: 'input',
D
Daniel Imms 已提交
122
				data: this._sanitizeInput(data)
D
Daniel Imms 已提交
123 124 125
			});
			return false;
		});
D
Daniel Imms 已提交
126
		this._xterm.attachCustomKeydownHandler((event: KeyboardEvent) => {
127 128 129 130 131
			// Disable all input if the terminal is exiting
			if (this._isExiting) {
				return false;
			}

132 133
			// Skip processing by xterm.js of keyboard events that resolve to commands described
			// within commandsToSkipShell
D
Daniel Imms 已提交
134
			const standardKeyboardEvent = new StandardKeyboardEvent(event);
135
			const keybinding = standardKeyboardEvent.toKeybinding();
D
Daniel Imms 已提交
136 137
			const resolveResult = this._keybindingService.resolve(keybinding, standardKeyboardEvent.target);
			if (resolveResult && this._skipTerminalCommands.some(k => k === resolveResult.commandId)) {
D
Daniel Imms 已提交
138 139 140 141 142 143 144 145 146
				event.preventDefault();
				return false;
			}

			// If tab focus mode is on, tab is not passed to the terminal
			if (TabFocus.getTabFocusMode() && event.keyCode === 9) {
				return false;
			}
		});
147 148 149 150
		(<HTMLElement>this._xterm.element).addEventListener('mouseup', event => {
			// Wait until mouseup has propogated through the DOM before evaluating the new selection
			// state.
			setTimeout(() => {
151 152 153 154 155 156 157 158 159 160
				this._refreshSelectionContextKey();
			}, 0);
		});

		// xterm.js currently drops selection on keyup as we need to handle this case.
		(<HTMLElement>this._xterm.element).addEventListener('keyup', event => {
			// Wait until keyup has propogated through the DOM before evaluating the new selection
			// state.
			setTimeout(() => {
				this._refreshSelectionContextKey();
161 162
			}, 0);
		});
D
Daniel Imms 已提交
163

D
Daniel Imms 已提交
164 165
		const xtermHelper: HTMLElement = this._xterm.element.querySelector('.xterm-helpers');
		const focusTrap: HTMLElement = document.createElement('div');
166 167 168 169 170 171 172
		focusTrap.setAttribute('tabindex', '0');
		DOM.addClass(focusTrap, 'focus-trap');
		focusTrap.addEventListener('focus', function (event: FocusEvent) {
			let currentElement = focusTrap;
			while (!DOM.hasClass(currentElement, 'part')) {
				currentElement = currentElement.parentElement;
			}
D
Daniel Imms 已提交
173
			const hidePanelElement = <HTMLElement>currentElement.querySelector('.hide-panel-action');
174 175
			hidePanelElement.focus();
		});
D
Daniel Imms 已提交
176
		xtermHelper.insertBefore(focusTrap, this._xterm.textarea);
177

D
Daniel Imms 已提交
178 179
		this._toDispose.push(DOM.addDisposableListener(this._xterm.textarea, 'focus', (event: KeyboardEvent) => {
			this._terminalFocusContextKey.set(true);
180
		}));
D
Daniel Imms 已提交
181 182
		this._toDispose.push(DOM.addDisposableListener(this._xterm.textarea, 'blur', (event: KeyboardEvent) => {
			this._terminalFocusContextKey.reset();
183
			this._refreshSelectionContextKey();
184
		}));
D
Daniel Imms 已提交
185 186
		this._toDispose.push(DOM.addDisposableListener(this._xterm.element, 'focus', (event: KeyboardEvent) => {
			this._terminalFocusContextKey.set(true);
187
		}));
D
Daniel Imms 已提交
188 189
		this._toDispose.push(DOM.addDisposableListener(this._xterm.element, 'blur', (event: KeyboardEvent) => {
			this._terminalFocusContextKey.reset();
190
			this._refreshSelectionContextKey();
191 192
		}));

D
Daniel Imms 已提交
193 194
		this._wrapperElement.appendChild(this._xtermElement);
		this._container.appendChild(this._wrapperElement);
195

196 197 198 199
		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 Dimension(width, height));
D
Daniel Imms 已提交
200
		this.setVisible(this._isVisible);
201
		this.updateConfig();
202 203
	}

204 205 206 207
	public hasSelection(): boolean {
		return !document.getSelection().isCollapsed;
	}

D
Daniel Imms 已提交
208
	public copySelection(): void {
D
Daniel Imms 已提交
209 210 211
		if (document.activeElement.classList.contains('xterm')) {
			document.execCommand('copy');
		} else {
D
Daniel Imms 已提交
212
			this._messageService.show(Severity.Warning, nls.localize('terminal.integrated.copySelection.noSelection', 'Cannot copy terminal selection when terminal does not have focus'));
D
Daniel Imms 已提交
213
		}
D
Daniel Imms 已提交
214 215
	}

216 217 218 219
	public clearSelection(): void {
		document.getSelection().empty();
	}

D
Daniel Imms 已提交
220
	public dispose(): void {
K
Kai Wood 已提交
221 222 223
		if (this._xterm && this._xterm.element) {
			this._hadFocusOnExit = DOM.hasClass(this._xterm.element, 'focus');
		}
D
Daniel Imms 已提交
224 225 226
		if (this._wrapperElement) {
			this._container.removeChild(this._wrapperElement);
			this._wrapperElement = null;
D
Daniel Imms 已提交
227
		}
D
Daniel Imms 已提交
228 229 230
		if (this._xterm) {
			this._xterm.destroy();
			this._xterm = null;
D
Daniel Imms 已提交
231
		}
D
Daniel Imms 已提交
232 233 234
		if (this._process) {
			if (this._process.connected) {
				this._process.kill();
D
Daniel Imms 已提交
235
			}
D
Daniel Imms 已提交
236
			this._process = null;
D
Daniel Imms 已提交
237
		}
238
		this._onDisposed.fire(this);
D
Daniel Imms 已提交
239
		this._toDispose = lifecycle.dispose(this._toDispose);
D
Daniel Imms 已提交
240 241
	}

D
Daniel Imms 已提交
242
	public focus(force?: boolean): void {
D
Daniel Imms 已提交
243
		if (!this._xterm) {
D
Daniel Imms 已提交
244 245
			return;
		}
D
Daniel Imms 已提交
246
		const text = window.getSelection().toString();
D
Daniel Imms 已提交
247
		if (!text || force) {
D
Daniel Imms 已提交
248
			this._xterm.focus();
D
Daniel Imms 已提交
249
		}
D
Daniel Imms 已提交
250 251 252
	}

	public paste(): void {
D
Daniel Imms 已提交
253 254
		this.focus();
		document.execCommand('paste');
D
Daniel Imms 已提交
255 256 257
	}

	public sendText(text: string, addNewLine: boolean): void {
D
Daniel Imms 已提交
258 259 260
		if (addNewLine && text.substr(text.length - os.EOL.length) !== os.EOL) {
			text += os.EOL;
		}
D
Daniel Imms 已提交
261
		this._process.send({
D
Daniel Imms 已提交
262 263 264
			event: 'input',
			data: text
		});
D
Daniel Imms 已提交
265
	}
266 267

	public setVisible(visible: boolean): void {
D
Daniel Imms 已提交
268 269 270
		this._isVisible = visible;
		if (this._wrapperElement) {
			DOM.toggleClass(this._wrapperElement, 'active', visible);
271
		}
272 273
	}

274
	public scrollDownLine(): void {
D
Daniel Imms 已提交
275
		this._xterm.scrollDisp(1);
D
Daniel Imms 已提交
276 277
	}

278
	public scrollDownPage(): void {
279
		this._xterm.scrollPages(1);
280 281
	}

282 283 284 285
	public scrollToBottom(): void {
		this._xterm.scrollToBottom();
	}

286
	public scrollUpLine(): void {
D
Daniel Imms 已提交
287
		this._xterm.scrollDisp(-1);
D
Daniel Imms 已提交
288
	}
289

290
	public scrollUpPage(): void {
291
		this._xterm.scrollPages(-1);
292 293
	}

294 295 296 297
	public scrollToTop(): void {
		this._xterm.scrollToTop();
	}

D
Daniel Imms 已提交
298 299 300 301
	public clear(): void {
		this._xterm.clear();
	}

302
	private _refreshSelectionContextKey() {
303 304 305
		const activePanel = this._panelService.getActivePanel();
		const isFocused = activePanel && activePanel.getId() === TERMINAL_PANEL_ID;
		this._terminalHasTextContextKey.set(isFocused && !window.getSelection().isCollapsed);
306 307
	}

D
Daniel Imms 已提交
308
	private _sanitizeInput(data: any) {
309
		return typeof data === 'string' ? data.replace(TerminalInstance.EOL_REGEX, os.EOL) : data;
C
Christof Marti 已提交
310 311
	}

312 313 314 315 316
	protected _getCwd(shell: IShellLaunchConfig, workspace: IWorkspace): string {
		if (shell.cwd) {
			return shell.cwd;
		}

B
Benjamin Pasero 已提交
317
		let cwd: string;
D
Daniel Imms 已提交
318 319

		// TODO: Handle non-existent customCwd
320
		if (!shell.ignoreConfigurationCwd) {
321
			// Evaluate custom cwd first
D
Daniel Imms 已提交
322
			const customCwd = this._configHelper.getCwd();
323 324 325 326 327 328
			if (customCwd) {
				if (path.isAbsolute(customCwd)) {
					cwd = customCwd;
				} else if (workspace) {
					cwd = path.normalize(path.join(workspace.resource.fsPath, customCwd));
				}
D
Daniel Imms 已提交
329 330 331 332 333
			}
		}

		// If there was no custom cwd or it was relative with no workspace
		if (!cwd) {
334
			cwd = workspace ? workspace.resource.fsPath : os.homedir();
D
Daniel Imms 已提交
335 336 337 338 339
		}

		return TerminalInstance._sanitizeCwd(cwd);
	}

340
	protected _createProcess(workspace: IWorkspace, name: string, shell: IShellLaunchConfig) {
D
Daniel Imms 已提交
341
		const locale = this._configHelper.isSetLocaleVariables() ? platform.locale : undefined;
D
Daniel Imms 已提交
342
		if (!shell.executable) {
343
			this._configHelper.mergeDefaultShellPathAndArgs(shell);
P
Pine Wu 已提交
344
		}
345
		const env = TerminalInstance.createTerminalEnv(process.env, shell, this._getCwd(shell, workspace), locale);
346
		this._title = name ? name : '';
D
Daniel Imms 已提交
347
		this._process = cp.fork('./terminalProcess', [], {
348 349 350 351 352
			env: env,
			cwd: URI.parse(path.dirname(require.toUrl('./terminalProcess'))).fsPath
		});
		if (!name) {
			// Only listen for process title changes when a name is not provided
D
Daniel Imms 已提交
353
			this._process.on('message', (message) => {
354
				if (message.type === 'title') {
D
Daniel Imms 已提交
355
					this._title = message.content ? message.content : '';
356
					this._onTitleChanged.fire(this._title);
357
				}
358 359
			});
		}
360 361 362 363 364 365
		this._process.on('message', (message) => {
			if (message.type === 'pid') {
				this._processId = message.content;
				this._onProcessIdReady.fire(this);
			}
		});
366 367 368 369 370 371 372 373 374 375 376 377 378 379
		this._process.on('exit', exitCode => this._onPtyProcessExit(exitCode));
		setTimeout(() => {
			this._isLaunching = false;
		}, LAUNCHING_DURATION);
	}

	private _onPtyProcessExit(exitCode: number): void {
		// Prevent dispose functions being triggered multiple times
		if (this._isExiting) {
			return;
		}

		this._isExiting = true;
		let exitCodeMessage: string;
380
		if (exitCode) {
381 382 383
			exitCodeMessage = nls.localize('terminal.integrated.exitedWithCode', 'The terminal process terminated with exit code: {0}', exitCode);
		}

384 385 386 387 388
		// Only trigger wait on exit when the exit was triggered by the process, not through the
		// `workbench.action.terminal.kill` command
		const triggeredByProcess = exitCode !== null;

		if (triggeredByProcess && this._shellLaunchConfig.waitOnExit) {
389
			if (exitCode) {
390 391 392 393 394 395
				this._xterm.writeln(exitCodeMessage);
			}
			this._xterm.writeln(nls.localize('terminal.integrated.waitOnExit', 'Press any key to close the terminal'));
			// Disable all input if the terminal is exiting and listen for next keypress
			this._xterm.setOption('disableStdin', true);
			(<HTMLElement>this._xterm.textarea).addEventListener('keypress', (data) => {
396
				this.dispose();
397 398 399
			});
		} else {
			this.dispose();
400
			if (exitCode) {
401 402 403 404 405 406 407 408 409
				if (this._isLaunching) {
					let args = '';
					if (this._shellLaunchConfig.args && this._shellLaunchConfig.args.length) {
						args = ' ' + this._shellLaunchConfig.args.map(a => {
							if (a.indexOf(' ') !== -1) {
								return `'${a}'`;
							}
							return a;
						}).join(' ');
410
					}
411 412 413
					this._messageService.show(Severity.Error, nls.localize('terminal.integrated.launchFailed', 'The terminal process command `{0}{1}` failed to launch (exit code: {2})', this._shellLaunchConfig.executable, args, exitCode));
				} else {
					this._messageService.show(Severity.Error, exitCodeMessage);
414 415
				}
			}
416
		}
417 418
	}

419 420
	// TODO: This should be private/protected
	// TODO: locale should not be optional
421
	public static createTerminalEnv(parentEnv: IStringDictionary<string>, shell: IShellLaunchConfig, cwd: string, locale?: string): IStringDictionary<string> {
422
		const env = shell.env ? shell.env : TerminalInstance._cloneEnv(parentEnv);
423 424
		env['PTYPID'] = process.pid.toString();
		env['PTYSHELL'] = shell.executable;
D
Daniel Imms 已提交
425 426 427 428 429
		if (shell.args) {
			shell.args.forEach((arg, i) => {
				env[`PTYSHELLARG${i}`] = arg;
			});
		}
D
Daniel Imms 已提交
430
		env['PTYCWD'] = cwd;
431
		if (locale) {
D
Daniel Imms 已提交
432
			env['LANG'] = TerminalInstance._getLangEnvVariable(locale);
433 434
		}
		return env;
435 436
	}

437 438 439 440 441 442 443 444
	public onData(listener: (data: string) => void): void {
		this._process.on('message', (message) => {
			if (message.type === 'data') {
				listener(message.content);
			}
		});
	}

445 446 447 448
	public onExit(listener: (exitCode: number) => void): void {
		this._process.on('exit', listener);
	}

D
Daniel Imms 已提交
449
	private static _sanitizeCwd(cwd: string) {
450 451 452
		// Make the drive letter uppercase on Windows (see #9448)
		if (platform.platform === platform.Platform.Windows && cwd && cwd[1] === ':') {
			return cwd[0].toUpperCase() + cwd.substr(1);
D
Daniel Imms 已提交
453
		}
454
		return cwd;
D
Daniel Imms 已提交
455 456
	}

D
Daniel Imms 已提交
457
	private static _cloneEnv(env: IStringDictionary<string>): IStringDictionary<string> {
D
Daniel Imms 已提交
458
		const newEnv: IStringDictionary<string> = Object.create(null);
459 460
		Object.keys(env).forEach((key) => {
			newEnv[key] = env[key];
461
		});
462
		return newEnv;
463 464
	}

D
Daniel Imms 已提交
465
	private static _getLangEnvVariable(locale: string) {
466 467 468 469
		const parts = locale.split('-');
		const n = parts.length;
		if (n > 1) {
			parts[n - 1] = parts[n - 1].toUpperCase();
D
Daniel Imms 已提交
470
		}
471
		return parts.join('_') + '.UTF-8';
D
Daniel Imms 已提交
472
	}
D
Daniel Imms 已提交
473

474 475 476 477 478 479 480
	public updateConfig(): void {
		this._setCursorBlink(this._configHelper.getCursorBlink());
		this._setCommandsToSkipShell(this._configHelper.getCommandsToSkipShell());
		this._setScrollback(this._configHelper.getScrollback());
	}

	private _setCursorBlink(blink: boolean): void {
D
Daniel Imms 已提交
481
		if (this._xterm && this._xterm.getOption('cursorBlink') !== blink) {
D
Daniel Imms 已提交
482
			this._xterm.setOption('cursorBlink', blink);
D
Daniel Imms 已提交
483
			this._xterm.refresh(0, this._xterm.rows - 1);
D
Daniel Imms 已提交
484 485 486
		}
	}

487
	private _setCommandsToSkipShell(commands: string[]): void {
D
Daniel Imms 已提交
488
		this._skipTerminalCommands = commands;
D
Daniel Imms 已提交
489 490
	}

491
	private _setScrollback(lineCount: number): void {
D
Daniel Imms 已提交
492 493 494 495 496
		if (this._xterm && this._xterm.getOption('scrollback') !== lineCount) {
			this._xterm.setOption('scrollback', lineCount);
		}
	}

497
	public layout(dimension: { width: number, height: number }): void {
D
Daniel Imms 已提交
498
		const font = this._configHelper.getFont();
499
		if (!font || !font.charWidth || !font.charHeight) {
D
Daniel Imms 已提交
500 501 502 503
			return;
		}
		if (!dimension.height) { // Minimized
			return;
504 505 506 507 508 509
		} else {
			// Trigger scroll event manually so that the viewport's scroll area is synced. This
			// needs to happen otherwise its scrollTop value is invalid when the panel is toggled as
			// it gets removed and then added back to the DOM (resetting scrollTop to 0).
			// Upstream issue: https://github.com/sourcelair/xterm.js/issues/291
			this._xterm.emit('scroll', this._xterm.ydisp);
D
Daniel Imms 已提交
510
		}
511
		const padding = parseInt(getComputedStyle(document.querySelector('.terminal-outer-container')).paddingLeft.split('px')[0], 10);
D
Daniel Imms 已提交
512 513 514
		// Use left padding as right padding, right padding is not defined in CSS just in case
		// xterm.js causes an unexpected overflow.
		const innerWidth = dimension.width - padding * 2;
D
Daniel Imms 已提交
515 516
		const cols = Math.floor(innerWidth / font.charWidth);
		const rows = Math.floor(dimension.height / font.charHeight);
D
Daniel Imms 已提交
517 518 519
		if (this._xterm) {
			this._xterm.resize(cols, rows);
			this._xterm.element.style.width = innerWidth + 'px';
D
Daniel Imms 已提交
520
		}
D
Daniel Imms 已提交
521 522
		if (this._process.connected) {
			this._process.send({
D
Daniel Imms 已提交
523 524 525 526 527 528
				event: 'resize',
				cols: cols,
				rows: rows
			});
		}
	}
529
}