main.js 15.1 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');

J
Joao Moreno 已提交
10
// Perf measurements
11
global.perfStartTime = Date.now();
J
Joao Moreno 已提交
12

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

J
Joao Moreno 已提交
15 16 17 18 19 20 21 22 23 24 25 26 27 28 29
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));
	}
}

30 31 32 33 34 35 36 37 38 39 40 41 42 43
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 已提交
44 45 46 47 48 49 50 51
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 已提交
52 53
}

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

A
Alex Dima 已提交
58 59 60 61 62 63 64 65
//#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 已提交
66 67
	Module._resolveLookupPaths = function (request, parent, newReturn) {
		const result = originalResolveLookupPaths(request, parent, newReturn);
A
Alex Dima 已提交
68

A
Alex Dima 已提交
69
		const paths = newReturn ? result : result[1];
A
Alex Dima 已提交
70 71 72 73 74 75 76 77 78 79 80 81
		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 已提交
82
const app = require('electron').app;
B
Benjamin Pasero 已提交
83

J
Joao Moreno 已提交
84 85
const minimist = require('minimist');
const paths = require('./paths');
J
Joao Moreno 已提交
86

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

96 97 98 99 100 101 102 103 104 105 106 107 108
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 已提交
109
//#region NLS
110
function stripComments(content) {
D
Dirk Baeumer 已提交
111 112
	let regexp = /("(?:[^\\\"]*(?:\\.)?)*")|('(?:[^\\\']*(?:\\.)?)*')|(\/\*(?:\r?\n|.)*?\*\/)|(\/{2,}.*?(?:(?:\r?\n)|$))/g;
	let result = content.replace(regexp, function (match, m1, m2, m3, m4) {
113 114 115 116
		// Only one of m1, m2, m3, m4 matches
		if (m3) {
			// A block comment. Replace with nothing
			return '';
D
Dirk Baeumer 已提交
117
		} else if (m4) {
118
			// A line comment. If it ends in \r?\n then keep it.
D
Dirk Baeumer 已提交
119
			let length_1 = m4.length;
120 121 122 123 124 125
			if (length_1 > 2 && m4[length_1 - 1] === '\n') {
				return m4[length_1 - 2] === '\r' ? '\r\n' : '\n';
			}
			else {
				return '';
			}
D
Dirk Baeumer 已提交
126
		} else {
127 128 129 130 131
			// We match a string
			return match;
		}
	});
	return result;
J
lint  
Joao Moreno 已提交
132
}
133

J
Johannes Rieken 已提交
134
const mkdir = dir => new Promise((c, e) => fs.mkdir(dir, err => (err && err.code !== 'EEXIST') ? e(err) : c(dir)));
J
Joao Moreno 已提交
135 136 137 138
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()); });
139 140 141 142
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 已提交
143 144

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

J
Joao Moreno 已提交
149 150
			if (parent !== dir) { // if not arrived at root
				return mkdirp(parent).then(() => mkdir(dir));
D
Dirk Baeumer 已提交
151
			}
J
Joao Moreno 已提交
152
		}
D
Dirk Baeumer 已提交
153

J
Joao Moreno 已提交
154
		throw err;
D
Dirk Baeumer 已提交
155 156 157
	});
}

158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174
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;
	});
}

175
function resolveJSFlags(...jsFlags) {
J
Joao Moreno 已提交
176

177
	if (args['js-flags']) {
P
Peng Lyu 已提交
178
		jsFlags.push(args['js-flags']);
179
	}
J
Joao Moreno 已提交
180

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

	return jsFlags.length > 0 ? jsFlags.join(' ') : null;
186 187
}

D
Dirk Baeumer 已提交
188 189 190 191 192 193 194 195 196 197 198
// 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());
	}

199
	let localeConfig = path.join(userDataPath, 'User', 'locale.json');
D
Dirk Baeumer 已提交
200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217
	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() {
218
	let configFile = path.join(userDataPath, 'languagepacks.json');
D
Dirk Baeumer 已提交
219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238
	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;
239 240 241
				}
			}
		}
D
Dirk Baeumer 已提交
242 243
	} catch (err) {
		console.error('Resolving language pack configuration failed.', err);
244
	}
D
Dirk Baeumer 已提交
245 246
	return undefined;
}
247

D
Dirk Baeumer 已提交
248
function getNLSConfiguration(locale) {
J
Joao Moreno 已提交
249
	if (locale === 'pseudo') {
D
Dirk Baeumer 已提交
250
		return Promise.resolve({ locale: locale, availableLanguages: {}, pseudo: true });
J
Joao Moreno 已提交
251
	}
D
Dirk Baeumer 已提交
252

J
Joao Moreno 已提交
253
	if (process.env['VSCODE_DEV']) {
D
Dirk Baeumer 已提交
254
		return Promise.resolve({ locale: locale, availableLanguages: {} });
J
Joao Moreno 已提交
255
	}
256

J
Joao Moreno 已提交
257 258
	// We have a built version so we have extracted nls file. Try to find
	// the right file to use.
259 260 261 262

	// 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 已提交
263
		return Promise.resolve({ locale: locale, availableLanguages: {} });
264 265
	}

