main.js 15.9 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

//@ts-check
7 8
'use strict';

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

J
Joao Moreno 已提交
12 13
const fs = require('fs');
const path = require('path');
14 15
const bootstrap = require('./bootstrap');
const paths = require('./paths');
16 17 18 19
// @ts-ignore
const product = require('../product.json');
// @ts-ignore
const app = require('electron').app;
J
Joao Moreno 已提交
20

21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37
// Enable portable support
const portable = bootstrap.configurePortable();

// Enable ASAR support
bootstrap.enableASARSupport();

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

// Update cwd based on environment and platform
setCurrentWorkingDirectory();

// Global app listeners
registerListeners();

38 39 40 41 42
/**
 * Support user defined locale
 *
 * @type {Promise}
 */
43
let nlsConfiguration = undefined;
44
const userDefinedLocale = getUserDefinedLocale();
45 46 47 48 49 50 51
userDefinedLocale.then((locale) => {
	if (locale && !nlsConfiguration) {
		nlsConfiguration = getNLSConfiguration(locale);
	}
});

// Configure command line switches
52
const nodeCachedDataDir = getNodeCachedDir();
53 54 55 56
configureCommandlineSwitches(args, nodeCachedDataDir);

// Load our code once ready
app.once('ready', function () {
B
Benjamin Pasero 已提交
57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72
	if (args['trace']) {
		// @ts-ignore
		const contentTracing = require('electron').contentTracing;

		const traceOptions = {
			categoryFilter: args['trace-category-filter'] || '*',
			traceOptions: args['trace-options'] || 'record-until-full,enable-sampling'
		};

		contentTracing.startRecording(traceOptions, () => onReady());
	} else {
		onReady();
	}
});

function onReady() {
73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90
	perf.mark('main:appReady');

	Promise.all([nodeCachedDataDir.ensureExists(), userDefinedLocale]).then(([cachedDataDir, locale]) => {
		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) => {

			const startup = nlsConfig => {
				nlsConfig._languagePackSupport = true;
				process.env['VSCODE_NLS_CONFIG'] = JSON.stringify(nlsConfig);
91
				process.env['VSCODE_NODE_CACHED_DATA_DIR'] = cachedDataDir || '';
92

93
				// Load main in AMD
94 95 96 97 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
				require('./bootstrap-amd').load('vs/code/electron-main/main');
			};

			// We recevied a valid nlsConfig from a user defined locale
			if (nlsConfig) {
				startup(nlsConfig);
			}

			// 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.
			else {
				let appLocale = app.getLocale();
				if (!appLocale) {
					startup({ locale: 'en', availableLanguages: {} });
				} else {

					// See above the comment about the loader and case sensitiviness
					appLocale = appLocale.toLowerCase();

					getNLSConfiguration(appLocale).then((nlsConfig) => {
						if (!nlsConfig) {
							nlsConfig = { locale: appLocale, availableLanguages: {} };
						}

						startup(nlsConfig);
					});
				}
			}
		});
	}, console.error);
B
Benjamin Pasero 已提交
125
}
126

127 128 129 130 131 132
/**
 * @typedef {import('minimist').ParsedArgs} ParsedArgs
 *
 * @param {ParsedArgs} cliArgs
 * @param {{ jsFlags: () => string }} nodeCachedDataDir
 */
133 134 135 136 137 138 139 140 141 142 143 144
function configureCommandlineSwitches(cliArgs, nodeCachedDataDir) {

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

	// Force pre-Chrome-60 color profile handling (for https://github.com/Microsoft/vscode/issues/51791)
	app.commandLine.appendSwitch('disable-features', 'ColorCorrectRendering');

	// Support JS Flags
	const jsFlags = resolveJSFlags(cliArgs, nodeCachedDataDir.jsFlags());
	if (jsFlags) {
		app.commandLine.appendSwitch('--js-flags', jsFlags);
J
Joao Moreno 已提交
145 146 147
	}
}

148
/**
149 150
 * @param {ParsedArgs} cliArgs
 * @param {string[]} jsFlags
151 152
 * @returns {string}
 */
153 154 155
function resolveJSFlags(cliArgs, ...jsFlags) {
	if (cliArgs['js-flags']) {
		jsFlags.push(cliArgs['js-flags']);
156 157
	}

158 159
	if (cliArgs['max-memory'] && !/max_old_space_size=(\d+)/g.exec(cliArgs['js-flags'])) {
		jsFlags.push(`--max_old_space_size=${cliArgs['max-memory']}`);
160
	}
161 162

	return jsFlags.length > 0 ? jsFlags.join(' ') : null;
163 164
}

165
/**
166 167
 * @param {ParsedArgs} cliArgs
 *
168 169
 * @returns {string}
 */
