terminalInstance.ts 5.1 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 platform = require('vs/base/common/platform');
11
import xterm = require('xterm');
12
import {Dimension} from 'vs/base/browser/builder';
C
Christof Marti 已提交
13
import {IMessageService, Severity} from 'vs/platform/message/common/message';
14
import {ITerminalFont} from 'vs/workbench/parts/terminal/electron-browser/terminalConfigHelper';
15 16
import {ITerminalProcess, ITerminalService} from 'vs/workbench/parts/terminal/electron-browser/terminal';
import {IWorkspaceContextService} from 'vs/platform/workspace/common/workspace';
17 18

export class TerminalInstance {
C
Christof Marti 已提交
19 20 21

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

22
	private isExiting: boolean = false;
23 24

	private toDispose: lifecycle.IDisposable[];
25
	private xterm;
26
	private terminalDomElement: HTMLDivElement;
27
	private wrapperElement: HTMLDivElement;
28 29 30
	private font: ITerminalFont;

	public constructor(
31
		private terminalProcess: ITerminalProcess,
32 33 34
		private parentDomElement: HTMLElement,
		private contextService: IWorkspaceContextService,
		private terminalService: ITerminalService,
C
Christof Marti 已提交
35
		private messageService: IMessageService,
36 37 38
		private onExitCallback: (TerminalInstance) => void
	) {
		this.toDispose = [];
39
		this.wrapperElement = document.createElement('div');
D
Tidy up  
Daniel Imms 已提交
40
		DOM.addClass(this.wrapperElement, 'terminal-wrapper');
41
		this.terminalDomElement = document.createElement('div');
42
		this.xterm = xterm();
43

44
		this.terminalProcess.process.on('message', (message) => {
D
Daniel Imms 已提交
45
			if (message.type === 'data') {
46
				this.xterm.write(message.content);
D
Daniel Imms 已提交
47
			}
48
		});
49
		this.xterm.on('data', (data) => {
50
			this.terminalProcess.process.send({
51
				event: 'input',
C
Christof Marti 已提交
52
				data: this.sanitizeInput(data)
53 54 55
			});
			return false;
		});
56
		this.terminalProcess.process.on('exit', (exitCode) => {
57 58 59 60 61
			// Prevent dispose functions being triggered multiple times
			if (!this.isExiting) {
				this.isExiting = true;
				this.dispose();
				if (exitCode) {
C
Christof Marti 已提交
62
					this.messageService.show(Severity.Error, nls.localize('terminal.integrated.exitedWithCode', 'The terminal process terminated with exit code: {0}', exitCode));
63 64
				}
				this.onExitCallback(this);
65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85
			}
		});
		this.toDispose.push(DOM.addDisposableListener(this.parentDomElement, 'mousedown', (event) => {
			// Drop selection and focus terminal on Linux to enable middle button paste when click
			// occurs on the selection itself.
			if (event.which === 2 && platform.isLinux) {
				this.focus(true);
			}
		}));
		this.toDispose.push(DOM.addDisposableListener(this.parentDomElement, 'mouseup', (event) => {
			if (event.which !== 3) {
				this.focus();
			}
		}));
		this.toDispose.push(DOM.addDisposableListener(this.parentDomElement, 'keyup', (event: KeyboardEvent) => {
			// Keep terminal open on escape
			if (event.keyCode === 27) {
				event.stopPropagation();
			}
		}));

86
		this.xterm.open(this.terminalDomElement);
87
		this.wrapperElement.appendChild(this.terminalDomElement);
88
		this.parentDomElement.appendChild(this.wrapperElement);
89 90
	}

C
Christof Marti 已提交
91 92 93 94
	private sanitizeInput(data: any) {
		return typeof data === 'string' ? data.replace(TerminalInstance.eolRegex, os.EOL) : data;
	}

95 96 97 98
	public layout(dimension: Dimension): void {
		if (!this.font || !this.font.charWidth || !this.font.charHeight) {
			return;
		}
99 100 101
		if (!dimension.height) { // Minimized
			return;
		}
D
Daniel Imms 已提交
102 103
		let cols = Math.floor(dimension.width / this.font.charWidth);
		let rows = Math.floor(dimension.height / this.font.charHeight);
104 105
		if (this.xterm) {
			this.xterm.resize(cols, rows);
106
		}
107 108
		if (this.terminalProcess.process.connected) {
			this.terminalProcess.process.send({
109 110 111 112 113 114 115
				event: 'resize',
				cols: cols,
				rows: rows
			});
		}
	}

116
	public toggleVisibility(visible: boolean) {
D
Daniel Imms 已提交
117
		DOM.toggleClass(this.wrapperElement, 'active', visible);
118 119
	}

120 121 122
	public setFont(font: ITerminalFont): void {
		this.font = font;
		this.terminalDomElement.style.fontFamily = this.font.fontFamily;
123 124
		this.terminalDomElement.style.lineHeight = this.font.lineHeight;
		this.terminalDomElement.style.fontSize = this.font.fontSize;
125 126
	}

D
Daniel Imms 已提交
127 128 129 130 131 132 133
	public setCursorBlink(blink: boolean): void {
		if (this.xterm && this.xterm.cursorBlink !== blink) {
			this.xterm.cursorBlink = blink;
			this.xterm.refresh(0, this.xterm.rows - 1);
		}
	}

134
	public focus(force?: boolean): void {
135
		if (!this.xterm) {
136 137 138 139
			return;
		}
		let text = window.getSelection().toString();
		if (!text || force) {
140
			this.xterm.focus();
141 142 143 144
		}
	}

	public dispose(): void {
D
Daniel Imms 已提交
145 146 147 148
		if (this.wrapperElement) {
			this.parentDomElement.removeChild(this.wrapperElement);
			this.wrapperElement = null;
		}
149 150 151 152 153 154 155 156
		if (this.xterm) {
			this.xterm.destroy();
			this.xterm = null;
		}
		if (this.terminalProcess) {
			this.terminalService.killTerminalProcess(this.terminalProcess);
			this.terminalProcess = null;
		}
157 158 159
		this.toDispose = lifecycle.dispose(this.toDispose);
	}
}