main.js 13.8 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
const lp = require('./vs/base/node/languagePacks');

12 13
perf.mark('main:started');

J
Joao Moreno 已提交
14
const path = require('path');
15 16
const fs = require('fs');
const os = require('os');
17 18
const bootstrap = require('./bootstrap');
const paths = require('./paths');
19 20 21
// @ts-ignore
const product = require('../product.json');
// @ts-ignore
22
const { app, protocol } = require('electron');
J
Joao Moreno 已提交
23

24 25 26 27 28 29
// Enable portable support
const portable = bootstrap.configurePortable();

// Enable ASAR support
bootstrap.enableASARSupport();

B
Benjamin Pasero 已提交
30
// Set userData path before app 'ready' event
31 32 33 34
const args = parseCLIArgs();
const userDataPath = getUserDataPath(args);
app.setPath('userData', userDataPath);

B
Benjamin Pasero 已提交
35 36 37 38 39 40 41 42
// Set logs path before app 'ready' event if running portable
// to ensure that no 'logs' folder is created on disk at a
// location outside of the portable directory
// (https://github.com/microsoft/vscode/issues/56651)
if (portable.isPortable) {
	app.setAppLogsPath(path.join(userDataPath, 'logs'));
}

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

46 47 48 49 50
// Register custom schemes with privileges
protocol.registerSchemesAsPrivileged([
	{ scheme: 'vscode-resource', privileges: { secure: true, supportFetchAPI: true, corsEnabled: true } }
]);

51 52 53
// Global app listeners
registerListeners();

54 55 56 57 58 59
// Cached data
const nodeCachedDataDir = getNodeCachedDir();

// Configure static command line arguments
const argvConfig = configureCommandlineSwitchesSync(args);

60
/**
B
Benjamin Pasero 已提交
61 62
 * Support user defined locale: load it early before app('ready')
 * to have more things running in parallel.
63
 *
B
Benjamin Pasero 已提交
64
 * @type {Promise<import('./vs/base/node/languagePacks').NLSConfiguration>} nlsConfig | undefined
65
 */
B
Benjamin Pasero 已提交
66
let nlsConfigurationPromise = undefined;
67

68 69 70 71 72
const metaDataFile = path.join(__dirname, 'nls.metadata.json');
const locale = getUserDefinedLocale(argvConfig);
if (locale) {
	nlsConfigurationPromise = lp.getNLSConfiguration(product.commit, userDataPath, metaDataFile, locale);
}
73 74 75

// Load our code once ready
app.once('ready', function () {
B
Benjamin Pasero 已提交
76 77 78 79 80 81 82 83 84
	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'
		};

85
		contentTracing.startRecording(traceOptions).finally(() => onReady());
B
Benjamin Pasero 已提交
86 87 88 89 90
	} else {
		onReady();
	}
});

B
Benjamin Pasero 已提交
91 92 93 94 95 96 97 98
/**
 * Main startup routine
 *
 * @param {string | undefined} cachedDataDir
 * @param {import('./vs/base/node/languagePacks').NLSConfiguration} nlsConfig
 */
function startup(cachedDataDir, nlsConfig) {
	nlsConfig._languagePackSupport = true;
99

B
Benjamin Pasero 已提交
100 101
	process.env['VSCODE_NLS_CONFIG'] = JSON.stringify(nlsConfig);
	process.env['VSCODE_NODE_CACHED_DATA_DIR'] = cachedDataDir || '';
102

B
Benjamin Pasero 已提交
103 104 105 106 107 108
	// Load main in AMD
	perf.mark('willLoadMainBundle');
	require('./bootstrap-amd').load('vs/code/electron-main/main', () => {
		perf.mark('didLoadMainBundle');
	});
}
109

B
Benjamin Pasero 已提交
110 111
async function onReady() {
	perf.mark('main:appReady');
112

B
Benjamin Pasero 已提交
113
	try {
114
		const [cachedDataDir, nlsConfig] = await Promise.all([nodeCachedDataDir.ensureExists(), resolveNlsConfiguration()]);
B
Benjamin Pasero 已提交
115

116
		startup(cachedDataDir, nlsConfig);
B
Benjamin Pasero 已提交
117 118 119
	} catch (error) {
		console.error(error);
	}
B
Benjamin Pasero 已提交
120
}
121

122
/**
123
 * @typedef	 {{ [arg: string]: any; '--'?: string[]; _: string[]; }} ParsedArgs
124 125 126
 *
 * @param {ParsedArgs} cliArgs
 */