170 171 172 173
function getUserDataPath(cliArgs) {
	if (portable.isPortable) {
		return path.join(portable.portableDataPath, 'user-data');
	}
J
Joao Moreno 已提交
174

175
	return path.resolve(cliArgs['user-data-dir'] || paths.getDefaultUserDataPath(process.platform));
J
Joao Moreno 已提交
176 177
}

178 179 180
/**
 * @returns {ParsedArgs}
 */
181
function parseCLIArgs() {
B
Benjamin Pasero 已提交
182
	const minimist = require('minimist');
183

184 185 186 187 188 189 190 191
	return minimist(process.argv, {
		string: [
			'user-data-dir',
			'locale',
			'js-flags',
			'max-memory'
		]
	});
J
Joao Moreno 已提交
192 193
}

194 195 196 197 198 199 200
function setCurrentWorkingDirectory() {
	try {
		if (process.platform === 'win32') {
			process.env['VSCODE_CWD'] = process.cwd(); // remember as environment letiable
			process.chdir(path.dirname(app.getPath('exe'))); // always set application folder as cwd
		} else if (process.env['VSCODE_CWD']) {
			process.chdir(process.env['VSCODE_CWD']);
A
Alex Dima 已提交
201
		}
202 203 204 205
	} catch (err) {
		console.error(err);
	}
}
A
Alex Dima 已提交
206

207
function registerListeners() {
A
Alex Dima 已提交
208

209 210 211 212 213 214
	/**
	 * 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.
	 *
	 * @type {string[]}
	 */
215 216
	const macOpenFiles = [];
	global['macOpenFiles'] = macOpenFiles;
217
	app.on('open-file', function (event, path) {
218
		macOpenFiles.push(path);
219
	});
220

221 222 223 224 225
	/**
	 * React to open-url requests.
	 *
	 * @type {string[]}
	 */
226 227 228
	const openUrls = [];
	const onOpenUrl = function (event, url) {
		event.preventDefault();
229

230 231
		openUrls.push(url);
	};
J
Joao Moreno 已提交
232

233 234 235
	app.on('will-finish-launching', function () {
		app.on('open-url', onOpenUrl);
	});
J
Joao Moreno 已提交
236

237
	global['getOpenUrls'] = function () {
238
		app.removeListener('open-url', onOpenUrl);
239

240 241
		return openUrls;
	};
242 243
}

244
/**
245
 * @returns {{ jsFlags: () => string; ensureExists: () => Promise<string | void>, _compute: () => string; }}
246
 */
247
function getNodeCachedDir() {
248
	return new class {
249

250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265
		constructor() {
			this.value = this._compute();
		}

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

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

		_compute() {
			if (process.argv.indexOf('--no-cached-data') > 0) {
				return undefined;
			}
266

267 268 269 270
			// IEnvironmentService.isBuilt
			if (process.env['VSCODE_DEV']) {
				return undefined;
			}
271

272
			// find commit id
273
			const commit = product.commit;
274 275 276
			if (!commit) {
				return undefined;
			}
277

278 279 280 281
			return path.join(userDataPath, 'CachedData', commit);
		}
	};
}
282

283
//#region NLS Support
284 285 286 287
/**
 * @param {string} content
 * @returns {string}
 */
288
function stripComments(content) {
289 290 291
	const regexp = /("(?:[^\\\"]*(?:\\.)?)*")|('(?:[^\\\']*(?:\\.)?)*')|(\/\*(?:\r?\n|.)*?\*\/)|(\/{2,}.*?(?:(?:\r?\n)|$))/g;

	return content.replace(regexp, function (match, m1, m2, m3, m4) {
292 293 294 295
		// Only one of m1, m2, m3, m4 matches
		if (m3) {
			// A block comment. Replace with nothing
			return '';
D
Dirk Baeumer 已提交
296
		} else if (m4) {
297
			// A line comment. If it ends in \r?\n then keep it.
298
			const length_1 = m4.length;
299 300 301 302 303 304
			if (length_1 > 2 && m4[length_1 - 1] === '\n') {
				return m4[length_1 - 2] === '\r' ? '\r\n' : '\n';
			}
			else {
				return '';
			}
D
Dirk Baeumer 已提交
305
		} else {
306 307 308 309
			// We match a string
			return match;
		}
	});
J
lint  
Joao Moreno 已提交
310
}
311

312 313 314 315 316 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
/**
 * @param {string} dir
 * @returns {Promise<string>}
 */
function mkdir(dir) {
	return new Promise((c, e) => fs.mkdir(dir, err => (err && err.code !== 'EEXIST') ? e(err) : c(dir)));
}

/**
 * @param {string} file
 * @returns {Promise<boolean>}
 */
function exists(file) {
	return new Promise(c => fs.exists(file, c));
}

/**
 * @param {string} file
 * @returns {Promise<void>}
 */
