terminalPanel.ts 8.5 KB
Newer Older
D
Daniel Imms 已提交
1 2 3 4 5
/*---------------------------------------------------------------------------------------------
 *  Copyright (c) Microsoft Corporation. All rights reserved.
 *  Licensed under the MIT License. See License.txt in the project root for license information.
 *--------------------------------------------------------------------------------------------*/

6
import cp = require('child_process');
D
Daniel Imms 已提交
7
import termJs = require('term.js');
8
import lifecycle = require('vs/base/common/lifecycle');
9
import os = require('os');
10 11
import path = require('path');
import URI from 'vs/base/common/uri';
12
import DOM = require('vs/base/browser/dom');
13
import platform = require('vs/base/common/platform');
14
import {getBaseThemeId} from 'vs/platform/theme/common/themes';
D
Daniel Imms 已提交
15 16
import {TPromise} from 'vs/base/common/winjs.base';
import {Builder, Dimension} from 'vs/base/browser/builder';
17
import {IConfigurationService} from 'vs/platform/configuration/common/configuration';
18
import {IStringDictionary} from 'vs/base/common/collections';
D
Daniel Imms 已提交
19
import {ITelemetryService} from 'vs/platform/telemetry/common/telemetry';
20
import {IThemeService} from 'vs/workbench/services/themes/common/themeService';
21
import {IWorkspaceContextService} from 'vs/platform/workspace/common/workspace';
22
import {ITerminalConfiguration, ITerminalService, TERMINAL_PANEL_ID} from 'vs/workbench/parts/terminal/electron-browser/terminal';
D
Daniel Imms 已提交
23
import {Panel} from 'vs/workbench/browser/panel';
24 25
import {DomScrollableElement} from 'vs/base/browser/ui/scrollbar/scrollableElement';
import {ScrollbarVisibility} from 'vs/base/browser/ui/scrollbar/scrollableElementOptions';
D
Daniel Imms 已提交
26 27 28 29

const TERMINAL_CHAR_WIDTH = 8;
const TERMINAL_CHAR_HEIGHT = 18;

30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51
const DEFAULT_ANSI_COLORS = {
	'hc-black': [
		'#000000', // black
		'#cd0000', // red
		'#00cd00', // green
		'#cdcd00', // yellow
		'#0000ee', // blue
		'#cd00cd', // magenta
		'#00cdcd', // cyan
		'#e5e5e5', // white
		'#7f7f7f', // bright black
		'#ff0000', // bright red
		'#00ff00', // bright green
		'#ffff00', // bright yellow
		'#5c5cff', // bright blue
		'#ff00ff', // bright magenta
		'#00ffff', // bright cyan
		'#ffffff'  // bright white
	],
	'vs': [
		'#000000', // black
		'#cd3131', // red
52 53
		'#008000', // green
		'#949800', // yellow
D
Daniel Imms 已提交
54
		'#0451a5', // blue
55 56
		'#bc05bc', // magenta
		'#0598bc', // cyan
57 58
		'#555555', // white
		'#666666', // bright black
59
		'#cd3131', // bright red
60 61
		'#00aa00', // bright green
		'#b5ba00', // bright yellow
D
Daniel Imms 已提交
62
		'#0451a5', // bright blue
63 64
		'#bc05bc', // bright magenta
		'#0598bc', // bright cyan
65
		'#a5a5a5'  // bright white
66 67 68 69 70 71
	],
	'vs-dark': [
		'#000000', // black
		'#cd3131', // red
		'#09885a', // green
		'#e5e510', // yellow
D
Daniel Imms 已提交
72 73 74
		'#2472c8', // blue
		'#bc3fbc', // magenta
		'#11a8cd', // cyan
75
		'#e5e5e5', // white
D
Daniel Imms 已提交
76 77 78 79 80 81 82
		'#666666', // bright black
		'#f14c4c', // bright red
		'#17a773', // bright green
		'#f5f543', // bright yellow
		'#3b8eea', // bright blue
		'#d670d6', // bright magenta
		'#29b8db', // bright cyan
83 84 85 86
		'#e5e5e5'  // bright white
	]
};

D
Daniel Imms 已提交
87 88
export class TerminalPanel extends Panel {

89
	private toDispose: lifecycle.IDisposable[];
90
	private ptyProcess: cp.ChildProcess;
D
Daniel Imms 已提交
91 92 93 94 95
	private parentDomElement: HTMLElement;
	private terminal;
	private terminalDomElement: HTMLDivElement;

	constructor(
96
		@IConfigurationService private configurationService: IConfigurationService,
97
		@ITelemetryService telemetryService: ITelemetryService,
98
		@IWorkspaceContextService private contextService: IWorkspaceContextService,
99 100
		@ITerminalService private terminalService: ITerminalService,
		@IThemeService private themeService: IThemeService
D
Daniel Imms 已提交
101 102
	) {
		super(TERMINAL_PANEL_ID, telemetryService);
103
		this.toDispose = [];
D
Daniel Imms 已提交
104 105 106 107 108
	}

	public layout(dimension: Dimension): void {
		let cols = Math.floor(this.parentDomElement.offsetWidth / TERMINAL_CHAR_WIDTH);
		let rows = Math.floor(this.parentDomElement.offsetHeight / TERMINAL_CHAR_HEIGHT);
109 110 111
		if (this.terminal) {
			this.terminal.resize(cols, rows);
		}
112 113 114 115 116 117 118
		if (this.ptyProcess.connected) {
			this.ptyProcess.send({
				event: 'resize',
				cols: cols,
				rows: rows
			});
		}
D
Daniel Imms 已提交
119 120 121 122
	}

