main.js 15.4 KB
Newer Older
J
Joao Moreno 已提交
1 2 3 4
/*---------------------------------------------------------------------------------------------
 *  Copyright (c) Microsoft Corporation. All rights reserved.
 *  Licensed under the MIT License. See License.txt in the project root for license information.
 *--------------------------------------------------------------------------------------------*/
5 6
'use strict';

J
Joao Moreno 已提交
7
const perf = require('./vs/base/common/performance');
8 9
perf.mark('main:started');

B
Benjamin Pasero 已提交
10 11
Error.stackTraceLimit = 100; // increase number of stack frames (from 10, https://github.com/v8/v8/wiki/Stack-Trace-API)

J
Joao Moreno 已提交
12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
const fs = require('fs');
const path = require('path');
const product = require('../product.json');
const appRoot = path.dirname(__dirname);

function getApplicationPath() {
	if (process.env['VSCODE_DEV']) {
		return appRoot;
	} else if (process.platform === 'darwin') {
		return path.dirname(path.dirname(path.dirname(appRoot)));
	} else {
		return path.dirname(path.dirname(appRoot));
	}
}

27 28 29 30 31 32 33 34 35 36 37 38 39 40
function getPortableDataPath() {
	if (process.env['VSCODE_PORTABLE']) {
		return process.env['VSCODE_PORTABLE'];
	}

	if (process.platform === 'win32' || process.platform === 'linux') {
		return path.join(getApplicationPath(), 'data');
	} else {
		const portableDataName = product.portable || `${product.applicationName}-portable-data`;
		return path.join(path.dirname(getApplicationPath()), portableDataName);
	}
}

const portableDataPath = getPortableDataPath();
J
Joao Moreno 已提交
41 42 43 44 45 46 47 48
const isPortable = fs.existsSync(portableDataPath);
const portableTempPath = path.join(portableDataPath, 'tmp');
const isTempPortable = isPortable && fs.existsSync(portableTempPath);

if (isPortable) {
	process.env['VSCODE_PORTABLE'] = portableDataPath;
} else {
	delete process.env['VSCODE_PORTABLE'];
J
Joao Moreno 已提交
49 50
}

J
Joao Moreno 已提交
51 52
if (isTempPortable) {
	process.env[process.platform === 'win32' ? 'TEMP' : 'TMPDIR'] = portableTempPath;
J
Joao Moreno 已提交
53 54
}

A
Alex Dima 已提交
55 56 57 58 59 60 61 62
//#region Add support for using node_modules.asar
(function () {
	const path = require('path');
	const Module = require('module');
	const NODE_MODULES_PATH = path.join(__dirname, '../node_modules');
	const NODE_MODULES_ASAR_PATH = NODE_MODULES_PATH + '.asar';

	const originalResolveLookupPaths = Module._resolveLookupPaths;
A
Alex Dima 已提交
63 64
	Module._resolveLookupPaths = function (request, parent, newReturn) {
		const result = originalResolveLookupPaths(request, parent, newReturn);
A
Alex Dima 已提交
65

A
Alex Dima 已提交
66
		const paths = newReturn ? result : result[1];
A
Alex Dima 已提交
67 68 69 70 71 72 73 74 75 76 77 78
		for (let i = 0, len = paths.length; i < len; i++) {
			if (paths[i] === NODE_MODULES_PATH) {
				paths.splice(i, 0, NODE_MODULES_ASAR_PATH);
				break;
			}
		}

		return result;
	};
})();
//#endregion

J
Joao Moreno 已提交
79
const app = require('electron').app;
B
Benjamin Pasero 已提交
80

81 82 83 84 85 86 87
// TODO@Ben Electron 2.0.x: prevent localStorage migration from SQLite to LevelDB due to issues
app.commandLine.appendSwitch('disable-mojo-local-storage');

// TODO@Ben Electron 2.0.x: force srgb color profile (for https://github.com/Microsoft/vscode/issues/51791)
// This also seems to fix: https://github.com/Microsoft/vscode/issues/48043
app.commandLine.appendSwitch('force-color-profile', 'srgb');

J
Joao Moreno 已提交
88 89
const minimist = require('minimist');
const paths = require('./paths');
J
Joao Moreno 已提交
90

J
Joao Moreno 已提交
91
const args = minimist(process.argv, {
J
Joao Moreno 已提交
92 93 94 95 96 97
	string: [
		'user-data-dir',
		'locale',
		'js-flags',
		'max-memory'
	]
J
Joao Moreno 已提交
98 99
});