127 128 129 130 131 132 133
function configureCommandlineSwitchesSync(cliArgs) {
	const SUPPORTED_ELECTRON_SWITCHES = [

		// alias from us for --disable-gpu
		'disable-hardware-acceleration',

		// provided by Electron
134 135 136 137
		'disable-color-correct-rendering',

		// override for the color profile to use
		'force-color-profile'
138
	];
139

140 141 142
	if (process.platform === 'linux') {
		SUPPORTED_ELECTRON_SWITCHES.push('force-renderer-accessibility');
	}
143

144
	// Read argv config
145
	const argvConfig = readArgvConfigSync();
146 147

	// Append each flag to Electron
148 149 150 151 152 153
	Object.keys(argvConfig).forEach(argvKey => {
		if (SUPPORTED_ELECTRON_SWITCHES.indexOf(argvKey) === -1) {
			return; // unsupported argv key
		}

		const argvValue = argvConfig[argvKey];
154 155 156 157 158 159 160 161 162 163

		// Color profile
		if (argvKey === 'force-color-profile') {
			if (argvValue) {
				app.commandLine.appendSwitch(argvKey, argvValue);
			}
		}

		// Others
		else if (argvValue === true || argvValue === 'true') {
164
			if (argvKey === 'disable-hardware-acceleration') {
165
				app.disableHardwareAcceleration(); // needs to be called explicitly
166
			} else {
167
				app.commandLine.appendSwitch(argvKey);
168 169 170
			}
		}
	});
171 172

	// Support JS Flags
173
	const jsFlags = getJSFlags(cliArgs);
174
	if (jsFlags) {
175
		app.commandLine.appendSwitch('js-flags', jsFlags);
J
Joao Moreno 已提交
176
	}
177

178
	// TODO@Deepak Electron 7 workaround for https://github.com/microsoft/vscode/issues/88873
179 180
	app.commandLine.appendSwitch('disable-features', 'LayoutNG');

181
	return argvConfig;
J
Joao Moreno 已提交
182 183
}

184
function readArgvConfigSync() {
185 186 187 188 189 190 191 192

	// Read or create the argv.json config file sync before app('ready')
	const argvConfigPath = getArgvConfigPath();
	let argvConfig;
	try {
		argvConfig = JSON.parse(stripComments(fs.readFileSync(argvConfigPath).toString()));
	} catch (error) {
		if (error && error.code === 'ENOENT') {
193
			createDefaultArgvConfigSync(argvConfigPath);
194 195 196 197 198 199 200 201 202 203 204 205 206 207 208
		} else {
			console.warn(`Unable to read argv.json configuration file in ${argvConfigPath}, falling back to defaults (${error})`);
		}
	}

	// Fallback to default
	if (!argvConfig) {
		argvConfig = {
			'disable-color-correct-rendering': true // Force pre-Chrome-60 color profile handling (for https://github.com/Microsoft/vscode/issues/51791)
		};
	}

	return argvConfig;
}

209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233
/**
 * @param {string} argvConfigPath
 */
function createDefaultArgvConfigSync(argvConfigPath) {
	try {

		// Ensure argv config parent exists
		const argvConfigPathDirname = path.dirname(argvConfigPath);
		if (!fs.existsSync(argvConfigPathDirname)) {
			fs.mkdirSync(argvConfigPathDirname);
		}

		// Migrate over legacy locale
		const localeConfigPath = path.join(userDataPath, 'User', 'locale.json');
		const legacyLocale = getLegacyUserDefinedLocaleSync(localeConfigPath);
		if (legacyLocale) {
			try {
				fs.unlinkSync(localeConfigPath);
			} catch (error) {
				//ignore
			}
		}

		// Default argv content
		const defaultArgvConfigContent = [
G
Greg Van Liew 已提交
234
			'// This configuration file allows you to pass permanent command line arguments to VS Code.',
235 236 237 238 239
			'// Only a subset of arguments is currently supported to reduce the likelyhood of breaking',
			'// the installation.',
			'//',
			'// PLEASE DO NOT CHANGE WITHOUT UNDERSTANDING THE IMPACT',
			'//',
G
Greg Van Liew 已提交
240
			'// NOTE: Changing this file requires a restart of VS Code.',
241
			'{',
B
Benjamin Pasero 已提交
242 243
			'	// Use software rendering instead of hardware accelerated rendering.',
			'	// This can help in cases where you see rendering issues in VS Code.',
B
Benjamin Pasero 已提交
244 245 246 247 248
			'	// "disable-hardware-acceleration": true,',
			'',
			'	// Enabled by default by VS Code to resolve color issues in the renderer',
			'	// See https://github.com/Microsoft/vscode/issues/51791 for details',
			'	"disable-color-correct-rendering": true'
249 250 251 252 253 254
		];

		if (legacyLocale) {
			defaultArgvConfigContent[defaultArgvConfigContent.length - 1] = `${defaultArgvConfigContent[defaultArgvConfigContent.length - 1]},`; // append trailing ","

			defaultArgvConfigContent.push('');
G
Greg Van Liew 已提交
255
			defaultArgvConfigContent.push('	// Display language of VS Code');
256 257 258 259 260 261 262 263 264 265 266 267
			defaultArgvConfigContent.push(`	"locale": "${legacyLocale}"`);
		}

		defaultArgvConfigContent.push('}');

		// Create initial argv.json with default content
		fs.writeFileSync(argvConfigPath, defaultArgvConfigContent.join('\n'));
	} catch (error) {
		console.error(`Unable to create argv.json configuration file in ${argvConfigPath}, falling back to defaults (${error})`);
	}
}

