env.ts 11.1 KB
Newer Older
E
Erich Gamma 已提交
1 2 3 4 5 6 7 8 9 10 11
/*---------------------------------------------------------------------------------------------
 *  Copyright (c) Microsoft Corporation. All rights reserved.
 *  Licensed under the MIT License. See License.txt in the project root for license information.
 *--------------------------------------------------------------------------------------------*/

'use strict';

import crypto = require('crypto');
import fs = require('fs');
import path = require('path');
import os = require('os');
12
import {app} from 'electron';
E
Erich Gamma 已提交
13 14
import arrays = require('vs/base/common/arrays');
import strings = require('vs/base/common/strings');
15
import paths = require('vs/base/common/paths');
E
Erich Gamma 已提交
16 17 18
import platform = require('vs/base/common/platform');
import uri from 'vs/base/common/uri';
import types = require('vs/base/common/types');
J
Joao Moreno 已提交
19
import {ServiceIdentifier, createDecorator} from 'vs/platform/instantiation/common/instantiation';
J
Joao Moreno 已提交
20
import product, {IProductConfiguration} from './product';
J
Joao Moreno 已提交
21
import { parseArgs } from './argv';
J
Joao Moreno 已提交
22 23 24 25 26 27 28 29 30

export interface IProcessEnvironment {
	[key: string]: string;
}

export interface ICommandLineArguments {
	verboseLogging: boolean;
	debugExtensionHostPort: number;
	debugBrkExtensionHost: boolean;
31
	debugBrkFileWatcherPort: number;
J
Joao Moreno 已提交
32 33 34 35 36 37 38 39 40 41 42 43 44 45
	logExtensionHostCommunication: boolean;
	disableExtensions: boolean;
	extensionsHomePath: string;
	extensionDevelopmentPath: string;
	extensionTestsPath: string;
	programStart: number;
	pathArguments?: string[];
	enablePerformance?: boolean;
	openNewWindow?: boolean;
	openInSameWindow?: boolean;
	gotoLineMode?: boolean;
	diffMode?: boolean;
	locale?: string;
	waitForWindowClose?: boolean;
J
Joao Moreno 已提交
46 47
}

J
renames  
Joao Moreno 已提交
48
export const IEnvironmentService = createDecorator<IEnvironmentService>('environmentService');
J
Joao Moreno 已提交
49

J
renames  
Joao Moreno 已提交
50
export interface IEnvironmentService {
J
Joao Moreno 已提交
51
	serviceId: ServiceIdentifier<any>;
J
Joao Moreno 已提交
52 53 54
	cliArgs: ICommandLineArguments;
	userExtensionsHome: string;
	isTestingFromCli: boolean;
J
Joao Moreno 已提交
55 56 57 58 59
	isBuilt: boolean;
	product: IProductConfiguration;
	updateUrl: string;
	quality: string;
	userHome: string;
J
Joao Moreno 已提交
60 61 62 63 64 65 66
	appRoot: string;
	currentWorkingDirectory: string;
	version: string;
	appHome: string;
	appSettingsHome: string;
	appSettingsPath: string;
	appKeybindingsPath: string;
J
Joao Moreno 已提交
67 68
	mainIPCHandle: string;
	sharedIPCHandle: string;
J
Joao Moreno 已提交
69 70
}

J
Joao Moreno 已提交
71 72 73 74 75 76 77 78 79 80 81 82 83 84
function getNumericValue(value: string, defaultValue: number, fallback: number = void 0) {
	const numericValue = parseInt(value);

	if (types.isNumber(numericValue)) {
		return numericValue;
	}

	if (value) {
		return defaultValue;
	}

	return fallback;
}

J
renames  
Joao Moreno 已提交
85
export class EnvService implements IEnvironmentService {
J
Joao Moreno 已提交
86

J
renames  
Joao Moreno 已提交
87
	serviceId = IEnvironmentService;
J
Joao Moreno 已提交
88

J
Joao Moreno 已提交
89
	private _cliArgs: ICommandLineArguments;
J
Joao Moreno 已提交
90 91
	get cliArgs(): ICommandLineArguments { return this._cliArgs; }

J
Joao Moreno 已提交
92
	private _userExtensionsHome: string;
J
Joao Moreno 已提交
93 94
	get userExtensionsHome(): string { return this._userExtensionsHome; }

J
Joao Moreno 已提交
95
	private _isTestingFromCli: boolean;
J
Joao Moreno 已提交
96 97 98 99
	get isTestingFromCli(): boolean { return this._isTestingFromCli; }