100 101 102 103 104 105 106 107 108 109 110 111 112
function getUserDataPath() {
	if (isPortable) {
		return path.join(portableDataPath, 'user-data');
	}

	return path.resolve(args['user-data-dir'] || paths.getDefaultUserDataPath(process.platform));
}

const userDataPath = getUserDataPath();

// Set userData path before app 'ready' event and call to process.chdir
app.setPath('userData', userDataPath);

J
Joao Moreno 已提交
113
//#region NLS
114
function stripComments(content) {
D
Dirk Baeumer 已提交
115 116
	let regexp = /("(?:[^\\\"]*(?:\\.)?)*")|('(?:[^\\\']*(?:\\.)?)*')|(\/\*(?:\r?\n|.)*?\*\/)|(\/{2,}.*?(?:(?:\r?\n)|$))/g;
	let result = content.replace(regexp, function (match, m1, m2, m3, m4) {
117 118 119 120
		// Only one of m1, m2, m3, m4 matches
		if (m3) {
			// A block comment. Replace with nothing
			return '';
D
Dirk Baeumer 已提交
121
		} else if (m4) {
122
			// A line comment. If it ends in \r?\n then keep it.
D
Dirk Baeumer 已提交
123
			let length_1 = m4.length;
124 125 126 127 128 129
			if (length_1 > 2 && m4[length_1 - 1] === '\n') {
				return m4[length_1 - 2] === '\r' ? '\r\n' : '\n';
			}
			else {
				return '';
			}
D
Dirk Baeumer 已提交
130
		} else {
131 132 133 134 135
			// We match a string
			return match;
		}
	});
	return result;
J
lint  
Joao Moreno 已提交
136
}
137

J
Johannes Rieken 已提交
138
const mkdir = dir => new Promise((c, e) => fs.mkdir(dir, err => (err && err.code !== 'EEXIST') ? e(err) : c(dir)));
J
Joao Moreno 已提交
139 140 141 142
const exists = file => new Promise(c => fs.exists(file, c));
const readFile = file => new Promise((c, e) => fs.readFile(file, 'utf8', (err, data) => err ? e(err) : c(data)));
const writeFile = (file, content) => new Promise((c, e) => fs.writeFile(file, content, 'utf8', err => err ? e(err) : c()));
const touch = file => new Promise((c, e) => { const d = new Date(); fs.utimes(file, d, d, err => err ? e(err) : c()); });
143 144 145 146
const lstat = file => new Promise((c, e) => fs.lstat(file, (err, stats) => err ? e(err) : c(stats)));
const readdir = dir => new Promise((c, e) => fs.readdir(dir, (err, files) => err ? e(err) : c(files)));
const rmdir = dir => new Promise((c, e) => fs.rmdir(dir, err => err ? e(err) : c(undefined)));
const unlink = file => new Promise((c, e) => fs.unlink(file, err => err ? e(err) : c(undefined)));
D
Dirk Baeumer 已提交
147 148

function mkdirp(dir) {
J
Joao Moreno 已提交
149 150 151
	return mkdir(dir).then(null, err => {
		if (err && err.code === 'ENOENT') {
			const parent = path.dirname(dir);
D
Dirk Baeumer 已提交
152

J
Joao Moreno 已提交
153 154
			if (parent !== dir) { // if not arrived at root
				return mkdirp(parent).then(() => mkdir(dir));
D
Dirk Baeumer 已提交
155
			}
J
Joao Moreno 已提交
156
		}
D
Dirk Baeumer 已提交
157

J
Joao Moreno 已提交
158
		throw err;
D
Dirk Baeumer 已提交
159 160 161
	});
}

162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178
function rimraf(location) {
	return lstat(location).then(stat => {
		if (stat.isDirectory() && !stat.isSymbolicLink()) {
			return readdir(location)
				.then(children => Promise.all(children.map(child => rimraf(path.join(location, child)))))
				.then(() => rmdir(location));
		} else {
			return unlink(location);
		}
	}, (err) => {
		if (err.code === 'ENOENT') {
			return void 0;
		}
		throw err;
	});
}

179
function resolveJSFlags(...jsFlags) {
J
Joao Moreno 已提交
180

181
	if (args['js-flags']) {
P
Peng Lyu 已提交
182
		jsFlags.push(args['js-flags']);
183
	}
J
Joao Moreno 已提交
184

P
Peng Lyu 已提交
185 186
	if (args['max-memory'] && !/max_old_space_size=(\d+)/g.exec(args['js-flags'])) {
		jsFlags.push(`--max_old_space_size=${args['max-memory']}`);
187
	}
J
Joao Moreno 已提交
188 189

	return jsFlags.length > 0 ? jsFlags.join(' ') : null;
190 191
}

