terminalPanel.ts 13.7 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 DOM = require('vs/base/browser/dom');
7
import lifecycle = require('vs/base/common/lifecycle');
D
Daniel Imms 已提交
8
import nls = require('vs/nls');
9
import platform = require('vs/base/common/platform');
10
import {Action, IAction} from 'vs/base/common/actions';
D
Daniel Imms 已提交
11
import {Builder, Dimension} from 'vs/base/browser/builder';
D
Daniel Imms 已提交
12
import {getBaseThemeId} from 'vs/platform/theme/common/themes';
13
import {IActionItem} from 'vs/base/browser/ui/actionbar/actionbar';
14
import {IConfigurationService} from 'vs/platform/configuration/common/configuration';
D
Daniel Imms 已提交
15
import {IContextMenuService} from 'vs/platform/contextview/browser/contextView';
D
Daniel Imms 已提交
16
import {IInstantiationService} from 'vs/platform/instantiation/common/instantiation';
17
import {IKeybindingService} from 'vs/platform/keybinding/common/keybinding';
A
Alex Dima 已提交
18
import {IContextKey} from 'vs/platform/contextkey/common/contextkey';
C
Christof Marti 已提交
19
import {IMessageService} from 'vs/platform/message/common/message';
D
Daniel Imms 已提交
20
import {ITelemetryService} from 'vs/platform/telemetry/common/telemetry';
D
Daniel Imms 已提交
21
import {ITerminalFont, TerminalConfigHelper} from 'vs/workbench/parts/terminal/electron-browser/terminalConfigHelper';
22
import {ITerminalProcess, ITerminalService, TERMINAL_PANEL_ID} from 'vs/workbench/parts/terminal/electron-browser/terminal';
23
import {IThemeService} from 'vs/workbench/services/themes/common/themeService';
24
import {IWorkspaceContextService} from 'vs/platform/workspace/common/workspace';
D
Daniel Imms 已提交
25
import {KillTerminalAction, CreateNewTerminalAction, SwitchTerminalInstanceAction, SwitchTerminalInstanceActionItem, CopyTerminalSelectionAction, TerminalPasteAction} from 'vs/workbench/parts/terminal/electron-browser/terminalActions';
D
Daniel Imms 已提交
26
import {Panel} from 'vs/workbench/browser/panel';
D
Daniel Imms 已提交
27 28
import {Separator} from 'vs/base/browser/ui/actionbar/actionbar';
import {StandardMouseEvent} from 'vs/base/browser/mouseEvent';
29
import {TerminalInstance} from 'vs/workbench/parts/terminal/electron-browser/terminalInstance';
D
Daniel Imms 已提交
30
import {TPromise} from 'vs/base/common/winjs.base';
D
Daniel Imms 已提交
31 32 33

export class TerminalPanel extends Panel {

34 35 36
	private toDispose: lifecycle.IDisposable[] = [];
	private terminalInstances: TerminalInstance[] = [];

D
Daniel Imms 已提交
37
	private actions: IAction[];
D
Daniel Imms 已提交
38
	private contextMenuActions: IAction[];
D
Daniel Imms 已提交
39
	private parentDomElement: HTMLElement;
D
Daniel Imms 已提交
40
	private terminalContainer: HTMLElement;
41
	private currentBaseThemeId: string;
42
	private themeStyleElement: HTMLElement;
43 44
	private fontStyleElement: HTMLElement;
	private font: ITerminalFont;
45
	private configurationHelper: TerminalConfigHelper;
D
Daniel Imms 已提交
46 47