	get isBuilt(): boolean { return !process.env['VSCODE_DEV']; }

J
Joao Moreno 已提交
100 101 102
	get product(): IProductConfiguration { return product; }
	get updateUrl(): string { return product.updateUrl; }
	get quality(): string { return product.quality; }
J
Joao Moreno 已提交
103 104 105 106

	private _userHome: string;
	get userHome(): string { return this._userHome; }

J
Joao Moreno 已提交
107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127
	private _appRoot: string;
	get appRoot(): string { return this._appRoot; }

	private _currentWorkingDirectory: string;
	get currentWorkingDirectory(): string { return this._currentWorkingDirectory; }

	private _version: string;
	get version(): string { return this._version; }

	private _appHome: string;
	get appHome(): string { return this._appHome; }

	private _appSettingsHome: string;
	get appSettingsHome(): string { return this._appSettingsHome; }

	private _appSettingsPath: string;
	get appSettingsPath(): string { return this._appSettingsPath; }

	private _appKeybindingsPath: string;
	get appKeybindingsPath(): string { return this._appKeybindingsPath; }

J
Joao Moreno 已提交
128 129 130 131 132
	private _mainIPCHandle: string;
	get mainIPCHandle(): string { return this._mainIPCHandle; }

	private _sharedIPCHandle: string;
	get sharedIPCHandle(): string { return this._sharedIPCHandle; }
J
Joao Moreno 已提交
133

J
Joao Moreno 已提交
134
	constructor() {
J
Joao Moreno 已提交
135 136 137 138 139 140 141 142 143 144 145 146 147 148
		this._appRoot = path.dirname(uri.parse(require.toUrl('')).fsPath);
		this._currentWorkingDirectory = process.env['VSCODE_CWD'] || process.cwd();
		this._version = app.getVersion();
		this._appHome = app.getPath('userData');
		this._appSettingsHome = path.join(this._appHome, 'User');

		// TODO move out of here!
		if (!fs.existsSync(this._appSettingsHome)) {
			fs.mkdirSync(this._appSettingsHome);
		}

		this._appSettingsPath = path.join(this._appSettingsHome, 'settings.json');
		this._appKeybindingsPath = path.join(this._appSettingsHome, 'keybindings.json');

J
Joao Moreno 已提交
149 150 151
		// Remove the Electron executable
		let [, ...args] = process.argv;

J
Joao Moreno 已提交
152
		// If dev, remove the first non-option argument: it's the app location
J
Joao Moreno 已提交
153
		if (!this.isBuilt) {
J
Joao Moreno 已提交
154 155 156 157 158
			const index = arrays.firstIndex(args, a => !/^-/.test(a));

			if (index > -1) {
				args.splice(index, 1);
			}
J
Joao Moreno 已提交
159 160 161
		}

		// Finally, prepend any extra arguments from the 'argv' file
J
Joao Moreno 已提交
162 163
		if (fs.existsSync(path.join(this._appRoot, 'argv'))) {
			const extraargs: string[] = JSON.parse(fs.readFileSync(path.join(this._appRoot, 'argv'), 'utf8'));
J
Joao Moreno 已提交
164 165 166
			args = [...extraargs, ...args];
		}

J
Joao Moreno 已提交
167
		const argv = parseArgs(args);
J
Joao Moreno 已提交
168

J
Joao Moreno 已提交
169 170
		const debugBrkExtensionHostPort = getNumericValue(argv.debugBrkPluginHost, 5870);
		const debugExtensionHostPort = getNumericValue(argv.debugPluginHost, 5870, this.isBuilt ? void 0 : 5870);
J
Joao Moreno 已提交
171
		const pathArguments = parsePathArguments(this._currentWorkingDirectory, argv._, argv.goto);
J
Joao Moreno 已提交
172
		const timestamp = parseInt(argv.timestamp);
173
		const debugBrkFileWatcherPort = getNumericValue(argv.debugBrkFileWatcherPort, void 0);
J
Joao Moreno 已提交
174 175 176

		this._cliArgs = Object.freeze({
			pathArguments: pathArguments,
J
Joao Moreno 已提交
177
			programStart: types.isNumber(timestamp) ? timestamp : 0,
J
Joao Moreno 已提交
178 179
			enablePerformance: argv.performance,
			verboseLogging: argv.verbose,
J
Joao Moreno 已提交
180 181
			debugExtensionHostPort: debugBrkExtensionHostPort || debugExtensionHostPort,
			debugBrkExtensionHost: !!debugBrkExtensionHostPort,
J
Joao Moreno 已提交
182
			logExtensionHostCommunication: argv.logExtensionHostCommunication,
183
			debugBrkFileWatcherPort: debugBrkFileWatcherPort,
J
Joao Moreno 已提交
184 185 186 187
			openNewWindow: argv['new-window'],
			openInSameWindow: argv['reuse-window'],
			gotoLineMode: argv.goto,
			diffMode: argv.diff && pathArguments.length === 2,
J
Joao Moreno 已提交
188 189 190
			extensionsHomePath: normalizePath(argv.extensionHomePath),
			extensionDevelopmentPath: normalizePath(argv.extensionDevelopmentPath),
			extensionTestsPath: normalizePath(argv.extensionTestsPath),
J
Joao Moreno 已提交
191
			disableExtensions: argv['disable-extensions'],
J
Joao Moreno 已提交
192
			locale: argv.locale,
J
Joao Moreno 已提交
193
			waitForWindowClose: argv.wait
J
Joao Moreno 已提交
194 195
		});

J
Joao Moreno 已提交
196 197
		this._isTestingFromCli = this.cliArgs.extensionTestsPath && !this.cliArgs.debugBrkExtensionHost;

J
Joao Moreno 已提交
198
		this._userHome = path.join(app.getPath('home'), product.dataFolderName);
J
Joao Moreno 已提交
199 200

		// TODO move out of here!
J
Joao Moreno 已提交
201 202
		if (!fs.existsSync(this._userHome)) {
			fs.mkdirSync(this._userHome);
J
Joao Moreno 已提交
203 204
		}

J
Joao Moreno 已提交
205
		this._userExtensionsHome = this.cliArgs.extensionsHomePath || path.join(this._userHome, 'extensions');
J
Joao Moreno 已提交
206 207 208 209 210 211 212 213

		// TODO move out of here!
		if (!fs.existsSync(this._userExtensionsHome)) {
			fs.mkdirSync(this._userExtensionsHome);
		}

		this._mainIPCHandle = this.getMainIPCHandle();
		this._sharedIPCHandle = this.getSharedIPCHandle();
J
Joao Moreno 已提交
214
	}
J
Joao Moreno 已提交
215

J
Joao Moreno 已提交
216 217
	private getMainIPCHandle(): string {
		return this.getIPCHandleName() + (process.platform === 'win32' ? '-sock' : '.sock');
J
Joao Moreno 已提交
218 219
	}

J
Joao Moreno 已提交
220 221
	private getSharedIPCHandle(): string {
		return this.getIPCHandleName() + '-shared' + (process.platform === 'win32' ? '-sock' : '.sock');
J
Joao Moreno 已提交
222 223
	}

J
Joao Moreno 已提交
224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258
	private getIPCHandleName(): string {
		let handleName = app.getName();

		if (!this.isBuilt) {
			handleName += '-dev';
		}

		// Support to run VS Code multiple times as different user
		// by making the socket unique over the logged in user
		let userId = EnvService.getUniqueUserId();
		if (userId) {
			handleName += ('-' + userId);
		}

		if (process.platform === 'win32') {
			return '\\\\.\\pipe\\' + handleName;
		}

		return path.join(os.tmpdir(), handleName);
	}