D
Dirk Baeumer 已提交
192 193 194 195 196 197 198 199 200 201 202
// Language tags are case insensitve however an amd loader is case sensitive
// To make this work on case preserving & insensitive FS we do the following:
// the language bundles have lower case language tags and we always lower case
// the locale we receive from the user or OS.

function getUserDefinedLocale() {
	let locale = args['locale'];
	if (locale) {
		return Promise.resolve(locale.toLowerCase());
	}

203
	let localeConfig = path.join(userDataPath, 'User', 'locale.json');
D
Dirk Baeumer 已提交
204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221
	return exists(localeConfig).then((result) => {
		if (result) {
			return readFile(localeConfig).then((content) => {
				content = stripComments(content);
				try {
					let value = JSON.parse(content).locale;
					return value && typeof value === 'string' ? value.toLowerCase() : undefined;
				} catch (e) {
					return undefined;
				}
			});
		} else {
			return undefined;
		}
	});
}

function getLanguagePackConfigurations() {
222
	let configFile = path.join(userDataPath, 'languagepacks.json');
D
Dirk Baeumer 已提交
223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242
	try {
		return require(configFile);
	} catch (err) {
		// Do nothing. If we can't read the file we have no
		// language pack config.
	}
	return undefined;
}

function resolveLanguagePackLocale(config, locale) {
	try {
		while (locale) {
			if (config[locale]) {
				return locale;
			} else {
				let index = locale.lastIndexOf('-');
				if (index > 0) {
					locale = locale.substring(0, index);
				} else {
					return undefined;
243 244 245
				}
			}
		}
D
Dirk Baeumer 已提交
246 247
	} catch (err) {
		console.error('Resolving language pack configuration failed.', err);
248
	}
D
Dirk Baeumer 已提交
249 250
	return undefined;
}
251

