terminalInstance.ts 5.8 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 cp = require('child_process');
D
Daniel Imms 已提交
7
import xterm = require('xterm');
8 9 10 11 12 13 14 15 16 17 18 19
import lifecycle = require('vs/base/common/lifecycle');
import os = require('os');
import path = require('path');
import URI from 'vs/base/common/uri';
import DOM = require('vs/base/browser/dom');
import platform = require('vs/base/common/platform');
import {Dimension} from 'vs/base/browser/builder';
import {IStringDictionary} from 'vs/base/common/collections';
import {IWorkspaceContextService} from 'vs/platform/workspace/common/workspace';
import {ITerminalService} from 'vs/workbench/parts/terminal/electron-browser/terminal';
import {DomScrollableElement} from 'vs/base/browser/ui/scrollbar/scrollableElement';
import {ScrollbarVisibility} from 'vs/base/browser/ui/scrollbar/scrollableElementOptions';
D
Daniel Imms 已提交
20
import {IShell, ITerminalFont} from 'vs/workbench/parts/terminal/electron-browser/terminalConfigHelper';
21 22 23

export class TerminalInstance {

24 25
	private processTitle: string = '';

26 27 28 29
	private toDispose: lifecycle.IDisposable[];
	private ptyProcess: cp.ChildProcess;
	private terminal;
	private terminalDomElement: HTMLDivElement;
30
	private wrapperElement: HTMLDivElement;
31 32 33
	private font: ITerminalFont;

	public constructor(
D
Daniel Imms 已提交
34
		private shell: IShell,
35 36 37 38 39 40
		private parentDomElement: HTMLElement,
		private contextService: IWorkspaceContextService,
		private terminalService: ITerminalService,
		private onExitCallback: (TerminalInstance) => void
	) {
		this.toDispose = [];
41
		this.wrapperElement = document.createElement('div');
D
Tidy up  
Daniel Imms 已提交
42
		DOM.addClass(this.wrapperElement, 'terminal-wrapper');
43 44 45 46 47 48 49 50
		this.ptyProcess = this.createTerminalProcess();
		this.terminalDomElement = document.createElement('div');
		let terminalScrollbar = new DomScrollableElement(this.terminalDomElement, {
			canUseTranslate3d: false,
			horizontal: ScrollbarVisibility.Hidden,
			vertical: ScrollbarVisibility.Auto
		});
		this.toDispose.push(terminalScrollbar);
D
Daniel Imms 已提交
51
		this.terminal = xterm();
52

D
Daniel Imms 已提交
53 54 55 56
		this.ptyProcess.on('message', (message) => {
			if (message.type === 'data') {
				this.terminal.write(message.content);
			} else if (message.type === 'title') {
57
				this.processTitle = message.content;
D
Daniel Imms 已提交
58
			}
59 60 61 62 63 64 65 66 67 68
		});
		this.terminal.on('data', (data) => {
			this.ptyProcess.send({
				event: 'input',
				data: data
			});
			return false;
		});
		this.ptyProcess.on('exit', (exitCode) => {
			this.dispose();
D
Daniel Imms 已提交
69
			if (exitCode) {
70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93
				console.error('Integrated terminal exited with code ' + exitCode);
			}
			this.onExitCallback(this);
		});
		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();
			}
		}));

		this.terminal.open(this.terminalDomElement);
94 95
		this.wrapperElement.appendChild(terminalScrollbar.getDomNode());
		this.parentDomElement.appendChild(this.wrapperElement);
96 97 98 99 100 101
	}

	public layout(dimension: Dimension): void {
		if (!this.font || !this.font.charWidth || !this.font.charHeight) {
			return;
		}
102 103 104
		if (!dimension.height) { // Minimized
			return;
		}
D
Daniel Imms 已提交
105 106
		let cols = Math.floor(dimension.width / this.font.charWidth);
		let rows = Math.floor(dimension.height / this.font.charHeight);
107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128
		if (this.terminal) {
			this.terminal.resize(cols, rows);
		}
		if (this.ptyProcess.connected) {
			this.ptyProcess.send({
				event: 'resize',
				cols: cols,
				rows: rows
			});
		}
	}

	private cloneEnv(): IStringDictionary<string> {
		let newEnv: IStringDictionary<string> = Object.create(null);
		Object.keys(process.env).forEach((key) => {
			newEnv[key] = process.env[key];
		});
		return newEnv;
	}

	private createTerminalProcess(): cp.ChildProcess {
		let env = this.cloneEnv();
129
		env['PTYPID'] = process.pid.toString();
D
Daniel Imms 已提交
130 131 132 133
		env['PTYSHELL'] = this.shell.executable;
		this.shell.args.forEach((arg, i) => {
			env[`PTYSHELLARG${i}`] = arg;
		});
134 135 136 137 138 139 140
		env['PTYCWD'] = this.contextService.getWorkspace() ? this.contextService.getWorkspace().resource.fsPath : os.homedir();
		return cp.fork('./terminalProcess', [], {
			env: env,
			cwd: URI.parse(path.dirname(require.toUrl('./terminalProcess'))).fsPath
		});
	}

141
	public toggleVisibility(visible: boolean) {
D
Daniel Imms 已提交
142
		DOM.toggleClass(this.wrapperElement, 'active', visible);
143 144
	}

145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161
	public setFont(font: ITerminalFont): void {
		this.font = font;
		this.terminalDomElement.style.fontFamily = this.font.fontFamily;
		this.terminalDomElement.style.lineHeight = this.font.lineHeight + 'px';
		this.terminalDomElement.style.fontSize = this.font.fontSize + 'px';
	}

	public focus(force?: boolean): void {
		if (!this.terminal) {
			return;
		}
		let text = window.getSelection().toString();
		if (!text || force) {
			this.terminal.focus();
		}
	}

162 163 164 165
	public dispatchEvent(event: Event) {
		this.terminal.element.dispatchEvent(event);
	}

166 167
	public getProcessTitle(): string {
		return this.processTitle;
D
Daniel Imms 已提交
168 169
	}

170
	public dispose(): void {
D
Daniel Imms 已提交
171 172 173 174
		if (this.wrapperElement) {
			this.parentDomElement.removeChild(this.wrapperElement);
			this.wrapperElement = null;
		}
175 176 177 178 179
		this.toDispose = lifecycle.dispose(this.toDispose);
		this.terminal.destroy();
		this.ptyProcess.kill();
	}
}