	private static getUniqueUserId(): string {
		let username: string;
		if (platform.isWindows) {
			username = process.env.USERNAME;
		} else {
			username = process.env.USER;
		}

		if (!username) {
			return ''; // fail gracefully if there is no user name
		}

		// use sha256 to ensure the userid value can be used in filenames and are unique
		return crypto.createHash('sha256').update(username).digest('hex').substr(0, 6);
J
Joao Moreno 已提交
259 260
	}
}
E
Erich Gamma 已提交
261

J
Joao Moreno 已提交
262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300
function parsePathArguments(cwd: string, args: string[], gotoLineMode?: boolean): string[] {
	const result = args.map(arg => {
		let pathCandidate = arg;

		let parsedPath: IParsedPath;
		if (gotoLineMode) {
			parsedPath = parseLineAndColumnAware(arg);
			pathCandidate = parsedPath.path;
		}

		if (pathCandidate) {
			pathCandidate = preparePath(cwd, pathCandidate);
		}

		let realPath: string;
		try {
			realPath = fs.realpathSync(pathCandidate);
		} catch (error) {
			// in case of an error, assume the user wants to create this file
			// if the path is relative, we join it to the cwd
			realPath = path.normalize(path.isAbsolute(pathCandidate) ? pathCandidate : path.join(cwd, pathCandidate));
		}

		if (!paths.isValidBasename(path.basename(realPath))) {
			return null; // do not allow invalid file names
		}

		if (gotoLineMode) {
			parsedPath.path = realPath;
			return toLineAndColumnPath(parsedPath);
		}

		return realPath;
	});

	const caseInsensitive = platform.isWindows || platform.isMacintosh;
	const distinct = arrays.distinct(result, e => e && caseInsensitive ? e.toLowerCase() : e);

	return arrays.coalesce(distinct);
301
}
E
Erich Gamma 已提交
302