D
Dirk Baeumer 已提交
252
function getNLSConfiguration(locale) {
J
Joao Moreno 已提交
253
	if (locale === 'pseudo') {
D
Dirk Baeumer 已提交
254
		return Promise.resolve({ locale: locale, availableLanguages: {}, pseudo: true });
J
Joao Moreno 已提交
255
	}
D
Dirk Baeumer 已提交
256

J
Joao Moreno 已提交
257
	if (process.env['VSCODE_DEV']) {
D
Dirk Baeumer 已提交
258
		return Promise.resolve({ locale: locale, availableLanguages: {} });
J
Joao Moreno 已提交
259
	}
260

J
Joao Moreno 已提交
261 262
	// We have a built version so we have extracted nls file. Try to find
	// the right file to use.
263 264 265 266

	// Check if we have an English locale. If so fall to default since that is our
	// English translation (we don't ship *.nls.en.json files)
	if (locale && (locale == 'en' || locale.startsWith('en-'))) {
D
Dirk Baeumer 已提交
267
		return Promise.resolve({ locale: locale, availableLanguages: {} });
268 269
	}

D
Dirk Baeumer 已提交
270 271
	let initialLocale = locale;

272
	perf.mark('nlsGeneration:start');
273

J
Johannes Rieken 已提交
274
	let defaultResult = function (locale) {
275 276
		perf.mark('nlsGeneration:end');
		return Promise.resolve({ locale: locale, availableLanguages: {} });
277 278
	};
	try {
J
Joao Moreno 已提交
279
		let commit = product.commit;
280
		if (!commit) {
281
			return defaultResult(initialLocale);
282 283 284
		}
		let configs = getLanguagePackConfigurations();
		if (!configs) {
285
			return defaultResult(initialLocale);
286 287 288 289 290 291 292 293
		}
		locale = resolveLanguagePackLocale(configs, locale);
		if (!locale) {
			return defaultResult(initialLocale);
		}
		let packConfig = configs[locale];
		let mainPack;
		if (!packConfig || typeof packConfig.hash !== 'string' || !packConfig.translations || typeof (mainPack = packConfig.translations['vscode']) !== 'string') {
294
			return defaultResult(initialLocale);
295 296 297
		}
		return exists(mainPack).then((fileExists) => {
			if (!fileExists) {
298
				return defaultResult(initialLocale);
D
Dirk Baeumer 已提交
299
			}
300
			let packId = packConfig.hash + '.' + locale;
301
			let cacheRoot = path.join(userDataPath, 'clp', packId);
302 303
			let coreLocation = path.join(cacheRoot, commit);
			let translationsConfigFile = path.join(cacheRoot, 'tcf.json');
304
			let corruptedFile = path.join(cacheRoot, 'corrupted.info');
305 306 307 308 309 310
			let result = {
				locale: initialLocale,
				availableLanguages: { '*': locale },
				_languagePackId: packId,
				_translationsConfigFile: translationsConfigFile,
				_cacheRoot: cacheRoot,
311 312
				_resolvedLanguagePackCoreLocation: coreLocation,
				_corruptedFile: corruptedFile
313
			};
314 315 316 317 318 319 320
			return exists(corruptedFile).then((corrupted) => {
				// The nls cache directory is corrupted.
				let toDelete;
				if (corrupted) {
					toDelete = rimraf(cacheRoot);
				} else {
					toDelete = Promise.resolve(undefined);
D
Dirk Baeumer 已提交
321
				}
322 323 324 325 326 327 328 329 330 331 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
				return toDelete.then(() => {
					return exists(coreLocation).then((fileExists) => {
						if (fileExists) {
							// We don't wait for this. No big harm if we can't touch
							touch(coreLocation).catch(() => { });
							perf.mark('nlsGeneration:end');
							return result;
						}
						return mkdirp(coreLocation).then(() => {
							return Promise.all([readFile(path.join(__dirname, 'nls.metadata.json')), readFile(mainPack)]);
						}).then((values) => {
							let metadata = JSON.parse(values[0]);
							let packData = JSON.parse(values[1]).contents;
							let bundles = Object.keys(metadata.bundles);
							let writes = [];
							for (let bundle of bundles) {
								let modules = metadata.bundles[bundle];
								let target = Object.create(null);
								for (let module of modules) {
									let keys = metadata.keys[module];
									let defaultMessages = metadata.messages[module];
									let translations = packData[module];
									let targetStrings;
									if (translations) {
										targetStrings = [];
										for (let i = 0; i < keys.length; i++) {
											let elem = keys[i];
											let key = typeof elem === 'string' ? elem : elem.key;
											let translatedMessage = translations[key];
											if (translatedMessage === undefined) {
												translatedMessage = defaultMessages[i];
											}
											targetStrings.push(translatedMessage);
										}
									} else {
										targetStrings = defaultMessages;
D
Dirk Baeumer 已提交
358
									}
359
									target[module] = targetStrings;
D
Dirk Baeumer 已提交
360
								}
361
								writes.push(writeFile(path.join(coreLocation, bundle.replace(/\//g, '!') + '.nls.json'), JSON.stringify(target)));
D
Dirk Baeumer 已提交
362
							}
363 364 365 366 367 368 369 370 371 372
							writes.push(writeFile(translationsConfigFile, JSON.stringify(packConfig.translations)));
							return Promise.all(writes);
						}).then(() => {
							perf.mark('nlsGeneration:end');
							return result;
						}).catch((err) => {
							console.error('Generating translation files failed.', err);
							return defaultResult(locale);
						});
					});
D
Dirk Baeumer 已提交
373 374
				});
			});
375 376 377 378
		});
	} catch (err) {
		console.error('Generating translation files failed.', err);
		return defaultResult(locale);
379
	}
J
Joao Moreno 已提交
380
}
J
Joao Moreno 已提交
381
//#endregion
J
Joao Moreno 已提交
382

J
Joao Moreno 已提交
383
//#region Cached Data Dir
384 385 386 387
const nodeCachedDataDir = new class {

	constructor() {
		this.value = this._compute();
388
	}
389

390 391
	jsFlags() {
		return this.value ? '--nolazy' : undefined;
392 393
	}

394 395
	ensureExists() {
		return mkdirp(this.value).then(() => this.value, () => { /*ignore*/ });
396 397
	}

398 399 400 401 402 403 404 405 406 407 408 409 410
	_compute() {
		if (process.argv.indexOf('--no-cached-data') > 0) {
			return undefined;
		}
		// IEnvironmentService.isBuilt
		if (process.env['VSCODE_DEV']) {
			return undefined;
		}
		// find commit id
		let commit = product.commit;
		if (!commit) {
			return undefined;
		}
411
		return path.join(userDataPath, 'CachedData', commit);
412 413
	}
};
414

J
Joao Moreno 已提交
415
//#endregion
416

B
polish  
Benjamin Pasero 已提交
417
// Update cwd based on environment and platform
J
Joao Moreno 已提交
418
try {
B
polish  
Benjamin Pasero 已提交
419
	if (process.platform === 'win32') {
D
Dirk Baeumer 已提交
420
		process.env['VSCODE_CWD'] = process.cwd(); // remember as environment letiable
B
polish  
Benjamin Pasero 已提交
421
		process.chdir(path.dirname(app.getPath('exe'))); // always set application folder as cwd
J
Joao Moreno 已提交
422 423
	} else if (process.env['VSCODE_CWD']) {
		process.chdir(process.env['VSCODE_CWD']);
J
Joao Moreno 已提交
424 425
	}
} catch (err) {
B
polish  
Benjamin Pasero 已提交
426
	console.error(err);
J
Joao Moreno 已提交
427 428 429 430 431
}

// Mac: when someone drops a file to the not-yet running VSCode, the open-file event fires even before
// the app-ready event. We listen very early for open-file and remember this upon startup as path to open.
global.macOpenFiles = [];
B
polish  
Benjamin Pasero 已提交
432
app.on('open-file', function (event, path) {
J
Joao Moreno 已提交
433 434 435
	global.macOpenFiles.push(path);
});

D
Dirk Baeumer 已提交
436 437
let openUrls = [];
let onOpenUrl = function (event, url) {
J
Joao Moreno 已提交
438 439 440 441
	event.preventDefault();
	openUrls.push(url);
};

442 443 444
app.on('will-finish-launching', function () {
	app.on('open-url', onOpenUrl);
});
J
Joao Moreno 已提交
445

446
global.getOpenUrls = function () {
J
Joao Moreno 已提交
447 448 449 450
	app.removeListener('open-url', onOpenUrl);
	return openUrls;
};

D
Dirk Baeumer 已提交
451 452 453 454 455 456 457

let nlsConfiguration = undefined;
let userDefinedLocale = getUserDefinedLocale();
userDefinedLocale.then((locale) => {
	if (locale && !nlsConfiguration) {
		nlsConfiguration = getNLSConfiguration(locale);
	}
458 459
});

460
let jsFlags = resolveJSFlags(nodeCachedDataDir.jsFlags());
461 462 463 464
if (jsFlags) {
	app.commandLine.appendSwitch('--js-flags', jsFlags);
}

465 466
// Load our code once ready
app.once('ready', function () {
467
	perf.mark('main:appReady');
468
	Promise.all([nodeCachedDataDir.ensureExists(), userDefinedLocale]).then(([cachedDataDir, locale]) => {
D
Dirk Baeumer 已提交
469 470 471 472 473 474 475 476 477 478 479
		if (locale && !nlsConfiguration) {
			nlsConfiguration = getNLSConfiguration(locale);
		}
		if (!nlsConfiguration) {
			nlsConfiguration = Promise.resolve(undefined);
		}
		// We first need to test a user defined locale. If it fails we try the app locale.
		// If that fails we fall back to English.
		nlsConfiguration.then((nlsConfig) => {
			let boot = (nlsConfig) => {
				process.env['VSCODE_NLS_CONFIG'] = JSON.stringify(nlsConfig);
480
				if (cachedDataDir) process.env['VSCODE_NODE_CACHED_DATA_DIR_' + process.pid] = cachedDataDir;
D
Dirk Baeumer 已提交
481 482 483 484 485 486 487 488 489 490 491 492 493 494
				require('./bootstrap-amd').bootstrap('vs/code/electron-main/main');
			};
			// We recevied a valid nlsConfig from a user defined locale
			if (nlsConfig) {
				boot(nlsConfig);
			} else {
				// Try to use the app locale. Please note that the app locale is only
				// valid after we have received the app ready event. This is why the
				// code is here.
				let appLocale = app.getLocale();
				if (!appLocale) {
					boot({ locale: 'en', availableLanguages: {} });
				} else {
					// See above the comment about the loader and case sensitiviness
495
					appLocale = appLocale.toLowerCase();
D
Dirk Baeumer 已提交
496 497 498 499 500 501 502 503 504
					getNLSConfiguration(appLocale).then((nlsConfig) => {
						if (!nlsConfig) {
							nlsConfig = { locale: appLocale, availableLanguages: {} };
						}
						boot(nlsConfig);
					});
				}
			}
		});
505
	}, console.error);
506
});