terminalEnvironment.ts 10.2 KB
Newer Older
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 path from 'vs/base/common/path';
7
import * as platform from 'vs/base/common/platform';
8
import { URI as Uri } from 'vs/base/common/uri';
9
import { IWorkspaceFolder } from 'vs/platform/workspace/common/workspace';
10
import { IShellLaunchConfig, ITerminalEnvironment } from 'vs/workbench/contrib/terminal/common/terminal';
11
import { IConfigurationResolverService } from 'vs/workbench/services/configurationResolver/common/configurationResolver';
12
import { sanitizeProcessEnvironment } from 'vs/base/common/processes';
13 14 15 16 17

/**
 * This module contains utility functions related to the environment, cwd and paths.
 */

D
Daniel Imms 已提交
18
export function mergeEnvironments(parent: platform.IProcessEnvironment, other: ITerminalEnvironment | undefined): void {
19 20 21 22 23 24 25
	if (!other) {
		return;
	}

	// On Windows apply the new values ignoring case, while still retaining
	// the case of the original key.
	if (platform.isWindows) {
D
Daniel Imms 已提交
26
		for (const configKey in other) {
27
			let actualKey = configKey;
D
Daniel Imms 已提交
28
			for (const envKey in parent) {
29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44
				if (configKey.toLowerCase() === envKey.toLowerCase()) {
					actualKey = envKey;
					break;
				}
			}
			const value = other[configKey];
			_mergeEnvironmentValue(parent, actualKey, value);
		}
	} else {
		Object.keys(other).forEach((key) => {
			const value = other[key];
			_mergeEnvironmentValue(parent, key, value);
		});
	}
}

45
function _mergeEnvironmentValue(env: ITerminalEnvironment, key: string, value: string | null): void {
46 47 48 49 50 51 52
	if (typeof value === 'string') {
		env[key] = value;
	} else {
		delete env[key];
	}
}

D
Daniel Imms 已提交
53
export function addTerminalEnvironmentKeys(env: platform.IProcessEnvironment, version: string | undefined, locale: string | undefined, setLocaleVariables: boolean): void {
54
	env['TERM_PROGRAM'] = 'vscode';
D
Daniel Imms 已提交
55 56 57
	if (version) {
		env['TERM_PROGRAM_VERSION'] = version;
	}
D
Daniel Imms 已提交
58 59 60
	if (setLocaleVariables) {
		env['LANG'] = _getLangEnvVariable(locale);
	}
61
	env['COLORTERM'] = 'truecolor';
62 63
}

64
function mergeNonNullKeys(env: platform.IProcessEnvironment, other: ITerminalEnvironment | NodeJS.ProcessEnv | undefined) {
D
Daniel Imms 已提交
65 66 67 68 69 70 71 72 73 74 75
	if (!other) {
		return;
	}
	for (const key of Object.keys(other)) {
		const value = other[key];
		if (value) {
			env[key] = value;
		}
	}
}

76
function resolveConfigurationVariables(configurationResolverService: IConfigurationResolverService, env: ITerminalEnvironment, lastActiveWorkspaceRoot: IWorkspaceFolder | null): ITerminalEnvironment {
77
	Object.keys(env).forEach((key) => {
78 79 80
		const value = env[key];
		if (typeof value === 'string' && lastActiveWorkspaceRoot !== null) {
			env[key] = configurationResolverService.resolve(lastActiveWorkspaceRoot, value);
81 82 83 84 85
		}
	});
	return env;
}

86
function _getLangEnvVariable(locale?: string) {
87 88 89 90 91 92 93 94 95
	const parts = locale ? locale.split('-') : [];
	const n = parts.length;
	if (n === 0) {
		// Fallback to en_US to prevent possible encoding issues.
		return 'en_US.UTF-8';
	}
	if (n === 1) {
		// app.getLocale can return just a language without a variant, fill in the variant for
		// supported languages as many shells expect a 2-part locale.
96
		const languageVariants: { [key: string]: string } = {
97
			cs: 'CZ',
98 99 100 101 102
			de: 'DE',
			en: 'US',
			es: 'ES',
			fi: 'FI',
			fr: 'FR',
D
Daniel Imms 已提交
103
			hu: 'HU',
104 105 106 107 108
			it: 'IT',
			ja: 'JP',
			ko: 'KR',
			pl: 'PL',
			ru: 'RU',
T
Tomas Kovar 已提交
109
			sk: 'SK',
110 111 112 113 114 115 116 117 118 119 120 121
			zh: 'CN'
		};
		if (parts[0] in languageVariants) {
			parts.push(languageVariants[parts[0]]);
		}
	} else {
		// Ensure the variant is uppercase
		parts[1] = parts[1].toUpperCase();
	}
	return parts.join('_') + '.UTF-8';
}