268 269 270 271 272 273 274 275 276 277 278 279 280 281
function getArgvConfigPath() {
	const vscodePortable = process.env['VSCODE_PORTABLE'];
	if (vscodePortable) {
		return path.join(vscodePortable, 'argv.json');
	}

	let dataFolderName = product.dataFolderName;
	if (process.env['VSCODE_DEV']) {
		dataFolderName = `${dataFolderName}-dev`;
	}

	return path.join(os.homedir(), dataFolderName, 'argv.json');
}

282
/**
283
 * @param {ParsedArgs} cliArgs
284 285
 * @returns {string}
 */
286 287
function getJSFlags(cliArgs) {
	const jsFlags = [];
288 289

	// Add any existing JS flags we already got from the command line
290 291
	if (cliArgs['js-flags']) {
		jsFlags.push(cliArgs['js-flags']);
292 293
	}

294
	// Support max-memory flag
295 296
	if (cliArgs['max-memory'] && !/max_old_space_size=(\d+)/g.exec(cliArgs['js-flags'])) {
		jsFlags.push(`--max_old_space_size=${cliArgs['max-memory']}`);
297
	}
298 299

	return jsFlags.length > 0 ? jsFlags.join(' ') : null;
300 301
}

302
/**
303 304
 * @param {ParsedArgs} cliArgs
 *
305 306
 * @returns {string}
 */
307 308 309 310
function getUserDataPath(cliArgs) {
	if (portable.isPortable) {
		return path.join(portable.portableDataPath, 'user-data');
	}
J
Joao Moreno 已提交
311

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

315 316 317
/**
 * @returns {ParsedArgs}
 */
318
function parseCLIArgs() {
D
Daniel Imms 已提交
319
	const minimist = require('minimist');
320

321 322 323 324 325 326 327 328
	return minimist(process.argv, {
		string: [
			'user-data-dir',
			'locale',
			'js-flags',
			'max-memory'
		]
	});
J
Joao Moreno 已提交
329 330
}

331 332 333
function setCurrentWorkingDirectory() {
	try {
		if (process.platform === 'win32') {
334
			process.env['VSCODE_CWD'] = process.cwd(); // remember as environment variable
335 336 337
			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 已提交
338
		}
339 340 341 342
	} catch (err) {
		console.error(err);
	}
}
A
Alex Dima 已提交
343

344
function registerListeners() {
A
Alex Dima 已提交
345

346
	/**
347
	 * macOS: when someone drops a file to the not-yet running VSCode, the open-file event fires even before
348 349 350 351
	 * the app-ready event. We listen very early for open-file and remember this upon startup as path to open.
	 *
	 * @type {string[]}
	 */
352 353
	const macOpenFiles = [];
	global['macOpenFiles'] = macOpenFiles;
354
	app.on('open-file', function (event, path) {
355
		macOpenFiles.push(path);
356
	});
357

358
	/**
359
	 * macOS: react to open-url requests.
360 361 362
	 *
	 * @type {string[]}
	 */
363 364 365
	const openUrls = [];
	const onOpenUrl = function (event, url) {
		event.preventDefault();
366

367 368
		openUrls.push(url);
	};
J
Joao Moreno 已提交
369

370 371 372
	app.on('will-finish-launching', function () {
		app.on('open-url', onOpenUrl);
	});
J
Joao Moreno 已提交
373

374
	global['getOpenUrls'] = function () {
375
		app.removeListener('open-url', onOpenUrl);
376

377 378
		return openUrls;
	};
379 380
}

381
/**
B
Benjamin Pasero 已提交
382
 * @returns {{ ensureExists: () => Promise<string | undefined> }}
383
 */
384
function getNodeCachedDir() {
385
	return new class {
386

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

B
Benjamin Pasero 已提交
391 392 393 394 395 396 397 398
		async ensureExists() {
			try {
				await bootstrap.mkdirp(this.value);

				return this.value;
			} catch (error) {
				// ignore
			}
399 400 401 402 403 404
		}

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

406 407 408 409
			// IEnvironmentService.isBuilt
			if (process.env['VSCODE_DEV']) {
				return undefined;
			}
410

411
			// find commit id
412
			const commit = product.commit;
413 414 415
			if (!commit) {
				return undefined;
			}
416

417 418 419 420
			return path.join(userDataPath, 'CachedData', commit);
		}
	};
}
421