	constructor(
48
		@ITelemetryService telemetryService: ITelemetryService,
D
Daniel Imms 已提交
49
		@IConfigurationService private configurationService: IConfigurationService,
D
Daniel Imms 已提交
50
		@IContextMenuService private contextMenuService: IContextMenuService,
D
Daniel Imms 已提交
51
		@IInstantiationService private instantiationService: IInstantiationService,
52
		@IKeybindingService private keybindingService: IKeybindingService,
53
		@IWorkspaceContextService private contextService: IWorkspaceContextService,
54
		@ITerminalService private terminalService: ITerminalService,
C
Christof Marti 已提交
55 56
		@IThemeService private themeService: IThemeService,
		@IMessageService private messageService: IMessageService
D
Daniel Imms 已提交
57 58 59 60
	) {
		super(TERMINAL_PANEL_ID, telemetryService);
	}

61
	public layout(dimension?: Dimension): void {
D
Daniel Imms 已提交
62 63 64
		if (!dimension) {
			return;
		}
65 66 67
		this.terminalInstances.forEach((t) => {
			t.layout(dimension);
		});
D
Daniel Imms 已提交
68 69
	}

D
Daniel Imms 已提交
70 71 72
	public getActions(): IAction[] {
		if (!this.actions) {
			this.actions = [
73
				this.instantiationService.createInstance(SwitchTerminalInstanceAction, SwitchTerminalInstanceAction.ID, SwitchTerminalInstanceAction.LABEL),
74 75
				this.instantiationService.createInstance(CreateNewTerminalAction, CreateNewTerminalAction.ID, CreateNewTerminalAction.PANEL_LABEL),
				this.instantiationService.createInstance(KillTerminalAction, KillTerminalAction.ID, KillTerminalAction.PANEL_LABEL)
D
Daniel Imms 已提交
76 77 78 79 80 81 82 83
			];
			this.actions.forEach(a => {
				this.toDispose.push(a);
			});
		}
		return this.actions;
	}

D
Daniel Imms 已提交
84 85 86
	private getContextMenuActions(): IAction[] {
		if (!this.contextMenuActions) {
			this.contextMenuActions = [
D
Daniel Imms 已提交
87
				this.instantiationService.createInstance(CreateNewTerminalAction, CreateNewTerminalAction.ID, nls.localize('createNewTerminal', "New Terminal")),
D
Daniel Imms 已提交
88 89 90 91 92 93 94 95 96 97 98
				new Separator(),
				this.instantiationService.createInstance(CopyTerminalSelectionAction, CopyTerminalSelectionAction.ID, nls.localize('copy', "Copy")),
				this.instantiationService.createInstance(TerminalPasteAction, TerminalPasteAction.ID, nls.localize('paste', "Paste"))
			];
			this.contextMenuActions.forEach(a => {
				this.toDispose.push(a);
			});
		}
		return this.contextMenuActions;
	}

99 100 101 102 103 104 105 106
	public getActionItem(action: Action): IActionItem {
		if (action.id === SwitchTerminalInstanceAction.ID) {
			return this.instantiationService.createInstance(SwitchTerminalInstanceActionItem, action);
		}

		return super.getActionItem(action);
	}

107
	public create(parent: Builder): TPromise<any> {
D
Daniel Imms 已提交
108
		super.create(parent);
109
		this.parentDomElement = parent.getHTMLElement();
D
Daniel Imms 已提交
110
		this.terminalService.initConfigHelper(parent);
D
Daniel Imms 已提交
111
		DOM.addClass(this.parentDomElement, 'integrated-terminal');
112
		this.themeStyleElement = document.createElement('style');
113
		this.fontStyleElement = document.createElement('style');
D
Daniel Imms 已提交
114

D
Daniel Imms 已提交
115
		this.terminalContainer = document.createElement('div');
D
Daniel Imms 已提交
116
		DOM.addClass(this.terminalContainer, 'terminal-outer-container');
117
		this.parentDomElement.appendChild(this.themeStyleElement);
118
		this.parentDomElement.appendChild(this.fontStyleElement);
D
Daniel Imms 已提交
119 120
		this.parentDomElement.appendChild(this.terminalContainer);

D
Daniel Imms 已提交
121 122
		this.attachEventListeners();

D
Daniel Imms 已提交
123
		this.configurationHelper = new TerminalConfigHelper(platform.platform, this.configurationService, parent);
124

125
		return this.terminalService.createNew();
126 127
	}

D
Daniel Imms 已提交
128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150
	private attachEventListeners(): void {
		this.toDispose.push(DOM.addDisposableListener(this.parentDomElement, 'mousedown', (event: MouseEvent) => {
			if (this.terminalInstances.length === 0) {
				return;
			}

			if (event.which === 2 && platform.isLinux) {
				// Drop selection and focus terminal on Linux to enable middle button paste when click
				// occurs on the selection itself.
				this.terminalInstances[this.terminalService.getActiveTerminalIndex()].focus(true);
			} else if (event.which === 3) {
				// Trigger the context menu on right click
				let anchor: HTMLElement | { x: number, y: number } = this.parentDomElement;
				if (event instanceof MouseEvent) {
					const standardEvent = new StandardMouseEvent(event);
					anchor = { x: standardEvent.posx, y: standardEvent.posy };
				}

				this.contextMenuService.showContextMenu({
					getAnchor: () => anchor,
					getActions: () => TPromise.as(this.getContextMenuActions()),
					getActionsContext: () => this.parentDomElement,
					getKeyBinding: (action) => {
151
						const opts = this.keybindingService.lookupKeybindings(action.id);
D
Daniel Imms 已提交
152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176
						if (opts.length > 0) {
							return opts[0]; // only take the first one
						}
						return null;
					}
				});
			}
		}));
		this.toDispose.push(DOM.addDisposableListener(this.parentDomElement, 'mouseup', (event) => {
			if (this.terminalInstances.length === 0) {
				return;
			}

			if (event.which !== 3) {
				this.terminalInstances[this.terminalService.getActiveTerminalIndex()].focus();
			}
		}));
		this.toDispose.push(DOM.addDisposableListener(this.parentDomElement, 'keyup', (event: KeyboardEvent) => {
			if (event.keyCode === 27) {
				// Keep terminal open on escape
				event.stopPropagation();
			}
		}));
	}

D
Daniel Imms 已提交
177 178
	public createNewTerminalInstance(process: ITerminalProcess, focusContextKey: IContextKey<boolean>): TPromise<number> {
		return this.createTerminal(process, focusContextKey).then((terminalInstance) => {
179
			this.updateConfig();
D
Daniel Imms 已提交
180
			this.focus();
181
			return TPromise.as(terminalInstance.id);
182 183 184
		});
	}

D
Daniel Imms 已提交
185
	public closeActiveTerminal(): TPromise<void> {
186
		return this.closeTerminal(this.terminalService.getActiveTerminalIndex());
187 188 189
	}