L
Logan Ramos 已提交
122
export function getCwd(shell: IShellLaunchConfig, userHome: string, lastActiveWorkspace: IWorkspaceFolder | undefined, configurationResolverService: IConfigurationResolverService | undefined, root: Uri | undefined, customCwd: string | undefined): string {
123
	if (shell.cwd) {
124
		return (typeof shell.cwd === 'object') ? shell.cwd.fsPath : shell.cwd;
125 126
	}

127
	let cwd: string | undefined;
128 129

	// TODO: Handle non-existent customCwd
130
	if (!shell.ignoreConfigurationCwd && customCwd) {
131 132 133 134 135 136 137 138
		if (configurationResolverService) {
			try {
				cwd = configurationResolverService.resolve(lastActiveWorkspace, customCwd);
			} catch {
				cwd = undefined;
			}
		}
		if (path.isAbsolute(customCwd) && !cwd) {
139
			cwd = customCwd;
140
		} else if (root && !cwd) {
141
			cwd = path.join(root.fsPath, customCwd);
142 143 144 145 146
		}
	}

	// If there was no custom cwd or it was relative with no workspace
	if (!cwd) {
147
		cwd = root ? root.fsPath : userHome;
148 149 150 151 152 153 154 155 156 157 158 159
	}

	return _sanitizeCwd(cwd);
}

function _sanitizeCwd(cwd: string): string {
	// Make the drive letter uppercase on Windows (see #9448)
	if (platform.platform === platform.Platform.Windows && cwd && cwd[1] === ':') {
		return cwd[0].toUpperCase() + cwd.substr(1);
	}
	return cwd;
}
160

161 162 163 164
export function escapeNonWindowsPath(path: string): string {
	let newPath = path;
	if (newPath.indexOf('\\') !== 0) {
		newPath = newPath.replace(/\\/g, '\\\\');
165
	}
166 167 168 169
	if (!newPath && (newPath.indexOf('"') !== -1)) {
		newPath = '\'' + newPath + '\'';
	} else if (newPath.indexOf(' ') !== -1) {
		newPath = newPath.replace(/ /g, '\\ ');
170
	}
171
	return newPath;
172
}
D
Daniel Imms 已提交
173

