terminalProcessManager.ts 8.7 KB
Newer Older
D
Daniel Imms 已提交
1 2 3 4 5
/*---------------------------------------------------------------------------------------------
 *  Copyright (c) Microsoft Corporation. All rights reserved.
 *  Licensed under the MIT License. See License.txt in the project root for license information.
 *--------------------------------------------------------------------------------------------*/

6
import * as platform from 'vs/base/common/platform';
7
import * as terminalEnvironment from 'vs/workbench/parts/terminal/node/terminalEnvironment';
D
Daniel Imms 已提交
8
import { IDisposable } from 'vs/base/common/lifecycle';
9
import { ProcessState, ITerminalProcessManager, IShellLaunchConfig, ITerminalConfigHelper } from 'vs/workbench/parts/terminal/common/terminal';
D
Daniel Imms 已提交
10 11
import { ILogService } from 'vs/platform/log/common/log';
import { Emitter, Event } from 'vs/base/common/event';
12
import { IHistoryService } from 'vs/workbench/services/history/common/history';
D
Daniel Imms 已提交
13 14
import { ITerminalChildProcess } from 'vs/workbench/parts/terminal/node/terminal';
import { TerminalProcessExtHostProxy } from 'vs/workbench/parts/terminal/node/terminalProcessExtHostProxy';
15
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
16
import { TerminalProcess } from 'vs/workbench/parts/terminal/node/terminalProcess';
17 18
import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace';
import { IConfigurationResolverService } from 'vs/workbench/services/configurationResolver/common/configurationResolver';
19
import { IWindowService } from 'vs/platform/windows/common/windows';
20
import { Schemas } from 'vs/base/common/network';
21
import { REMOTE_HOST_SCHEME } from 'vs/platform/remote/common/remoteHosts';
D
Daniel Imms 已提交
22

D
Daniel Imms 已提交
23 24 25
/** The amount of time to consider terminal errors to be related to the launch */
const LAUNCHING_DURATION = 500;

D
Daniel Imms 已提交
26 27 28
/**
 * Holds all state related to the creation and management of terminal processes.
 *
D
Daniel Imms 已提交
29 30
 * Internal definitions:
 * - Process: The process launched with the terminalProcess.ts file, or the pty as a whole
D
Daniel Imms 已提交
31 32
 * - Pty Process: The pseudoterminal master process (or the winpty agent process)
 * - Shell Process: The pseudoterminal slave process (ie. the shell)
D
Daniel Imms 已提交
33 34
 */
export class TerminalProcessManager implements ITerminalProcessManager {
35
	public processState: ProcessState = ProcessState.UNINITIALIZED;
36
	public ptyProcessReady: Promise<void>;
D
Daniel Imms 已提交
37
	public shellProcessId: number;
38
	public initialCwd: string;
D
Daniel Imms 已提交
39

40
	private _process: ITerminalChildProcess | null = null;
D
Daniel Imms 已提交
41
	private _preLaunchInputQueue: string[] = [];
D
Daniel Imms 已提交
42 43
	private _disposables: IDisposable[] = [];

D
Daniel Imms 已提交
44 45
	private readonly _onProcessReady: Emitter<void> = new Emitter<void>();
	public get onProcessReady(): Event<void> { return this._onProcessReady.event; }
D
Daniel Imms 已提交
46 47
	private readonly _onProcessData: Emitter<string> = new Emitter<string>();
	public get onProcessData(): Event<string> { return this._onProcessData.event; }
48 49
	private readonly _onProcessTitle: Emitter<string> = new Emitter<string>();
	public get onProcessTitle(): Event<string> { return this._onProcessTitle.event; }
D
Daniel Imms 已提交
50 51
	private readonly _onProcessExit: Emitter<number> = new Emitter<number>();
	public get onProcessExit(): Event<number> { return this._onProcessExit.event; }
D
Daniel Imms 已提交
52 53

	constructor(
54 55
		private readonly _terminalId: number,
		private readonly _configHelper: ITerminalConfigHelper,
56
		@IHistoryService private readonly _historyService: IHistoryService,
57
		@IInstantiationService private readonly _instantiationService: IInstantiationService,
58 59
		@ILogService private readonly _logService: ILogService,
		@IWorkspaceContextService private readonly _workspaceContextService: IWorkspaceContextService,
60 61
		@IConfigurationResolverService private readonly _configurationResolverService: IConfigurationResolverService,
		@IWindowService private readonly _windowService: IWindowService
D
Daniel Imms 已提交
62
	) {
63
		this.ptyProcessReady = new Promise<void>(c => {
64 65 66 67 68
			this.onProcessReady(() => {
				this._logService.debug(`Terminal process ready (shellProcessId: ${this.shellProcessId})`);
				c(void 0);
			});
		});
D
Daniel Imms 已提交
69 70
	}

71
	public dispose(immediate: boolean = false): void {
72
		if (this._process) {
73 74 75 76
			// If the process was still connected this dispose came from
			// within VS Code, not the process, so mark the process as
			// killed by the user.
			this.processState = ProcessState.KILLED_BY_USER;
77
			this._process.shutdown(immediate);
78
			this._process = null;
D
Daniel Imms 已提交
79
		}
D
Daniel Imms 已提交
80 81 82 83 84 85 86 87
		this._disposables.forEach(d => d.dispose());
		this._disposables.length = 0;
	}