function touch(file) {
	return new Promise((c, e) => { const d = new Date(); fs.utimes(file, d, d, err => err ? e(err) : c()); });
}

/**
 * @param {string} file
 * @returns {Promise<object>}
 */
function lstat(file) {
	return new Promise((c, e) => fs.lstat(file, (err, stats) => err ? e(err) : c(stats)));
}

/**
 * @param {string} dir
 * @returns {Promise<string[]>}
 */
function readdir(dir) {
	return new Promise((c, e) => fs.readdir(dir, (err, files) => err ? e(err) : c(files)));
}
D
Dirk Baeumer 已提交
351

352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371
/**
 * @param {string} dir
 * @returns {Promise<void>}
 */
function rmdir(dir) {
	return new Promise((c, e) => fs.rmdir(dir, err => err ? e(err) : c(undefined)));
}

/**
 * @param {string} file
 * @returns {Promise<void>}
 */
function unlink(file) {
	return new Promise((c, e) => fs.unlink(file, err => err ? e(err) : c(undefined)));
}

/**
 * @param {string} dir
 * @returns {Promise<string>}
 */
D
Dirk Baeumer 已提交
372
function mkdirp(dir) {
J
Joao Moreno 已提交
373 374 375
	return mkdir(dir).then(null, err => {
		if (err && err.code === 'ENOENT') {
			const parent = path.dirname(dir);
D
Dirk Baeumer 已提交
376

J
Joao Moreno 已提交
377 378
			if (parent !== dir) { // if not arrived at root
				return mkdirp(parent).then(() => mkdir(dir));
D
Dirk Baeumer 已提交
379
			}
J
Joao Moreno 已提交
380
		}
D
Dirk Baeumer 已提交
381

J
Joao Moreno 已提交
382
		throw err;
D
Dirk Baeumer 已提交
383 384 385
	});
}

386 387 388 389
/**
 * @param {string} location
 * @returns {Promise<void>}
 */
390 391 392 393 394 395 396 397 398
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);
		}
399
	}, err => {
400 401 402 403 404 405 406
		if (err.code === 'ENOENT') {
			return void 0;
		}
		throw err;
	});
}

J
Joshua 已提交
407
// Language tags are case insensitive however an amd loader is case sensitive
D
Dirk Baeumer 已提交
408 409 410
// 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.
411 412 413
/**
 * @returns {Promise<string>}
 */
D
Dirk Baeumer 已提交
414
function getUserDefinedLocale() {
415
	const locale = args['locale'];
D
Dirk Baeumer 已提交
416 417 418 419
	if (locale) {
		return Promise.resolve(locale.toLowerCase());
	}

420
	const localeConfig = path.join(userDataPath, 'User', 'locale.json');
D
Dirk Baeumer 已提交
421 422
	return exists(localeConfig).then((result) => {
		if (result) {
423
			return bootstrap.readFile(localeConfig).then((content) => {
D
Dirk Baeumer 已提交
424 425
				content = stripComments(content);
				try {
426
					const value = JSON.parse(content).locale;
D
Dirk Baeumer 已提交
427 428 429 430 431 432 433 434 435 436 437
					return value && typeof value === 'string' ? value.toLowerCase() : undefined;
				} catch (e) {
					return undefined;
				}
			});
		} else {
			return undefined;
		}
	});
}

438 439 440
/**
 * @returns {object}
 */
D
Dirk Baeumer 已提交
441
function getLanguagePackConfigurations() {
442
	const configFile = path.join(userDataPath, 'languagepacks.json');
D
Dirk Baeumer 已提交
443 444 445 446 447 448 449 450 451
	try {
		return require(configFile);
	} catch (err) {
		// Do nothing. If we can't read the file we have no
		// language pack config.
	}
	return undefined;
}

452 453 454 455
/**
 * @param {object} config
 * @param {string} locale
 */
D
Dirk Baeumer 已提交
456 457 458 459 460 461
function resolveLanguagePackLocale(config, locale) {
	try {
		while (locale) {
			if (config[locale]) {
				return locale;
			} else {
462
				const index = locale.lastIndexOf('-');
D
Dirk Baeumer 已提交
463 464 465 466
				if (index > 0) {
					locale = locale.substring(0, index);
				} else {
					return undefined;
467 468 469
				}
			}
		}
D
Dirk Baeumer 已提交
470 471
	} catch (err) {
		console.error('Resolving language pack configuration failed.', err);
472
	}
D
Dirk Baeumer 已提交
473 474
	return undefined;
}
475

476 477 478
/**
 * @param {string} locale
 */