	public closeTerminal(index: number): TPromise<void> {
190
		let self = this;
D
Daniel Imms 已提交
191
		return new TPromise<void>(resolve => {
192
			self.onTerminalInstanceExit(self.terminalInstances[index]);
D
Daniel Imms 已提交
193 194 195
		});
	}

196
	public setVisible(visible: boolean): TPromise<void> {
197
		if (visible) {
198
			if (this.terminalInstances.length > 0) {
199
				this.updateConfig();
200 201 202
				this.updateTheme();
			} else {
				return super.setVisible(visible).then(() => {
203
					this.terminalService.createNew();
204 205
				});
			}
206 207 208 209
		}
		return super.setVisible(visible);
	}

A
Alex Dima 已提交
210
	private createTerminal(terminalProcess: ITerminalProcess, terminalFocusContextKey: IContextKey<boolean>): TPromise<TerminalInstance> {
D
Daniel Imms 已提交
211
		return new TPromise<TerminalInstance>(resolve => {
212 213 214 215 216 217
			var terminalInstance = new TerminalInstance(
				terminalProcess,
				this.terminalContainer,
				this.contextMenuService,
				this.contextService,
				this.instantiationService,
218
				this.keybindingService,
219 220 221 222
				this.terminalService,
				this.messageService,
				terminalFocusContextKey,
				this.onTerminalInstanceExit.bind(this));
D
Daniel Imms 已提交
223
			this.terminalInstances.push(terminalInstance);
224
			this.setActiveTerminal(this.terminalInstances.length - 1);
225
			this.toDispose.push(this.themeService.onDidThemeChange(this.updateTheme.bind(this)));
226 227 228
			this.toDispose.push(this.configurationService.onDidUpdateConfiguration(this.updateConfig.bind(this)));
			this.updateTheme();
			this.updateConfig();
D
Daniel Imms 已提交
229
			resolve(terminalInstance);
D
Daniel Imms 已提交
230 231
		});
	}
232

233
	public setActiveTerminal(newActiveIndex: number) {
234
		this.terminalInstances.forEach((terminalInstance, i) => {
235
			terminalInstance.toggleVisibility(i === newActiveIndex);
236 237 238
		});
	}

D
Daniel Imms 已提交
239 240 241 242 243 244 245 246 247 248 249 250
	public setActiveTerminalById(terminalId: number) {
		let terminalIndex = -1;
		this.terminalInstances.forEach((terminalInstance, i) => {
			if (terminalInstance.id === terminalId) {
				terminalIndex = i;
			}
		});
		if (terminalIndex !== -1) {
			this.setActiveTerminal(terminalIndex);
		}
	}

251
	private onTerminalInstanceExit(terminalInstance: TerminalInstance): void {
252 253
		let index = this.terminalInstances.indexOf(terminalInstance);
		if (index !== -1) {
D
Daniel Imms 已提交
254
			this.terminalInstances[index].dispose();
255
			this.terminalInstances.splice(index, 1);
256
		}
D
Daniel Imms 已提交
257 258 259
		if (this.terminalInstances.length > 0) {
			this.setActiveTerminal(this.terminalService.getActiveTerminalIndex());
		}
D
Daniel Imms 已提交
260
		if (this.terminalInstances.length === 0) {
D
Daniel Imms 已提交
261
			this.terminalService.hide();
D
Daniel Imms 已提交
262
		} else {
D
Daniel Imms 已提交
263
			this.terminalService.show(true);
D
Daniel Imms 已提交
264
		}
265 266 267 268 269 270
	}

