terminalInstance.ts 7.6 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');
7
import lifecycle = require('vs/base/common/lifecycle');
C
Christof Marti 已提交
8
import nls = require('vs/nls');
C
Christof Marti 已提交
9
import os = require('os');
10
import xterm = require('xterm');
11
import {Dimension} from 'vs/base/browser/builder';
D
Daniel Imms 已提交
12 13
import {IContextMenuService} from 'vs/platform/contextview/browser/contextView';
import {IInstantiationService} from 'vs/platform/instantiation/common/instantiation';
14
import {IKeybindingService} from 'vs/platform/keybinding/common/keybinding';
A
Alex Dima 已提交
15
import {IContextKey} from 'vs/platform/contextkey/common/contextkey';
C
Christof Marti 已提交
16
import {IMessageService, Severity} from 'vs/platform/message/common/message';
17
import {ITerminalFont} from 'vs/workbench/parts/terminal/electron-browser/terminalConfigHelper';
18 19
import {ITerminalProcess, ITerminalService} from 'vs/workbench/parts/terminal/electron-browser/terminal';
import {IWorkspaceContextService} from 'vs/platform/workspace/common/workspace';
20
import {Keybinding} from 'vs/base/common/keyCodes';
21
import {StandardKeyboardEvent} from 'vs/base/browser/keyboardEvent';
22
import {TabFocus} from 'vs/editor/common/config/commonEditorConfig';
23 24

export class TerminalInstance {
C
Christof Marti 已提交
25

D
Daniel Imms 已提交
26 27
	public id: number;

C
Christof Marti 已提交
28 29
	private static eolRegex = /\r?\n/g;

30
	private isExiting: boolean = false;
31
	private skipTerminalKeybindings: Keybinding[] = [];
32 33

	private toDispose: lifecycle.IDisposable[];
34
	private xterm;
35
	private terminalDomElement: HTMLDivElement;
36
	private wrapperElement: HTMLDivElement;
37 38 39
	private font: ITerminalFont;

	public constructor(
40
		private terminalProcess: ITerminalProcess,
41
		private parentDomElement: HTMLElement,
D
Daniel Imms 已提交
42
		private contextMenuService: IContextMenuService,
43
		private contextService: IWorkspaceContextService,
D
Daniel Imms 已提交
44
		private instantiationService: IInstantiationService,
45
		private keybindingService: IKeybindingService,
46
		private terminalService: ITerminalService,
C
Christof Marti 已提交
47
		private messageService: IMessageService,
A
Alex Dima 已提交
48
		private terminalFocusContextKey: IContextKey<boolean>,
49 50
		private onExitCallback: (TerminalInstance) => void
	) {
51
		let self = this;
52
		this.toDispose = [];
53
		this.wrapperElement = document.createElement('div');
D
Tidy up  
Daniel Imms 已提交
54
		DOM.addClass(this.wrapperElement, 'terminal-wrapper');
55
		this.terminalDomElement = document.createElement('div');
56
		this.xterm = xterm();
57

D
Daniel Imms 已提交
58
		this.id = this.terminalProcess.process.pid;
59
		this.terminalProcess.process.on('message', (message) => {
D
Daniel Imms 已提交
60
			if (message.type === 'data') {
61
				this.xterm.write(message.content);
D
Daniel Imms 已提交
62
			}
63
		});
64
		this.xterm.on('data', (data) => {
65
			this.terminalProcess.process.send({
66
				event: 'input',
C
Christof Marti 已提交
67
				data: this.sanitizeInput(data)
68 69 70
			});
			return false;
		});
71
		this.xterm.attachCustomKeydownHandler(function (event: KeyboardEvent) {
72 73 74
			// Allow the toggle tab mode keybinding to pass through the terminal so that focus can
			// be escaped
			let standardKeyboardEvent = new StandardKeyboardEvent(event);
75
			if (self.skipTerminalKeybindings.some((k) => standardKeyboardEvent.equals(k.value))) {
76 77
				event.preventDefault();
				return false;
78 79 80 81 82 83 84
			}

			// If tab focus mode is on, tab is not passed to the terminal
			if (TabFocus.getTabFocusMode() && event.keyCode === 9) {
				return false;
			}
		});
85
		this.terminalProcess.process.on('exit', (exitCode) => {
86 87 88 89 90
			// Prevent dispose functions being triggered multiple times
			if (!this.isExiting) {
				this.isExiting = true;
				this.dispose();
				if (exitCode) {
C
Christof Marti 已提交
91
					this.messageService.show(Severity.Error, nls.localize('terminal.integrated.exitedWithCode', 'The terminal process terminated with exit code: {0}', exitCode));
92 93
				}
				this.onExitCallback(this);
94 95 96
			}
		});

97
		this.xterm.open(this.terminalDomElement);
98

99 100 101 102 103 104 105 106 107 108 109 110 111 112 113

		let xtermHelper: HTMLElement = this.xterm.element.querySelector('.xterm-helpers');
		let focusTrap: HTMLElement = document.createElement('div');
		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;
			}
			let hidePanelElement = <HTMLElement>currentElement.querySelector('.hide-panel-action');
			hidePanelElement.focus();
		});
		xtermHelper.insertBefore(focusTrap, this.xterm.textarea);

