terminalPanel.ts 10.0 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 lifecycle = require('vs/base/common/lifecycle');
7
import platform = require('vs/base/common/platform');
8
import DOM = require('vs/base/browser/dom');
D
Daniel Imms 已提交
9
import {IAction} from 'vs/base/common/actions';
D
Daniel Imms 已提交
10 11
import {TPromise} from 'vs/base/common/winjs.base';
import {Builder, Dimension} from 'vs/base/browser/builder';
12
import {IConfigurationService} from 'vs/platform/configuration/common/configuration';
D
Daniel Imms 已提交
13
import {IInstantiationService} from 'vs/platform/instantiation/common/instantiation';
D
Daniel Imms 已提交
14
import {ITelemetryService} from 'vs/platform/telemetry/common/telemetry';
15
import {IThemeService} from 'vs/workbench/services/themes/common/themeService';
16
import {IWorkspaceContextService} from 'vs/platform/workspace/common/workspace';
17
import {ITerminalService, TERMINAL_PANEL_ID} from 'vs/workbench/parts/terminal/electron-browser/terminal';
D
Daniel Imms 已提交
18
import {Panel} from 'vs/workbench/browser/panel';
19 20
import {TerminalConfigHelper} from 'vs/workbench/parts/terminal/electron-browser/terminalConfigHelper';
import {TerminalInstance} from 'vs/workbench/parts/terminal/electron-browser/terminalInstance';
D
Daniel Imms 已提交
21
import {CloseTerminalAction, CreateNewTerminalAction, FocusNextTerminalAction, FocusPreviousTerminalAction} from 'vs/workbench/parts/terminal/electron-browser/terminalActions';
D
Daniel Imms 已提交
22 23
import {ScrollableElement} from 'vs/base/browser/ui/scrollbar/scrollableElement';
import {ScrollbarVisibility} from 'vs/base/browser/ui/scrollbar/scrollableElementOptions';
D
Daniel Imms 已提交
24 25 26

export class TerminalPanel extends Panel {

27 28 29
	private toDispose: lifecycle.IDisposable[] = [];
	private terminalInstances: TerminalInstance[] = [];

D
Daniel Imms 已提交
30
	private actions: IAction[];
D
Daniel Imms 已提交
31
	private parentDomElement: HTMLElement;
D
Daniel Imms 已提交
32
	private tabsOuterContainer: HTMLElement;
D
Daniel Imms 已提交
33 34
	private tabsContainer: HTMLElement;
	private tabScrollbar: ScrollableElement;
D
Daniel Imms 已提交
35
	private terminalContainer: HTMLElement;
36
	private themeStyleElement: HTMLElement;
37
	private configurationHelper: TerminalConfigHelper;
38
	private activeTerminalIndex: number;
D
Daniel Imms 已提交
39 40