J
Joao Moreno 已提交
303
function preparePath(cwd: string, p: string): string {
B
polish  
Benjamin Pasero 已提交
304 305

	// Trim trailing quotes
306
	if (platform.isWindows) {
B
polish  
Benjamin Pasero 已提交
307
		p = strings.rtrim(p, '"'); // https://github.com/Microsoft/vscode/issues/1498
308
	}
309

310
	// Trim whitespaces
B
polish  
Benjamin Pasero 已提交
311
	p = strings.trim(strings.trim(p, ' '), '\t');
E
Erich Gamma 已提交
312

313
	if (platform.isWindows) {
B
polish  
Benjamin Pasero 已提交
314 315

		// Resolve the path against cwd if it is relative
J
Joao Moreno 已提交
316
		p = path.resolve(cwd, p);
B
polish  
Benjamin Pasero 已提交
317 318 319

		// Trim trailing '.' chars on Windows to prevent invalid file names
		p = strings.rtrim(p, '.');
320 321
	}

B
polish  
Benjamin Pasero 已提交
322
	return p;
E
Erich Gamma 已提交
323 324 325 326 327 328 329 330
}

function normalizePath(p?: string): string {
	return p ? path.normalize(p) : p;
}

export function getPlatformIdentifier(): string {
	if (process.platform === 'linux') {
331
		return `linux-${process.arch}`;
E
Erich Gamma 已提交
332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364
	}

	return process.platform;
}

export interface IParsedPath {
	path: string;
	line?: number;
	column?: number;
}

export function parseLineAndColumnAware(rawPath: string): IParsedPath {
	let segments = rawPath.split(':'); // C:\file.txt:<line>:<column>

	let path: string;
	let line: number = null;
	let column: number = null;

	segments.forEach(segment => {
		let segmentAsNumber = Number(segment);
		if (!types.isNumber(segmentAsNumber)) {
			path = !!path ? [path, segment].join(':') : segment; // a colon can well be part of a path (e.g. C:\...)
		} else if (line === null) {
			line = segmentAsNumber;
		} else if (column === null) {
			column = segmentAsNumber;
		}
	});

	return {
		path: path,
		line: line !== null ? line : void 0,
		column: column !== null ? column : line !== null ? 1 : void 0 // if we have a line, make sure column is also set
B
Benjamin Pasero 已提交
365
	};
E
Erich Gamma 已提交
366 367 368 369 370 371 372 373 374 375 376 377 378 379 380
}

export function toLineAndColumnPath(parsedPath: IParsedPath): string {
	let segments = [parsedPath.path];

	if (types.isNumber(parsedPath.line)) {
		segments.push(String(parsedPath.line));
	}

	if (types.isNumber(parsedPath.column)) {
		segments.push(String(parsedPath.column));
	}

	return segments.join(':');
}