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 14
import {IContextMenuService} from 'vs/platform/contextview/browser/contextView';
import {IInstantiationService} from 'vs/platform/instantiation/common/instantiation';
import {IKeybindingService, IKeybindingContextKey} from 'vs/platform/keybinding/common/keybinding';
C
Christof Marti 已提交
15
import {IMessageService, Severity} from 'vs/platform/message/common/message';
16
import {ITerminalFont} from 'vs/workbench/parts/terminal/electron-browser/terminalConfigHelper';
17
import {ITerminalProcess, ITerminalService} from 'vs/workbench/parts/terminal/electron-browser/terminal';
18
import {ScrollDownTerminalAction, ScrollUpTerminalAction} from 'vs/workbench/parts/terminal/electron-browser/terminalActions';
19
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 23
import {TabFocus} from 'vs/editor/common/config/commonEditorConfig';
import {ToggleTabFocusModeAction} from 'vs/editor/contrib/toggleTabFocusMode/common/toggleTabFocusMode';
24 25

export class TerminalInstance {
C
Christof Marti 已提交
26 27 28

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

29
	private isExiting: boolean = false;
30 31

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

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

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

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

100
		this.xterm.open(this.terminalDomElement);
101

102 103 104 105 106 107 108 109 110 111 112 113 114 115 116

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

117 118 119 120 121 122
		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();
		}));
123 124 125 126 127 128 129
		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();
		}));

130
		this.wrapperElement.appendChild(this.terminalDomElement);
131
		this.parentDomElement.appendChild(this.wrapperElement);
132 133
	}

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

138 139 140 141
	public layout(dimension: Dimension): void {
		if (!this.font || !this.font.charWidth || !this.font.charHeight) {
			return;
		}
142 143 144
		if (!dimension.height) { // Minimized
			return;
		}
D
Daniel Imms 已提交
145 146
		let cols = Math.floor(dimension.width / this.font.charWidth);
		let rows = Math.floor(dimension.height / this.font.charHeight);
147 148
		if (this.xterm) {
			this.xterm.resize(cols, rows);
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 focus(force?: boolean): void {
175
		if (!this.xterm) {
176 177 178 179
			return;
		}
		let text = window.getSelection().toString();
		if (!text || force) {
180
			this.xterm.focus();
181 182 183
		}
	}

184 185 186 187 188 189 190 191
	public scrollDown(): void {
		this.xterm.scrollDisp(1);
	}

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

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