D
Dirk Baeumer 已提交
479
function getNLSConfiguration(locale) {
J
Joao Moreno 已提交
480
	if (locale === 'pseudo') {
D
Dirk Baeumer 已提交
481
		return Promise.resolve({ locale: locale, availableLanguages: {}, pseudo: true });
J
Joao Moreno 已提交
482
	}
D
Dirk Baeumer 已提交
483

J
Joao Moreno 已提交
484
	if (process.env['VSCODE_DEV']) {
D
Dirk Baeumer 已提交
485
		return Promise.resolve({ locale: locale, availableLanguages: {} });
J
Joao Moreno 已提交
486
	}
487

J
Joao Moreno 已提交
488 489
	// We have a built version so we have extracted nls file. Try to find
	// the right file to use.
490

491
	// Check if we have an English or English US locale. If so fall to default since that is our
492
	// English translation (we don't ship *.nls.en.json files)
493
	if (locale && (locale === 'en' || locale === 'en-us')) {
D
Dirk Baeumer 已提交
494
		return Promise.resolve({ locale: locale, availableLanguages: {} });
495 496
	}

497
	const initialLocale = locale;
D
Dirk Baeumer 已提交
498

499
	perf.mark('nlsGeneration:start');
500

501
	const defaultResult = function (locale) {
502 503
		perf.mark('nlsGeneration:end');
		return Promise.resolve({ locale: locale, availableLanguages: {} });
504 505
	};
	try {
506
		const commit = product.commit;
507
		if (!commit) {
508
			return defaultResult(initialLocale);
509
		}
510
		const configs = getLanguagePackConfigurations();
511
		if (!configs) {
512
			return defaultResult(initialLocale);
513 514 515 516 517
		}
		locale = resolveLanguagePackLocale(configs, locale);
		if (!locale) {
			return defaultResult(initialLocale);
		}
518
		const packConfig = configs[locale];
519 520
		let mainPack;
		if (!packConfig || typeof packConfig.hash !== 'string' || !packConfig.translations || typeof (mainPack = packConfig.translations['vscode']) !== 'string') {
521
			return defaultResult(initialLocale);
522 523 524
		}
		return exists(mainPack).then((fileExists) => {
			if (!fileExists) {
525
				return defaultResult(initialLocale);
D
Dirk Baeumer 已提交
526
			}
527 528 529 530 531 532
			const packId = packConfig.hash + '.' + locale;
			const cacheRoot = path.join(userDataPath, 'clp', packId);
			const coreLocation = path.join(cacheRoot, commit);
			const translationsConfigFile = path.join(cacheRoot, 'tcf.json');
			const corruptedFile = path.join(cacheRoot, 'corrupted.info');
			const result = {
533 534 535 536 537
				locale: initialLocale,
				availableLanguages: { '*': locale },
				_languagePackId: packId,
				_translationsConfigFile: translationsConfigFile,
				_cacheRoot: cacheRoot,
538 539
				_resolvedLanguagePackCoreLocation: coreLocation,
				_corruptedFile: corruptedFile
540
			};
541 542 543 544 545 546 547
			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 已提交
548
				}
549 550 551 552 553 554 555 556 557
				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(() => {
558
							return Promise.all([bootstrap.readFile(path.join(__dirname, 'nls.metadata.json')), bootstrap.readFile(mainPack)]);
559
						}).then((values) => {
560 561 562 563
							const metadata = JSON.parse(values[0]);
							const packData = JSON.parse(values[1]).contents;
							const bundles = Object.keys(metadata.bundles);
							const writes = [];
564
							for (let bundle of bundles) {
565 566
								const modules = metadata.bundles[bundle];
								const target = Object.create(null);
567
								for (let module of modules) {
568 569 570
									const keys = metadata.keys[module];
									const defaultMessages = metadata.messages[module];
									const translations = packData[module];
571 572 573 574
									let targetStrings;
									if (translations) {
										targetStrings = [];
										for (let i = 0; i < keys.length; i++) {
575 576
											const elem = keys[i];
											const key = typeof elem === 'string' ? elem : elem.key;
577 578 579 580 581 582 583 584
											let translatedMessage = translations[key];
											if (translatedMessage === undefined) {
												translatedMessage = defaultMessages[i];
											}
											targetStrings.push(translatedMessage);
										}
									} else {
										targetStrings = defaultMessages;
D
Dirk Baeumer 已提交
585
									}
586
									target[module] = targetStrings;
D
Dirk Baeumer 已提交
587
								}
588
								writes.push(bootstrap.writeFile(path.join(coreLocation, bundle.replace(/\//g, '!') + '.nls.json'), JSON.stringify(target)));
D
Dirk Baeumer 已提交
589
							}
590
							writes.push(bootstrap.writeFile(translationsConfigFile, JSON.stringify(packConfig.translations)));
591 592 593 594 595 596 597 598 599
							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 已提交
600 601
				});
			});
602 603 604 605
		});
	} catch (err) {
		console.error('Generating translation files failed.', err);
		return defaultResult(locale);
606
	}
J
Joao Moreno 已提交
607
}
J
Joshua 已提交
608
//#endregion