422
//#region NLS Support
B
Benjamin Pasero 已提交
423 424 425 426 427
/**
 * Resolve the NLS configuration
 *
 * @return {Promise<import('./vs/base/node/languagePacks').NLSConfiguration>}
 */
428
async function resolveNlsConfiguration() {
B
Benjamin Pasero 已提交
429 430 431

	// First, we need to test a user defined locale. If it fails we try the app locale.
	// If that fails we fall back to English.
432
	let nlsConfiguration = nlsConfigurationPromise ? await nlsConfigurationPromise : undefined;
B
Benjamin Pasero 已提交
433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457
	if (!nlsConfiguration) {

		// 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) {
			nlsConfiguration = { locale: 'en', availableLanguages: {} };
		} else {

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

			nlsConfiguration = await lp.getNLSConfiguration(product.commit, userDataPath, metaDataFile, appLocale);
			if (!nlsConfiguration) {
				nlsConfiguration = { locale: appLocale, availableLanguages: {} };
			}
		}
	} else {
		// We received a valid nlsConfig from a user defined locale
	}

	return nlsConfiguration;
}

458 459 460 461
/**
 * @param {string} content
 * @returns {string}
 */
462
function stripComments(content) {
A
Alex Dima 已提交
463
	const regexp = /("(?:[^\\"]*(?:\\.)?)*")|('(?:[^\\']*(?:\\.)?)*')|(\/\*(?:\r?\n|.)*?\*\/)|(\/{2,}.*?(?:(?:\r?\n)|$))/g;
464 465

	return content.replace(regexp, function (match, m1, m2, m3, m4) {
466 467 468 469
		// Only one of m1, m2, m3, m4 matches
		if (m3) {
			// A block comment. Replace with nothing
			return '';
D
Dirk Baeumer 已提交
470
		} else if (m4) {
471
			// A line comment. If it ends in \r?\n then keep it.
472
			const length_1 = m4.length;
473 474 475 476 477 478
			if (length_1 > 2 && m4[length_1 - 1] === '\n') {
				return m4[length_1 - 2] === '\r' ? '\r\n' : '\n';
			}
			else {
				return '';
			}
D
Dirk Baeumer 已提交
479
		} else {
480 481 482 483
			// We match a string
			return match;
		}
	});
J
lint  
Joao Moreno 已提交
484
}
485

486
/**
B
Benjamin Pasero 已提交
487 488 489 490 491
 * Language tags are case insensitive 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.
 *
492 493
 * @param {{ locale: string | undefined; }} argvConfig
 * @returns {string | undefined}
494
 */
495
function getUserDefinedLocale(argvConfig) {
496
	const locale = args['locale'];
D
Dirk Baeumer 已提交
497
	if (locale) {
498
		return locale.toLowerCase(); // a directly provided --locale always wins
D
Dirk Baeumer 已提交
499 500
	}

501 502
	return argvConfig.locale && typeof argvConfig.locale === 'string' ? argvConfig.locale.toLowerCase() : undefined;
}
B
Benjamin Pasero 已提交
503

504 505 506 507 508
/**
 * @param {string} localeConfigPath
 * @returns {string | undefined}
 */
function getLegacyUserDefinedLocaleSync(localeConfigPath) {
B
Benjamin Pasero 已提交
509
	try {
510
		const content = stripComments(fs.readFileSync(localeConfigPath).toString());
B
Benjamin Pasero 已提交
511 512 513 514 515 516

		const value = JSON.parse(content).locale;
		return value && typeof value === 'string' ? value.toLowerCase() : undefined;
	} catch (error) {
		// ignore
	}
D
Dirk Baeumer 已提交
517
}
518
//#endregion