	constructor(
41
		@ITelemetryService telemetryService: ITelemetryService,
D
Daniel Imms 已提交
42 43
		@IConfigurationService private configurationService: IConfigurationService,
		@IInstantiationService private instantiationService: IInstantiationService,
44
		@IWorkspaceContextService private contextService: IWorkspaceContextService,
45 46
		@ITerminalService private terminalService: ITerminalService,
		@IThemeService private themeService: IThemeService
D
Daniel Imms 已提交
47 48 49 50
	) {
		super(TERMINAL_PANEL_ID, telemetryService);
	}

51
	public layout(dimension?: Dimension): void {
D
Daniel Imms 已提交
52 53 54
		if (!dimension) {
			return;
		}
55
		if (this.terminalInstances.length > 0) {
D
Daniel Imms 已提交
56 57 58 59
			let computedStyle = window.getComputedStyle(this.tabsContainer);
			let height = dimension.height - parseInt(computedStyle.height.replace(/px/, ''), 10);
			let terminalContainerDimension = new Dimension(dimension.width, height);
			this.terminalInstances[this.activeTerminalIndex].layout(terminalContainerDimension);
60
		}
D
Daniel Imms 已提交
61 62
	}

D
Daniel Imms 已提交
63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79
	public getActions(): IAction[] {
		if (!this.actions) {
			this.actions = [
				this.instantiationService.createInstance(CreateNewTerminalAction, CreateNewTerminalAction.ID, CreateNewTerminalAction.LABEL),
				this.instantiationService.createInstance(CloseTerminalAction, CloseTerminalAction.ID, CloseTerminalAction.LABEL),
				this.instantiationService.createInstance(FocusPreviousTerminalAction, FocusPreviousTerminalAction.ID, FocusPreviousTerminalAction.LABEL),
				this.instantiationService.createInstance(FocusNextTerminalAction, FocusNextTerminalAction.ID, FocusNextTerminalAction.LABEL)
			];

			this.actions.forEach(a => {
				this.toDispose.push(a);
			});
		}

		return this.actions;
	}

D
Daniel Imms 已提交
80 81
	public create(parent: Builder): TPromise<void> {
		super.create(parent);
82
		this.parentDomElement = parent.getHTMLElement();
D
Daniel Imms 已提交
83
		DOM.addClass(this.parentDomElement, 'integrated-terminal');
84
		this.themeStyleElement = document.createElement('style');
D
Daniel Imms 已提交
85 86
		this.tabsOuterContainer = document.createElement('div');
		DOM.addClass(this.tabsOuterContainer, 'tabs-outer-container');
D
Daniel Imms 已提交
87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103
		this.tabsContainer = document.createElement('ul');
		DOM.addClass(this.tabsContainer, 'tabs-container');

		// Custom Scrollbar
		this.tabScrollbar = new ScrollableElement(this.tabsContainer, {
			horizontal: ScrollbarVisibility.Auto,
			vertical: ScrollbarVisibility.Hidden,
			scrollYToX: true,
			useShadows: false,
			canUseTranslate3d: true,
			horizontalScrollbarSize: 3
		});

		this.tabScrollbar.onScroll(e => {
			this.tabsContainer.scrollLeft = e.scrollLeft;
		});

D
Daniel Imms 已提交
104
		this.tabsOuterContainer.appendChild(this.tabScrollbar.getDomNode());
D
Daniel Imms 已提交
105

D
Daniel Imms 已提交
106
		this.terminalContainer = document.createElement('div');
D
Daniel Imms 已提交
107
		DOM.addClass(this.terminalContainer, 'terminal-outer-container');
108
		this.parentDomElement.appendChild(this.themeStyleElement);
D
Daniel Imms 已提交
109
		this.parentDomElement.appendChild(this.terminalContainer);
D
Daniel Imms 已提交
110
		this.parentDomElement.appendChild(this.tabsOuterContainer);
D
Daniel Imms 已提交
111

112
		this.configurationHelper = new TerminalConfigHelper(platform.platform, this.configurationService, this.parentDomElement);
D
Daniel Imms 已提交
113
		this.toDispose.push(DOM.addDisposableListener(this.terminalContainer, 'wheel', (event: WheelEvent) => {
114
			this.terminalInstances[this.activeTerminalIndex].dispatchEvent(new WheelEvent(event.type, event));
115
		}));
116

D
Daniel Imms 已提交
117 118 119
		return this.createTerminal().then(() => {
			return Promise.resolve(void 0);
		});
120 121
	}

D
Daniel Imms 已提交
122
	public createNewTerminalInstance(): TPromise<void> {
123 124
		return this.createTerminal().then(() => {
			this.updateFont();
D
Daniel Imms 已提交
125
			this.focus();
126 127 128
		});
	}

D
Daniel Imms 已提交
129 130 131 132 133 134
	public closeActiveTerminal(): TPromise<void> {
		return new TPromise<void>(resolve => {
			this.onTerminalInstanceExit(this.terminalInstances[this.activeTerminalIndex]);
		});
	}

135
	public setVisible(visible: boolean): TPromise<void> {
136
		if (visible) {
137
			if (this.terminalInstances.length > 0) {
138 139 140 141
				this.updateFont();
				this.updateTheme();
			} else {
				return super.setVisible(visible).then(() => {
D
Daniel Imms 已提交
142
					this.createNewTerminalInstance();
143 144
				});
			}
145 146 147 148
		}
		return super.setVisible(visible);
	}

D
Daniel Imms 已提交
149 150
	private createTerminal(): TPromise<TerminalInstance> {
		return new TPromise<TerminalInstance>(resolve => {
D
Daniel Imms 已提交
151
			var terminalInstance = new TerminalInstance(this.configurationHelper.getShell(), this.terminalContainer, this.contextService, this.terminalService, this.onTerminalInstanceExit.bind(this));
D
Daniel Imms 已提交
152
			this.terminalInstances.push(terminalInstance);
153
			this.setActiveTerminal(this.terminalInstances.length - 1);
154 155
			this.toDispose.push(this.themeService.onDidThemeChange(this.updateTheme.bind(this)));
			this.toDispose.push(this.configurationService.onDidUpdateConfiguration(this.updateFont.bind(this)));
D
Daniel Imms 已提交
156
			this.tabsContainer.appendChild(terminalInstance.getTabElement());
D
Daniel Imms 已提交
157
			resolve(terminalInstance);
D
Daniel Imms 已提交
158 159
		});
	}
160

161 162 163 164 165 166 167
	private setActiveTerminal(index: number) {
		this.activeTerminalIndex = index;
		this.terminalInstances.forEach((terminalInstance, i) => {
			terminalInstance.toggleVisibility(i === this.activeTerminalIndex);
		});
	}

168
	private onTerminalInstanceExit(terminalInstance: TerminalInstance): void {
169 170
		for (var i = 0; i < this.terminalInstances.length; i++) {
			if (this.terminalInstances[i] === terminalInstance) {
D
Daniel Imms 已提交
171
				if (this.activeTerminalIndex > i) {
172 173
					this.activeTerminalIndex--;
				}
D
Daniel Imms 已提交
174 175
				let killedTerminal = this.terminalInstances.splice(i, 1)[0];
				killedTerminal.dispose();
176
			}
177
		}
D
Daniel Imms 已提交
178 179 180 181 182 183
		if (this.terminalInstances.length === 0) {
			this.activeTerminalIndex = -1;
			this.terminalService.toggle();
		} else {
			this.setActiveTerminal(Math.min(this.activeTerminalIndex, this.terminalInstances.length - 1));
		}
184 185 186 187 188 189
	}