	public addDisposable(disposable: IDisposable) {
		this._disposables.push(disposable);
	}

D
Daniel Imms 已提交
88 89 90 91 92
	public createProcess(
		shellLaunchConfig: IShellLaunchConfig,
		cols: number,
		rows: number
	): void {
93 94 95
		if (this._windowService.getConfiguration().remoteAuthority) {
			const activeWorkspaceRootUri = this._historyService.getLastActiveWorkspaceRoot(REMOTE_HOST_SCHEME);
			this._process = this._instantiationService.createInstance(TerminalProcessExtHostProxy, this._terminalId, shellLaunchConfig, activeWorkspaceRootUri, cols, rows);
D
Daniel Imms 已提交
96
		} else {
97 98 99
			if (!shellLaunchConfig.executable) {
				this._configHelper.mergeDefaultShellPathAndArgs(shellLaunchConfig);
			}
100 101 102
			// TODO: @daniel
			const activeWorkspaceRootUri = this._historyService.getLastActiveWorkspaceRoot(Schemas.file);
			this.initialCwd = terminalEnvironment.getCwd(shellLaunchConfig, activeWorkspaceRootUri, this._configHelper.config.cwd);
D
Daniel Imms 已提交
103

104
			// Resolve env vars from config and shell
105
			const lastActiveWorkspaceRoot = this._workspaceContextService.getWorkspaceFolder(activeWorkspaceRootUri);
106 107 108 109 110
			const platformKey = platform.isWindows ? 'windows' : (platform.isMacintosh ? 'osx' : 'linux');
			const envFromConfig = terminalEnvironment.resolveConfigurationVariables(this._configurationResolverService, { ...this._configHelper.config.env[platformKey] }, lastActiveWorkspaceRoot);
			const envFromShell = terminalEnvironment.resolveConfigurationVariables(this._configurationResolverService, { ...shellLaunchConfig.env }, lastActiveWorkspaceRoot);
			shellLaunchConfig.env = envFromShell;

111 112 113
			// Compell type system as process.env should not have any undefined entries
			const env: platform.IProcessEnvironment = { ...process.env } as any;

114 115 116 117 118 119 120 121 122 123
			// Merge process env with the env from config and from shellLaunchConfig
			terminalEnvironment.mergeEnvironments(env, envFromConfig);
			terminalEnvironment.mergeEnvironments(env, shellLaunchConfig.env);

			// Sanitize the environment, removing any undesirable VS Code and Electron environment
			// variables
			terminalEnvironment.sanitizeEnvironment(env);

			// Adding other env keys necessary to create the process
			const locale = this._configHelper.config.setLocaleVariables ? platform.locale : undefined;
124
			terminalEnvironment.addTerminalEnvironmentKeys(env, locale);
125 126 127

			this._logService.debug(`Terminal process launching`, shellLaunchConfig, this.initialCwd, cols, rows, env);
			this._process = new TerminalProcess(shellLaunchConfig, this.initialCwd, cols, rows, env);
D
Daniel Imms 已提交
128
		}
129
		this.processState = ProcessState.LAUNCHING;
130

131 132 133 134
		// The process is non-null, but TS isn't clever enough to know
		const p = this._process!;

		p.onProcessData(data => {
135 136
			this._onProcessData.fire(data);
		});
137

138
		p.onProcessIdReady(pid => {
139 140
			this.shellProcessId = pid;
			this._onProcessReady.fire();
141

142 143
			// Send any queued data that's waiting
			if (this._preLaunchInputQueue.length > 0) {
144
				p.input(this._preLaunchInputQueue.join(''));
145 146 147
				this._preLaunchInputQueue.length = 0;
			}
		});
D
Daniel Imms 已提交
148

149 150
		p.onProcessTitleChanged(title => this._onProcessTitle.fire(title));
		p.onProcessExit(exitCode => this._onExit(exitCode));
D
Daniel Imms 已提交
151 152 153 154 155 156

		setTimeout(() => {
			if (this.processState === ProcessState.LAUNCHING) {
				this.processState = ProcessState.RUNNING;
			}
		}, LAUNCHING_DURATION);
157 158
	}

D
Daniel Imms 已提交
159
	public setDimensions(cols: number, rows: number): void {
160 161 162 163 164 165 166 167 168 169 170
		if (!this._process) {
			return;
		}

		// The child process could already be terminated
		try {
			this._process.resize(cols, rows);
		} catch (error) {
			// We tried to write to a closed pipe / channel.
			if (error.code !== 'EPIPE' && error.code !== 'ERR_IPC_CHANNEL_CLOSED') {
				throw (error);
D
Daniel Imms 已提交
171 172 173 174
			}
		}
	}

D
Daniel Imms 已提交
175 176
	public write(data: string): void {
		if (this.shellProcessId) {
177 178
			if (this._process) {
				// Send data if the pty is ready
179
				this._process.input(data);
180
			}
D
Daniel Imms 已提交
181 182 183 184 185 186
		} else {
			// If the pty is not ready, queue the data received to send later
			this._preLaunchInputQueue.push(data);
		}
	}

D
Daniel Imms 已提交
187
	private _onExit(exitCode: number): void {
188
		this._process = null;
D
Daniel Imms 已提交
189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204

		// If the process is marked as launching then mark the process as killed
		// during launch. This typically means that there is a problem with the
		// shell and args.
		if (this.processState === ProcessState.LAUNCHING) {
			this.processState = ProcessState.KILLED_DURING_LAUNCH;
		}

		// If TerminalInstance did not know about the process exit then it was
		// triggered by the process, not on VS Code's side.
		if (this.processState === ProcessState.RUNNING) {
			this.processState = ProcessState.KILLED_BY_PROCESS;
		}

		this._onProcessExit.fire(exitCode);
	}
D
Daniel Imms 已提交
205
}