terminalInstanceService.ts 6.9 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 { ITerminalInstanceService } from 'vs/workbench/contrib/terminal/browser/terminal';
7
import { ITerminalInstance, IWindowsShellHelper, IShellLaunchConfig, ITerminalChildProcess, ITerminalConfigHelper, IS_WORKSPACE_SHELL_ALLOWED_STORAGE_KEY } from 'vs/workbench/contrib/terminal/common/terminal';
8 9
import { WindowsShellHelper } from 'vs/workbench/contrib/terminal/node/windowsShellHelper';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
10
import { IProcessEnvironment, Platform, isLinux, isMacintosh, isWindows, OperatingSystem, platform } from 'vs/base/common/platform';
11
import { TerminalProcess } from 'vs/workbench/contrib/terminal/node/terminalProcess';
D
Daniel Imms 已提交
12
import { getSystemShell } from 'vs/workbench/contrib/terminal/node/terminal';
D
Daniel Imms 已提交
13 14 15
import { Terminal as XTermTerminal } from 'xterm';
import { WebLinksAddon as XTermWebLinksAddon } from 'xterm-addon-web-links';
import { SearchAddon as XTermSearchAddon } from 'xterm-addon-search';
16 17
import { readFile } from 'vs/base/node/pfs';
import { basename } from 'vs/base/common/path';
18
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
D
Daniel Imms 已提交
19
import { mergeDefaultShellPathAndArgs, getDefaultShell, getDefaultShellArgs } from 'vs/workbench/contrib/terminal/common/terminalEnvironment';
20
import { StorageScope, IStorageService } from 'vs/platform/storage/common/storage';
21 22

let Terminal: typeof XTermTerminal;
D
Daniel Imms 已提交
23
let WebLinksAddon: typeof XTermWebLinksAddon;
D
Daniel Imms 已提交
24
let SearchAddon: typeof XTermSearchAddon;
25 26 27 28