	private updateTheme(themeId?: string): void {
		if (!themeId) {
			themeId = this.themeService.getTheme();
		}
D
Daniel Imms 已提交
190 191 192 193
		let theme = this.configurationHelper.getTheme(themeId);

		let css = '';
		theme.forEach((color: string, index: number) => {
194
			// TODO: The classes could probably be reduced, it's so long to beat the specificity of the general rule.
D
Daniel Imms 已提交
195
			let rgba = this.convertHexCssColorToRgba(color, 0.996);
D
Daniel Imms 已提交
196 197 198 199
			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 已提交
200 201
		});

202
		this.themeStyleElement.innerHTML = css;
D
Daniel Imms 已提交
203 204
	}

D
Daniel Imms 已提交
205 206 207
	/**
	 * Converts a CSS hex color (#rrggbb) to a CSS rgba color (rgba(r, g, b, a)).
	 */
D
Daniel Imms 已提交
208 209 210 211 212
	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})`;
213 214
	}

215
	private updateFont(): void {
216
		if (this.terminalInstances.length === 0) {
217 218
			return;
		}
219
		this.terminalInstances[this.activeTerminalIndex].setFont(this.configurationHelper.getFont());
220
		this.layout(new Dimension(this.parentDomElement.offsetWidth, this.parentDomElement.offsetHeight));
221 222
	}

223
	public focus(): void {
224 225
		if (this.terminalInstances.length > 0) {
			this.terminalInstances[this.activeTerminalIndex].focus(true);
226 227 228
		}
	}

229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250
	public focusNext(): void {
		if (this.terminalInstances.length > 1) {
			this.activeTerminalIndex++;
			if (this.activeTerminalIndex >= this.terminalInstances.length) {
				this.activeTerminalIndex = 0;
			}
			this.setActiveTerminal(this.activeTerminalIndex);
			this.focus();
		}
	}

	public focusPrevious(): void {
		if (this.terminalInstances.length > 1) {
			this.activeTerminalIndex--;
			if (this.activeTerminalIndex < 0) {
				this.activeTerminalIndex = this.terminalInstances.length - 1;
			}
			this.setActiveTerminal(this.activeTerminalIndex);
			this.focus();
		}
	}

251 252
	public dispose(): void {
		this.toDispose = lifecycle.dispose(this.toDispose);
253 254
		while (this.terminalInstances.length > 0) {
			this.terminalInstances.pop().dispose();
255
		}
256 257
		super.dispose();
	}
D
Daniel Imms 已提交
258
}