terminalInstance.ts 7.3 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 15
import {IKeybindingService2} from 'vs/platform/keybinding/common/keybinding';
import {IKeybindingContextKey} 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 26 27

	private static eolRegex = /\r?\n/g;

28
	private isExiting: boolean = false;
29
	private skipTerminalKeybindings: Keybinding[] = [];
30 31

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

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

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

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

94
		this.xterm.open(this.terminalDomElement);
95

96 97 98 99 100 101 102 103 104 105 106 107 108 109 110

		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);

111 112 113 114 115 116
		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();
		}));
117 118 119 120 121 122 123
		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();
		}));

124
		this.wrapperElement.appendChild(this.terminalDomElement);
125
		this.parentDomElement.appendChild(this.wrapperElement);
126 127
	}

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

132 133 134 135
	public layout(dimension: Dimension): void {
		if (!this.font || !this.font.charWidth || !this.font.charHeight) {
			return;
		}
136 137 138
		if (!dimension.height) { // Minimized
			return;
		}
D
Daniel Imms 已提交
139 140 141
		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 已提交
142
		let rows = Math.floor(dimension.height / this.font.charHeight);
143 144
		if (this.xterm) {
			this.xterm.resize(cols, rows);
D
Daniel Imms 已提交
145
			this.xterm.element.style.width = innerWidth + 'px';
146
		}
147 148
		if (this.terminalProcess.process.connected) {
			this.terminalProcess.process.send({
149 150 151 152 153 154 155
				event: 'resize',
				cols: cols,
				rows: rows
			});
		}
	}

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

160 161 162 163
	public setFont(font: ITerminalFont): void {
		this.font = font;
	}

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

171
	public setCommandsToSkipShell(commands: string[]): void {
172
		this.skipTerminalKeybindings = commands.map((c) => {
A
Alex Dima 已提交
173
			return this.keybindingService2.lookupKeybindings(c);
174 175 176 177 178
		}).reduce((prev, curr) => {
			return prev.concat(curr);
		});
	}

179
	public focus(force?: boolean): void {
180
		if (!this.xterm) {
181 182 183 184
			return;
		}
		let text = window.getSelection().toString();
		if (!text || force) {
185
			this.xterm.focus();
186 187 188
		}
	}

189 190 191 192 193 194 195 196
	public scrollDown(): void {
		this.xterm.scrollDisp(1);
	}

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

197
	public dispose(): void {
D
Daniel Imms 已提交
198 199 200 201
		if (this.wrapperElement) {
			this.parentDomElement.removeChild(this.wrapperElement);
			this.wrapperElement = null;
		}
202 203 204 205 206 207 208 209
		if (this.xterm) {
			this.xterm.destroy();
			this.xterm = null;
		}
		if (this.terminalProcess) {
			this.terminalService.killTerminalProcess(this.terminalProcess);
			this.terminalProcess = null;
		}
210 211 212
		this.toDispose = lifecycle.dispose(this.toDispose);
	}
}