	private updateTheme(themeId?: string): void {
		if (!themeId) {
			themeId = this.themeService.getTheme();
		}
271

272 273
		let baseThemeId = getBaseThemeId(themeId);
		if (baseThemeId === this.currentBaseThemeId) {
274 275
			return;
		}
276
		this.currentBaseThemeId = baseThemeId;
277

278
		let theme = this.configurationHelper.getTheme(baseThemeId);
D
Daniel Imms 已提交
279 280 281 282

		let css = '';
		theme.forEach((color: string, index: number) => {
			let rgba = this.convertHexCssColorToRgba(color, 0.996);
D
Daniel Imms 已提交
283 284 285 286
			css += `.monaco-workbench .panel.integrated-terminal .xterm .xterm-color-${index} { color: ${color}; }` +
				`.monaco-workbench .panel.integrated-terminal .xterm .xterm-color-${index}::selection { background-color: ${rgba}; }` +
				`.monaco-workbench .panel.integrated-terminal .xterm .xterm-bg-color-${index} { background-color: ${color}; }` +
				`.monaco-workbench .panel.integrated-terminal .xterm .xterm-bg-color-${index}::selection { color: ${color}; }`;
D
Daniel Imms 已提交
287 288
		});

289
		this.themeStyleElement.innerHTML = css;
D
Daniel Imms 已提交
290 291
	}

292 293 294 295 296 297 298 299
	public scrollDown(): void {
		this.terminalInstances[this.terminalService.getActiveTerminalIndex()].scrollDown();
	}

	public scrollUp(): void {
		this.terminalInstances[this.terminalService.getActiveTerminalIndex()].scrollUp();
	}

D
Daniel Imms 已提交
300 301 302
	/**
	 * Converts a CSS hex color (#rrggbb) to a CSS rgba color (rgba(r, g, b, a)).
	 */
D
Daniel Imms 已提交
303 304 305 306 307
	private convertHexCssColorToRgba(hex: string, alpha: number): string {
		let r = parseInt(hex.substr(1, 2), 16);
		let g = parseInt(hex.substr(3, 2), 16);
		let b = parseInt(hex.substr(5, 2), 16);
		return `rgba(${r}, ${g}, ${b}, ${alpha})`;
308 309
	}

310 311 312
	private updateConfig(): void {
		this.updateFont();
		this.updateCursorBlink();
313
		this.updateCommandsToSkipShell();
314 315
	}

316
	private updateFont(): void {
317
		if (this.terminalInstances.length === 0) {
318 319
			return;
		}
320
		let newFont = this.configurationHelper.getFont();
321
		DOM.toggleClass(this.parentDomElement, 'enable-ligatures', this.configurationHelper.getFontLigaturesEnabled());
322 323 324 325 326 327 328 329 330
		if (!this.font || this.fontsDiffer(this.font, newFont)) {
			this.fontStyleElement.innerHTML = '.monaco-workbench .panel.integrated-terminal .xterm {' +
				`font-family: ${newFont.fontFamily};` +
				`font-size: ${newFont.fontSize};` +
				`line-height: ${newFont.lineHeight};` +
				'}';
			this.font = newFont;
		}
		this.terminalInstances[this.terminalService.getActiveTerminalIndex()].setFont(newFont);
331
		this.layout(new Dimension(this.parentDomElement.offsetWidth, this.parentDomElement.offsetHeight));
332 333
	}

334 335 336 337 338 339 340 341
	private fontsDiffer(a: ITerminalFont, b: ITerminalFont): boolean {
		return a.charHeight !== b.charHeight ||
			a.charWidth !== b.charWidth ||
			a.fontFamily !== b.fontFamily ||
			a.fontSize !== b.fontSize ||
			a.lineHeight !== b.lineHeight;
	}

D
Daniel Imms 已提交
342 343 344 345 346 347
	private updateCursorBlink(): void {
		this.terminalInstances.forEach((instance) => {
			instance.setCursorBlink(this.configurationHelper.getCursorBlink());
		});
	}

348 349 350 351 352 353
	private updateCommandsToSkipShell(): void {
		this.terminalInstances.forEach((instance) => {
			instance.setCommandsToSkipShell(this.configurationHelper.getCommandsToSkipShell());
		});
	}

354
	public focus(): void {
D
Daniel Imms 已提交
355 356 357
		let activeIndex = this.terminalService.getActiveTerminalIndex();
		if (activeIndex !== -1 && this.terminalInstances.length > 0) {
			this.terminalInstances[activeIndex].focus(true);
358 359 360
		}
	}

361 362
	public dispose(): void {
		this.toDispose = lifecycle.dispose(this.toDispose);
363 364
		while (this.terminalInstances.length > 0) {
			this.terminalInstances.pop().dispose();
365
		}
366 367
		super.dispose();
	}
D
Daniel Imms 已提交
368
}