export class TerminalInstanceService implements ITerminalInstanceService {
	public _serviceBrand: any;

29 30
	private _mainProcessParentEnv: IProcessEnvironment | undefined;

31
	constructor(
32
		@IInstantiationService private readonly _instantiationService: IInstantiationService,
33 34
		@IConfigurationService private readonly _configurationService: IConfigurationService,
		@IStorageService private readonly _storageService: IStorageService
35 36 37
	) {
	}

38
	public async getXtermConstructor(): Promise<typeof XTermTerminal> {
39
		if (!Terminal) {
D
Daniel Imms 已提交
40
			Terminal = (await import('xterm')).Terminal;
41 42 43 44
		}
		return Terminal;
	}

D
Daniel Imms 已提交
45 46 47 48 49 50 51
	public async getXtermWebLinksConstructor(): Promise<typeof XTermWebLinksAddon> {
		if (!WebLinksAddon) {
			WebLinksAddon = (await import('xterm-addon-web-links')).WebLinksAddon;
		}
		return WebLinksAddon;
	}

D
Daniel Imms 已提交
52 53 54 55 56 57 58
	public async getXtermSearchConstructor(): Promise<typeof XTermSearchAddon> {
		if (!SearchAddon) {
			SearchAddon = (await import('xterm-addon-search')).SearchAddon;
		}
		return SearchAddon;
	}

59
	public createWindowsShellHelper(shellProcessId: number, instance: ITerminalInstance, xterm: XTermTerminal): IWindowsShellHelper {
60 61 62
		return new WindowsShellHelper(shellProcessId, instance, xterm);
	}

63
	public createTerminalProcess(shellLaunchConfig: IShellLaunchConfig, cwd: string, cols: number, rows: number, env: IProcessEnvironment, windowsEnableConpty: boolean): ITerminalChildProcess {
D
Daniel Imms 已提交
64
		return this._instantiationService.createInstance(TerminalProcess, shellLaunchConfig, cwd, cols, rows, env, windowsEnableConpty);
65
	}
66

67 68 69 70
	private _isWorkspaceShellAllowed(): boolean {
		return this._storageService.getBoolean(IS_WORKSPACE_SHELL_ALLOWED_STORAGE_KEY, StorageScope.WORKSPACE, false);
	}

71 72 73 74 75 76 77 78 79 80 81 82 83
	public mergeDefaultShellPathAndArgs(shell: IShellLaunchConfig, defaultShell: string, configHelper: ITerminalConfigHelper, platformOverride: Platform = platform): void {
		const isWorkspaceShellAllowed = configHelper.checkWorkspaceShellPermissions(platformOverride === Platform.Windows ? OperatingSystem.Windows : (platformOverride === Platform.Mac ? OperatingSystem.Macintosh : OperatingSystem.Linux));
		mergeDefaultShellPathAndArgs(
			shell,
			(key) => this._configurationService.inspect(key),
			isWorkspaceShellAllowed,
			defaultShell,
			process.env.hasOwnProperty('PROCESSOR_ARCHITEW6432'),
			process.env.windir,
			platformOverride
		);
	}

D
Daniel Imms 已提交
84
	public getDefaultShellAndArgs(): Promise<{ shell: string, args: string[] | string | undefined }> {
85
		const isWorkspaceShellAllowed = this._isWorkspaceShellAllowed();
D
Daniel Imms 已提交
86 87 88 89 90 91 92 93 94 95 96 97
		const shell = getDefaultShell(
			(key) => this._configurationService.inspect(key),
			isWorkspaceShellAllowed,
			getSystemShell(platform),
			process.env.hasOwnProperty('PROCESSOR_ARCHITEW6432'),
			process.env.windir
		);
		const args = getDefaultShellArgs(
			(key) => this._configurationService.inspect(key),
			isWorkspaceShellAllowed
		);
		return Promise.resolve({ shell, args });
98 99
	}

100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134
	public async getMainProcessParentEnv(): Promise<IProcessEnvironment> {
		if (this._mainProcessParentEnv) {
			return this._mainProcessParentEnv;
		}

		// For Linux use /proc/<pid>/status to get the parent of the main process and then fetch its
		// env using /proc/<pid>/environ.
		if (isLinux) {
			const mainProcessId = process.ppid;
			const codeProcessName = basename(process.argv[0]);
			let pid: number = 0;
			let ppid: number = mainProcessId;
			let name: string = codeProcessName;
			do {
				pid = ppid;
				const status = await readFile(`/proc/${pid}/status`, 'utf8');
				const splitByLine = status.split('\n');
				splitByLine.forEach(line => {
					if (line.indexOf('Name:') === 0) {
						name = line.replace(/^Name:\s+/, '');
					}
					if (line.indexOf('PPid:') === 0) {
						ppid = parseInt(line.replace(/^PPid:\s+/, ''));
					}
				});
			} while (name === codeProcessName);
			const rawEnv = await readFile(`/proc/${pid}/environ`, 'utf8');
			const env = {};
			rawEnv.split('\0').forEach(e => {
				const i = e.indexOf('=');
				env[e.substr(0, i)] = e.substr(i + 1);
			});
			this._mainProcessParentEnv = env;
		}

D
Daniel Imms 已提交
135 136 137 138 139
		// For macOS we want the "root" environment as shells by default run as login shells. It
		// doesn't appear to be possible to get the "root" environment as `ps eww -o command` for
		// PID 1 (the parent of the main process when launched from the dock/finder) returns no
		// environment, because of this we will fill in the root environment using a whitelist of
		// environment variables that we have.
140 141
		if (isMacintosh) {
			this._mainProcessParentEnv = {};
142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158
			// This list was generated by diffing launching a terminal with {} and the system
			// terminal launched from finder.
			const rootEnvVars = [
				'SHELL',
				'SSH_AUTH_SOCK',
				'Apple_PubSub_Socket_Render',
				'XPC_FLAGS',
				'XPC_SERVICE_NAME',
				'HOME',
				'LOGNAME',
				'TMPDIR'
			];
			rootEnvVars.forEach(k => {
				if (process.env[k]) {
					this._mainProcessParentEnv![k] = process.env[k]!;
				}
			});
159 160 161 162 163 164 165 166 167
		}

		// TODO: Windows should return a fresh environment block, might need native code?
		if (isWindows) {
			this._mainProcessParentEnv = process.env as IProcessEnvironment;
		}

		return this._mainProcessParentEnv!;
	}
168
}