terminalPanel.ts 10.8 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';
21
import {CreateNewTerminalAction} 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) {
56 57 58 59
			this.tabScrollbar.updateState({
				width: this.tabsOuterContainer.offsetWidth,
				scrollWidth: this.tabsContainer.offsetWidth
			});
D
Daniel Imms 已提交
60 61 62 63
			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);
64
		}
D
Daniel Imms 已提交
65 66
	}

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

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

		return this.actions;
	}

D
Daniel Imms 已提交
81 82
	public create(parent: Builder): TPromise<void> {
		super.create(parent);
83
		this.parentDomElement = parent.getHTMLElement();
D
Daniel Imms 已提交
84
		DOM.addClass(this.parentDomElement, 'integrated-terminal');
85
		this.themeStyleElement = document.createElement('style');
D
Daniel Imms 已提交
86 87
		this.tabsOuterContainer = document.createElement('div');
		DOM.addClass(this.tabsOuterContainer, 'tabs-outer-container');
D
Daniel Imms 已提交
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;
		});
104
		this.tabsContainer.style.overflow = 'scroll';
D
Daniel Imms 已提交
105
		this.tabsOuterContainer.appendChild(this.tabScrollbar.getDomNode());
D
Daniel Imms 已提交
106

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

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

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

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

D
Daniel Imms 已提交
130
	public closeActiveTerminal(): TPromise<void> {
131 132 133 134
		return this.closeTerminal(this.activeTerminalIndex);
	}

	public closeTerminal(index: number): TPromise<void> {
D
Daniel Imms 已提交
135
		return new TPromise<void>(resolve => {
136
			this.onTerminalInstanceExit(this.terminalInstances[index]);
D
Daniel Imms 已提交
137 138 139
		});
	}

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

D
Daniel Imms 已提交
154 155
	private createTerminal(): TPromise<TerminalInstance> {
		return new TPromise<TerminalInstance>(resolve => {
D
Daniel Imms 已提交
156
			var terminalInstance = new TerminalInstance(this.configurationHelper.getShell(), this.terminalContainer, this.contextService, this.terminalService, this.onTerminalInstanceExit.bind(this));
D
Daniel Imms 已提交
157
			this.terminalInstances.push(terminalInstance);
158
			this.setActiveTerminal(this.terminalInstances.length - 1);
159 160
			this.toDispose.push(this.themeService.onDidThemeChange(this.updateTheme.bind(this)));
			this.toDispose.push(this.configurationService.onDidUpdateConfiguration(this.updateFont.bind(this)));
161 162 163 164 165 166 167 168 169 170 171 172

			// TODO: Dispose these when the tab gets destroyed, not when the panel does
			this.toDispose.push(DOM.addDisposableListener(terminalInstance.getTabElement(), DOM.EventType.MOUSE_DOWN, (e: MouseEvent) => {
				if (e.button === 0 /* Left Button */) {
					this.setActiveTerminal(this.getTerminalInstanceIndex(terminalInstance));
				}
			}));
			this.toDispose.push(DOM.addDisposableListener(terminalInstance.getTabElement(), DOM.EventType.MOUSE_UP, (e: MouseEvent) => {
				DOM.EventHelper.stop(e);
				if (e.button === 1 /* Middle Button */) {
					this.closeTerminal(this.getTerminalInstanceIndex(terminalInstance));
				}
D
Daniel Imms 已提交
173
			}));
174

D
Daniel Imms 已提交
175
			this.tabsContainer.appendChild(terminalInstance.getTabElement());
D
Daniel Imms 已提交
176
			resolve(terminalInstance);
D
Daniel Imms 已提交
177 178
		});
	}
179

D
Daniel Imms 已提交
180 181 182 183 184 185 186 187 188
	private getTerminalInstanceIndex(terminalInstance: TerminalInstance): number {
		for (let i = 0; i < this.terminalInstances.length; i++) {
			if (terminalInstance === this.terminalInstances[i]) {
				return i;
			}
		};
		return -1;
	}

189 190 191
	private setActiveTerminal(index: number) {
		this.activeTerminalIndex = index;
		this.terminalInstances.forEach((terminalInstance, i) => {
D
Daniel Imms 已提交
192 193 194
			let isActive = i === this.activeTerminalIndex;
			terminalInstance.toggleVisibility(isActive);
			DOM.toggleClass(terminalInstance.getTabElement(), 'active', isActive);
195 196 197
		});
	}

198
	private onTerminalInstanceExit(terminalInstance: TerminalInstance): void {
199 200
		for (var i = 0; i < this.terminalInstances.length; i++) {
			if (this.terminalInstances[i] === terminalInstance) {
D
Daniel Imms 已提交
201
				if (this.activeTerminalIndex > i) {
202 203
					this.activeTerminalIndex--;
				}
D
Daniel Imms 已提交
204 205
				let killedTerminal = this.terminalInstances.splice(i, 1)[0];
				killedTerminal.dispose();
206
			}
207
		}
D
Daniel Imms 已提交
208
		this.tabsContainer.removeChild(terminalInstance.getTabElement());
D
Daniel Imms 已提交
209 210 211 212 213 214
		if (this.terminalInstances.length === 0) {
			this.activeTerminalIndex = -1;
			this.terminalService.toggle();
		} else {
			this.setActiveTerminal(Math.min(this.activeTerminalIndex, this.terminalInstances.length - 1));
		}
215 216 217 218 219 220
	}

	private updateTheme(themeId?: string): void {
		if (!themeId) {
			themeId = this.themeService.getTheme();
		}
D
Daniel Imms 已提交
221 222 223 224
		let theme = this.configurationHelper.getTheme(themeId);

		let css = '';
		theme.forEach((color: string, index: number) => {
225
			// TODO: The classes could probably be reduced, it's so long to beat the specificity of the general rule.
D
Daniel Imms 已提交
226
			let rgba = this.convertHexCssColorToRgba(color, 0.996);
D
Daniel Imms 已提交
227 228 229 230
			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 已提交
231 232
		});

233
		this.themeStyleElement.innerHTML = css;
D
Daniel Imms 已提交
234 235
	}

D
Daniel Imms 已提交
236 237 238
	/**
	 * Converts a CSS hex color (#rrggbb) to a CSS rgba color (rgba(r, g, b, a)).
	 */
D
Daniel Imms 已提交
239 240 241 242 243
	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})`;
244 245
	}

246
	private updateFont(): void {
247
		if (this.terminalInstances.length === 0) {
248 249
			return;
		}
250
		this.terminalInstances[this.activeTerminalIndex].setFont(this.configurationHelper.getFont());
251
		this.layout(new Dimension(this.parentDomElement.offsetWidth, this.parentDomElement.offsetHeight));
252 253
	}

254
	public focus(): void {
255 256
		if (this.terminalInstances.length > 0) {
			this.terminalInstances[this.activeTerminalIndex].focus(true);
257 258 259
		}
	}

260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281
	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();
		}
	}

282 283
	public dispose(): void {
		this.toDispose = lifecycle.dispose(this.toDispose);
284 285
		while (this.terminalInstances.length > 0) {
			this.terminalInstances.pop().dispose();
286
		}
287 288
		super.dispose();
	}
D
Daniel Imms 已提交
289
}