D
Daniel Imms 已提交
174
export function getDefaultShell(
D
Daniel Imms 已提交
175 176
	fetchSetting: (key: string) => { user: string | string[] | undefined, value: string | string[] | undefined, default: string | string[] | undefined },
	isWorkspaceShellAllowed: boolean,
177
	defaultShell: string,
178 179
	isWoW64: boolean,
	windir: string | undefined,
180 181 182
	lastActiveWorkspace: IWorkspaceFolder | undefined,
	configurationResolverService: IConfigurationResolverService | undefined,
	platformOverride: platform.Platform = platform.platform,
D
Daniel Imms 已提交
183
): string {
D
Daniel Imms 已提交
184 185
	const platformKey = platformOverride === platform.Platform.Windows ? 'windows' : platformOverride === platform.Platform.Mac ? 'osx' : 'linux';
	const shellConfigValue = fetchSetting(`terminal.integrated.shell.${platformKey}`);
D
Daniel Imms 已提交
186
	let executable = (isWorkspaceShellAllowed ? <string>shellConfigValue.value : <string>shellConfigValue.user) || (<string | null>shellConfigValue.default || defaultShell);
D
Daniel Imms 已提交
187 188 189 190

	// Change Sysnative to System32 if the OS is Windows but NOT WoW64. It's
	// safe to assume that this was used by accident as Sysnative does not
	// exist and will break the terminal in non-WoW64 environments.
191 192
	if ((platformOverride === platform.Platform.Windows) && !isWoW64 && windir) {
		const sysnativePath = path.join(windir, 'Sysnative').toLowerCase();
D
Daniel Imms 已提交
193
		if (executable && executable.toLowerCase().indexOf(sysnativePath) === 0) {
194
			executable = path.join(windir, 'System32', executable.substr(sysnativePath.length));
D
Daniel Imms 已提交
195 196 197 198
		}
	}

	// Convert / to \ on Windows for convenience
D
Daniel Imms 已提交
199 200
	if (executable && platformOverride === platform.Platform.Windows) {
		executable = executable.replace(/\//g, '\\');
D
Daniel Imms 已提交
201
	}
D
Daniel Imms 已提交
202

203 204 205 206
	if (configurationResolverService) {
		executable = configurationResolverService.resolve(lastActiveWorkspace, executable);
	}

D
Daniel Imms 已提交
207 208 209
	return executable;
}

D
Daniel Imms 已提交
210
export function getDefaultShellArgs(
D
Daniel Imms 已提交
211 212
	fetchSetting: (key: string) => { user: string | string[] | undefined, value: string | string[] | undefined, default: string | string[] | undefined },
	isWorkspaceShellAllowed: boolean,
213 214 215 216
	lastActiveWorkspace: IWorkspaceFolder | undefined,
	configurationResolverService: IConfigurationResolverService | undefined,
	platformOverride: platform.Platform = platform.platform,
): string | string[] {
D
Daniel Imms 已提交
217 218
	const platformKey = platformOverride === platform.Platform.Windows ? 'windows' : platformOverride === platform.Platform.Mac ? 'osx' : 'linux';
	const shellArgsConfigValue = fetchSetting(`terminal.integrated.shellArgs.${platformKey}`);
219 220 221 222 223 224 225 226 227 228 229
	let args = <string[] | string>((isWorkspaceShellAllowed ? shellArgsConfigValue.value : shellArgsConfigValue.user) || shellArgsConfigValue.default);
	if (typeof args === 'string' && platformOverride === platform.Platform.Windows) {
		return configurationResolverService ? configurationResolverService.resolve(lastActiveWorkspace, args) : args;
	}
	if (configurationResolverService) {
		const resolvedArgs: string[] = [];
		for (const arg of args) {
			resolvedArgs.push(configurationResolverService.resolve(lastActiveWorkspace, arg));
		}
		args = resolvedArgs;
	}
D
Daniel Imms 已提交
230 231 232
	return args;
}

233 234 235 236 237 238 239
export function createTerminalEnvironment(
	shellLaunchConfig: IShellLaunchConfig,
	lastActiveWorkspace: IWorkspaceFolder | null,
	envFromConfig: { user: ITerminalEnvironment | undefined, value: ITerminalEnvironment | undefined, default: ITerminalEnvironment | undefined },
	configurationResolverService: IConfigurationResolverService | undefined,
	isWorkspaceShellAllowed: boolean,
	version: string | undefined,
240
	setLocaleVariables: boolean,
D
Daniel Imms 已提交
241
	baseEnv: platform.IProcessEnvironment
242 243 244 245 246 247 248 249
): platform.IProcessEnvironment {
	// Create a terminal environment based on settings, launch config and permissions
	let env: platform.IProcessEnvironment = {};
	if (shellLaunchConfig.strictEnv) {
		// strictEnv is true, only use the requested env (ignoring null entries)
		mergeNonNullKeys(env, shellLaunchConfig.env);
	} else {
		// Merge process env with the env from config and from shellLaunchConfig
D
Daniel Imms 已提交
250
		mergeNonNullKeys(env, baseEnv);
251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269

		// const platformKey = platform.isWindows ? 'windows' : (platform.isMacintosh ? 'osx' : 'linux');
		// const envFromConfigValue = this._workspaceConfigurationService.inspect<ITerminalEnvironment | undefined>(`terminal.integrated.env.${platformKey}`);
		const allowedEnvFromConfig = { ...(isWorkspaceShellAllowed ? envFromConfig.value : envFromConfig.user) };

		// Resolve env vars from config and shell
		if (configurationResolverService) {
			if (allowedEnvFromConfig) {
				resolveConfigurationVariables(configurationResolverService, allowedEnvFromConfig, lastActiveWorkspace);
			}
			if (shellLaunchConfig.env) {
				resolveConfigurationVariables(configurationResolverService, shellLaunchConfig.env, lastActiveWorkspace);
			}
		}

		// Sanitize the environment, removing any undesirable VS Code and Electron environment
		// variables
		sanitizeProcessEnvironment(env, 'VSCODE_IPC_HOOK_CLI');

270 271 272 273
		// Merge config (settings) and ShellLaunchConfig environments
		mergeEnvironments(env, allowedEnvFromConfig);
		mergeEnvironments(env, shellLaunchConfig.env);

274 275 276 277
		// Adding other env keys necessary to create the process
		addTerminalEnvironmentKeys(env, version, platform.locale, setLocaleVariables);
	}
	return env;
278
}