114 115 116 117 118 119
		this.toDispose.push(DOM.addDisposableListener(this.xterm.textarea, 'focus', (event: KeyboardEvent) => {
			self.terminalFocusContextKey.set(true);
		}));
		this.toDispose.push(DOM.addDisposableListener(this.xterm.textarea, 'blur', (event: KeyboardEvent) => {
			self.terminalFocusContextKey.reset();
		}));
120 121 122 123 124 125 126
		this.toDispose.push(DOM.addDisposableListener(this.xterm.element, 'focus', (event: KeyboardEvent) => {
			self.terminalFocusContextKey.set(true);
		}));
		this.toDispose.push(DOM.addDisposableListener(this.xterm.element, 'blur', (event: KeyboardEvent) => {
			self.terminalFocusContextKey.reset();
		}));

127
		this.wrapperElement.appendChild(this.terminalDomElement);
128
		this.parentDomElement.appendChild(this.wrapperElement);
129 130
	}

C
Christof Marti 已提交
131 132 133 134
	private sanitizeInput(data: any) {
		return typeof data === 'string' ? data.replace(TerminalInstance.eolRegex, os.EOL) : data;
	}

135 136 137 138
	public layout(dimension: Dimension): void {
		if (!this.font || !this.font.charWidth || !this.font.charHeight) {
			return;
		}
139 140 141
		if (!dimension.height) { // Minimized
			return;
		}
D
Daniel Imms 已提交
142 143 144
		let leftPadding = parseInt(getComputedStyle(document.querySelector('.terminal-outer-container')).paddingLeft.split('px')[0], 10);
		let innerWidth = dimension.width - leftPadding;
		let cols = Math.floor(innerWidth / this.font.charWidth);
D
Daniel Imms 已提交
145
		let rows = Math.floor(dimension.height / this.font.charHeight);
146 147
		if (this.xterm) {
			this.xterm.resize(cols, rows);
D
Daniel Imms 已提交
148
			this.xterm.element.style.width = innerWidth + 'px';
149
		}
150 151
		if (this.terminalProcess.process.connected) {
			this.terminalProcess.process.send({
152 153 154 155 156 157 158
				event: 'resize',
				cols: cols,
				rows: rows
			});
		}
	}

159
	public toggleVisibility(visible: boolean) {
D
Daniel Imms 已提交
160
		DOM.toggleClass(this.wrapperElement, 'active', visible);
161 162
	}

163 164 165 166
	public setFont(font: ITerminalFont): void {
		this.font = font;
	}

D
Daniel Imms 已提交
167 168 169 170 171 172 173
	public setCursorBlink(blink: boolean): void {
		if (this.xterm && this.xterm.cursorBlink !== blink) {
			this.xterm.cursorBlink = blink;
			this.xterm.refresh(0, this.xterm.rows - 1);
		}
	}

174
	public setCommandsToSkipShell(commands: string[]): void {
175
		this.skipTerminalKeybindings = commands.map((c) => {
176
			return this.keybindingService.lookupKeybindings(c);
177 178 179 180 181
		}).reduce((prev, curr) => {
			return prev.concat(curr);
		});
	}

D
Daniel Imms 已提交
182 183 184 185 186 187 188 189 190 191
	public sendText(text: string, addNewLine: boolean): void {;
		if (addNewLine && text.substr(text.length - os.EOL.length) !== os.EOL) {
			text += os.EOL;
		}
		this.terminalProcess.process.send({
			event: 'input',
			data: text
		});
	}

192
	public focus(force?: boolean): void {
193
		if (!this.xterm) {
194 195 196 197
			return;
		}
		let text = window.getSelection().toString();
		if (!text || force) {
198
			this.xterm.focus();
199 200 201
		}
	}

202 203 204 205 206 207 208 209
	public scrollDown(): void {
		this.xterm.scrollDisp(1);
	}

	public scrollUp(): void {
		this.xterm.scrollDisp(-1);
	}

210
	public dispose(): void {
D
Daniel Imms 已提交
211 212 213 214
		if (this.wrapperElement) {
			this.parentDomElement.removeChild(this.wrapperElement);
			this.wrapperElement = null;
		}
215 216 217 218 219 220 221 222
		if (this.xterm) {
			this.xterm.destroy();
			this.xterm = null;
		}
		if (this.terminalProcess) {
			this.terminalService.killTerminalProcess(this.terminalProcess);
			this.terminalProcess = null;
		}
223 224 225
		this.toDispose = lifecycle.dispose(this.toDispose);
	}
}