	public create(parent: Builder): TPromise<void> {
		super.create(parent);
123
		this.parentDomElement = parent.getHTMLElement();
124
		return this.createTerminal();
125 126
	}

127 128 129 130 131 132 133 134 135
	public setVisible(visible: boolean): TPromise<void> {
		if (visible && this.terminal === null) {
			return super.setVisible(visible).then(() => {
				return this.createTerminal();
			});
		}
		return super.setVisible(visible);
	}

136 137 138 139 140 141 142 143 144 145 146
	private cloneEnv(): IStringDictionary<string> {
		let newEnv: IStringDictionary<string> = Object.create(null);
		Object.keys(process.env).forEach((key) => {
			newEnv[key] = process.env[key];
		});
		return newEnv;
	}

	private createTerminalProcess(): cp.ChildProcess {
		let env = this.cloneEnv();
		env['PTYSHELL'] = this.getShell();
147
		env['PTYCWD'] = this.contextService.getWorkspace() ? this.contextService.getWorkspace().resource.fsPath : os.homedir();
148 149 150 151 152 153
		return cp.fork('./terminalProcess', [], {
			env: env,
			cwd: URI.parse(path.dirname(require.toUrl('./terminalProcess'))).fsPath
		});
	}

154 155 156
	private createTerminal(): TPromise<void> {
		return new TPromise<void>(resolve => {
			this.parentDomElement.innerHTML = '';
157
			this.ptyProcess = this.createTerminalProcess();
158 159
			this.terminalDomElement = document.createElement('div');
			this.parentDomElement.classList.add('integrated-terminal');
160
			let terminalScrollbar = new DomScrollableElement(this.terminalDomElement, {
161 162 163 164
				canUseTranslate3d: false,
				horizontal: ScrollbarVisibility.Hidden,
				vertical: ScrollbarVisibility.Auto
			});
165
			this.toDispose.push(terminalScrollbar);
166 167 168 169
			this.terminal = termJs({
				cursorBlink: false // term.js' blinking cursor breaks selection
			});

170
			this.ptyProcess.on('message', (data) => {
171 172 173
				this.terminal.write(data);
			});
			this.terminal.on('data', (data) => {
174 175 176 177
				this.ptyProcess.send({
					event: 'input',
					data: data
				});
178 179
				return false;
			});
180 181
			this.ptyProcess.on('exit', (exitCode) => {
				this.toDispose = lifecycle.dispose(this.toDispose);
182
				this.terminal.destroy();
183
				this.terminal = null;
184
				// TODO: When multiple terminals are supported this should do something smarter. There is
185
				// also a weird bug here at least on Ubuntu 15.10 where the new terminal text does not
186
				// repaint correctly.
187
				if (exitCode !== 0) {
188 189 190 191
					// TODO: Allow the terminal to be relaunched after an error
					console.error('Integrated terminal exited with code ' + exitCode);
				}
				this.terminalService.toggle();
192
			});
193
			this.toDispose.push(DOM.addDisposableListener(this.parentDomElement, 'mousedown', (event) => {
194 195 196 197 198
				// Drop selection and focus terminal on Linux to enable middle button paste when click
				// occurs on the selection itself.
				if (event.which === 2 && platform.isLinux) {
					this.focusTerminal(true);
				}
199 200
			}));
			this.toDispose.push(DOM.addDisposableListener(this.parentDomElement, 'mouseup', (event) => {
201 202 203
				if (event.which !== 3) {
					this.focusTerminal();
				}
204
			}));
D
Daniel Imms 已提交
205 206 207 208 209 210
			this.toDispose.push(DOM.addDisposableListener(this.parentDomElement, 'keyup', (event: KeyboardEvent) => {
				// Keep terminal open on escape
				if (event.keyCode === 27) {
					event.stopPropagation();
				}
			}));
211 212 213
			this.toDispose.push(this.themeService.onDidThemeChange((themeId) => {
				this.setTerminalTheme(themeId);
			}));
214 215

			this.terminal.open(this.terminalDomElement);
216
			this.parentDomElement.appendChild(terminalScrollbar.getDomNode());
217 218

			let config = this.configurationService.getConfiguration<ITerminalConfiguration>();
219
			this.terminalDomElement.style.fontFamily = config.terminal.integrated.fontFamily;
220
			this.setTerminalTheme(this.themeService.getTheme());
221
			resolve(void 0);
D
Daniel Imms 已提交
222 223
		});
	}
224

225
	private setTerminalTheme(themeId: string) {
226 227 228
		if (!this.terminal) {
			return;
		}
229 230 231 232 233
		let baseThemeId = getBaseThemeId(themeId);
		this.terminal.colors = DEFAULT_ANSI_COLORS[baseThemeId];
		this.terminal.refresh(0, this.terminal.rows);
	}

234 235 236 237
	public focus(): void {
		this.focusTerminal(true);
	}

238
	private focusTerminal(force?: boolean): void {
239 240 241
		if (!this.terminal) {
			return;
		}
242
		let text = window.getSelection().toString();
243
		if (!text || force) {
244 245 246 247 248 249 250
			this.terminal.focus();
			if (this.terminal._textarea) {
				this.terminal._textarea.focus();
			}
		}
	}

251 252 253
	private getShell(): string {
		let config = this.configurationService.getConfiguration<ITerminalConfiguration>();
		if (platform.isWindows) {
254
			return config.terminal.integrated.shell.windows;
255
		}
256
		if (platform.isMacintosh) {
257
			return config.terminal.integrated.shell.osx;
258
		}
259
		return config.terminal.integrated.shell.linux;
260 261
	}

262 263
	public dispose(): void {
		this.toDispose = lifecycle.dispose(this.toDispose);
264 265
		this.terminal.destroy();
		this.ptyProcess.kill();
266 267
		super.dispose();
	}
D
Daniel Imms 已提交
268
}