D
Dirk Baeumer 已提交
266 267
	let initialLocale = locale;

268
	perf.mark('nlsGeneration:start');
269

J
Johannes Rieken 已提交
270
	let defaultResult = function (locale) {
271 272
		perf.mark('nlsGeneration:end');
		return Promise.resolve({ locale: locale, availableLanguages: {} });
273 274
	};
	try {
J
Joao Moreno 已提交
275
		let commit = product.commit;
276
		if (!commit) {
277
			return defaultResult(initialLocale);
278 279 280
		}
		let configs = getLanguagePackConfigurations();
		if (!configs) {
281
			return defaultResult(initialLocale);
282 283 284 285 286 287 288 289
		}
		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') {
290
			return defaultResult(initialLocale);
291 292 293
		}
		return exists(mainPack).then((fileExists) => {
			if (!fileExists) {
294
				return defaultResult(initialLocale);
D
Dirk Baeumer 已提交
295
			}
296
			let packId = packConfig.hash + '.' + locale;
297
			let cacheRoot = path.join(userDataPath, 'clp', packId);
298 299
			let coreLocation = path.join(cacheRoot, commit);
			let translationsConfigFile = path.join(cacheRoot, 'tcf.json');
300
			let corruptedFile = path.join(cacheRoot, 'corrupted.info');
301 302 303 304 305 306
			let result = {
				locale: initialLocale,
				availableLanguages: { '*': locale },
				_languagePackId: packId,
				_translationsConfigFile: translationsConfigFile,
				_cacheRoot: cacheRoot,
307 308
				_resolvedLanguagePackCoreLocation: coreLocation,
				_corruptedFile: corruptedFile
309
			};
310 311 312 313 314 315 316
			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 已提交
317
				}
318 319 320 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
				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 已提交
354
									}
355
									target[module] = targetStrings;
D
Dirk Baeumer 已提交
356
								}
357
								writes.push(writeFile(path.join(coreLocation, bundle.replace(/\//g, '!') + '.nls.json'), JSON.stringify(target)));
D
Dirk Baeumer 已提交
358
							}
359 360 361 362 363 364 365 366 367 368
							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 已提交
369 370
				});
			});
371 372 373 374
		});
	} catch (err) {
		console.error('Generating translation files failed.', err);
		return defaultResult(locale);
375
	}
J
Joao Moreno 已提交
376
}
J
Joao Moreno 已提交
377
//#endregion
J
Joao Moreno 已提交
378

J
Joao Moreno 已提交
379
//#region Cached Data Dir
380 381 382 383
const nodeCachedDataDir = new class {

	constructor() {
		this.value = this._compute();
384
	}
385

386 387
	jsFlags() {
		return this.value ? '--nolazy' : undefined;
388 389
	}

390 391
	ensureExists() {
		return mkdirp(this.value).then(() => this.value, () => { /*ignore*/ });
392 393
	}

394 395 396 397 398 399 400 401 402 403 404 405 406
	_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;
		}
407
		return path.join(userDataPath, 'CachedData', commit);
408 409
	}
};
410

J
Joao Moreno 已提交
411
//#endregion
412

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

// 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 已提交
428
app.on('open-file', function (event, path) {
J
Joao Moreno 已提交
429 430 431
	global.macOpenFiles.push(path);
});

D
Dirk Baeumer 已提交
432 433
let openUrls = [];
let onOpenUrl = function (event, url) {
J
Joao Moreno 已提交
434 435 436 437
	event.preventDefault();
	openUrls.push(url);
};

438 439 440
app.on('will-finish-launching', function () {
	app.on('open-url', onOpenUrl);
});
J
Joao Moreno 已提交
441

442
global.getOpenUrls = function () {
J
Joao Moreno 已提交
443 444 445 446
	app.removeListener('open-url', onOpenUrl);
	return openUrls;
};

D
Dirk Baeumer 已提交
447 448 449 450 451 452 453

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

456
let jsFlags = resolveJSFlags(nodeCachedDataDir.jsFlags());
457 458 459 460
if (jsFlags) {
	app.commandLine.appendSwitch('--js-flags', jsFlags);
}

461 462
// Load our code once ready
app.once('ready', function () {
463
	perf.mark('main:appReady');
464
	Promise.all([nodeCachedDataDir.ensureExists(), userDefinedLocale]).then(([cachedDataDir, locale]) => {
D
Dirk Baeumer 已提交
465 466 467 468 469 470 471 472 473 474 475
		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);
476
				if (cachedDataDir) process.env['VSCODE_NODE_CACHED_DATA_DIR_' + process.pid] = cachedDataDir;
D
Dirk Baeumer 已提交
477 478 479 480 481 482 483 484 485 486 487 488 489 490
				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
491
					appLocale = appLocale.toLowerCase();
D
Dirk Baeumer 已提交
492 493 494 495 496 497 498 499 500
					getNLSConfiguration(appLocale).then((nlsConfig) => {
						if (!nlsConfig) {
							nlsConfig = { locale: appLocale, availableLanguages: {} };
						}
						boot(nlsConfig);
					});
				}
			}
		});
501
	}, console.error);
502
});