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

'use strict';

J
Joao Moreno 已提交
8
import * as nls from 'vs/nls';
B
Benjamin Pasero 已提交
9
import * as fs from 'original-fs';
J
Joao Moreno 已提交
10 11 12
import { app, ipcMain as ipc } from 'electron';
import { assign } from 'vs/base/common/objects';
import * as platform from 'vs/base/common/platform';
J
Joao Moreno 已提交
13
import { parseArgs } from 'vs/code/node/argv';
J
Joao Moreno 已提交
14
import { mkdirp } from 'vs/base/node/pfs';
J
Joao Moreno 已提交
15
import { IProcessEnvironment, IEnvService, EnvService } from 'vs/code/electron-main/env';
J
Joao Moreno 已提交
16
import { IWindowsService, WindowsManager } from 'vs/code/electron-main/windows';
17 18
import { ILifecycleService, LifecycleService } from 'vs/code/electron-main/lifecycle';
import { VSCodeMenu } from 'vs/code/electron-main/menus';
J
Joao Moreno 已提交
19 20
import { ISettingsService, SettingsManager } from 'vs/code/electron-main/settings';
import { IUpdateService, UpdateManager } from 'vs/code/electron-main/update-manager';
J
Joao Moreno 已提交
21
import { Server as ElectronIPCServer } from 'vs/base/parts/ipc/common/ipc.electron';
J
Joao Moreno 已提交
22 23 24 25 26 27 28 29 30 31 32
import { Server, serve, connect } from 'vs/base/parts/ipc/node/ipc.net';
import { TPromise } from 'vs/base/common/winjs.base';
import { AskpassChannel } from 'vs/workbench/parts/git/common/gitIpc';
import { GitAskpassService } from 'vs/workbench/parts/git/electron-main/askpassService';
import { spawnSharedProcess } from 'vs/code/electron-main/sharedProcess';
import { Mutex } from 'windows-mutex';
import { LaunchService, ILaunchChannel, LaunchChannel, LaunchChannelClient } from './launch';
import { ServicesAccessor, IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { InstantiationService } from 'vs/platform/instantiation/common/instantiationService';
import { ServiceCollection } from 'vs/platform/instantiation/common/serviceCollection';
import { SyncDescriptor } from 'vs/platform/instantiation/common/descriptors';
B
Benjamin Pasero 已提交
33 34
import { ILogService, MainLogService } from 'vs/code/electron-main/log';
import { IStorageService, StorageService } from 'vs/code/electron-main/storage';
J
Joao Moreno 已提交
35 36 37 38 39 40
import { IEnvironmentService } from 'vs/platform/environment/common/environment';
import { EnvironmentService } from 'vs/platform/environment/node/environmentService';
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
import { NodeConfigurationService } from 'vs/platform/configuration/node/nodeConfigurationService';
import { IRequestService } from 'vs/platform/request/common/request';
import { RequestService } from 'vs/platform/request/node/requestService';
41
import * as cp from 'child_process';
J
Joao Moreno 已提交
42
import { generateUuid } from 'vs/base/common/uuid';
J
Joao Moreno 已提交
43 44
import { URLChannel } from 'vs/platform/url/common/urlIpc';
import { URLService } from 'vs/platform/url/electron-main/urlService';
E
Erich Gamma 已提交
45

J
Joao Moreno 已提交
46 47 48 49
function quit(accessor: ServicesAccessor, error?: Error);
function quit(accessor: ServicesAccessor, message?: string);
function quit(accessor: ServicesAccessor, arg?: any) {
	const logService = accessor.get(ILogService);
E
Erich Gamma 已提交
50

51
	let exitCode = 0;
E
Erich Gamma 已提交
52
	if (typeof arg === 'string') {
J
Joao Moreno 已提交
53
		logService.log(arg);
E
Erich Gamma 已提交
54
	} else {
55
		exitCode = 1; // signal error to the outside
56 57 58 59 60
		if (arg.stack) {
			console.error(arg.stack);
		} else {
			console.error('Startup error: ' + arg.toString());
		}
E
Erich Gamma 已提交
61 62
	}

63
	process.exit(exitCode); // in main, process.exit === app.exit
E
Erich Gamma 已提交
64 65
}

J
Joao Moreno 已提交
66
function main(accessor: ServicesAccessor, mainIpcServer: Server, userEnv: IProcessEnvironment): void {
J
Joao Moreno 已提交
67 68
	const instantiationService = accessor.get(IInstantiationService);
	const logService = accessor.get(ILogService);
J
Joao Moreno 已提交
69
	const envService = accessor.get(IEnvService);
B
Benjamin Pasero 已提交
70
	const windowsService = accessor.get(IWindowsService);
J
Joao Moreno 已提交
71
	const lifecycleService = accessor.get(ILifecycleService);
B
Benjamin Pasero 已提交
72 73
	const updateService = accessor.get(IUpdateService);
	const settingsService = accessor.get(ISettingsService);
J
Joao Moreno 已提交
74 75 76 77 78 79 80 81 82 83 84 85

	// We handle uncaught exceptions here to prevent electron from opening a dialog to the user
	process.on('uncaughtException', (err: any) => {
		if (err) {

			// take only the message and stack property
			let friendlyError = {
				message: err.message,
				stack: err.stack
			};

			// handle on client side
B
Benjamin Pasero 已提交
86
			windowsService.sendToFocused('vscode:reportError', JSON.stringify(friendlyError));
J
Joao Moreno 已提交
87
		}
J
Joao Moreno 已提交
88

J
Joao Moreno 已提交
89 90 91 92 93
		console.error('[uncaught exception in main]: ' + err);
		if (err.stack) {
			console.error(err.stack);
		}
	});
J
Joao Moreno 已提交
94

J
Joao Moreno 已提交
95
	logService.log('### VSCode main.js ###');
J
Joao Moreno 已提交
96
	logService.log(envService.appRoot, envService.cliArgs);
E
Erich Gamma 已提交
97

J
Joao Moreno 已提交
98 99 100
	// Setup Windows mutex
	let windowsMutex: Mutex = null;
	try {
B
Benjamin Pasero 已提交
101
		const Mutex = (<any>require.__$__nodeRequire('windows-mutex')).Mutex;
J
Joao Moreno 已提交
102
		windowsMutex = new Mutex(envService.product.win32MutexName);
J
Joao Moreno 已提交
103 104 105 106
	} catch (e) {
		// noop
	}

J
Joao Moreno 已提交
107
	// Register Main IPC services
J
Joao Moreno 已提交
108
	const launchService = instantiationService.createInstance(LaunchService);
J
Joao Moreno 已提交
109
	const launchChannel = new LaunchChannel(launchService);
J
Joao Moreno 已提交
110
	mainIpcServer.registerChannel('launch', launchChannel);
J
Joao Moreno 已提交
111 112 113

	const askpassService = new GitAskpassService();
	const askpassChannel = new AskpassChannel(askpassService);
J
Joao Moreno 已提交
114 115 116 117 118 119 120
	mainIpcServer.registerChannel('askpass', askpassChannel);

	// Create Electron IPC Server
	const electronIpcServer = new ElectronIPCServer(ipc);

	// Register Electron IPC services
	const urlService = instantiationService.createInstance(URLService);
J
Joao Moreno 已提交
121
	const urlChannel = instantiationService.createInstance(URLChannel, urlService);
J
Joao Moreno 已提交
122
	electronIpcServer.registerChannel('url', urlChannel);
E
Erich Gamma 已提交
123 124

	// Spawn shared process
J
Joao Moreno 已提交
125
	const sharedProcess = spawnSharedProcess({
B
Benjamin Pasero 已提交
126
		allowOutput: !envService.isBuilt || envService.cliArgs.verbose,
J
Joao Moreno 已提交
127 128
		debugPort: envService.isBuilt ? null : 5871
	});
E
Erich Gamma 已提交
129 130 131 132 133

	// Make sure we associate the program with the app user model id
	// This will help Windows to associate the running program with
	// any shortcut that is pinned to the taskbar and prevent showing
	// two icons in the taskbar for the same app.
J
Joao Moreno 已提交
134 135
	if (platform.isWindows && envService.product.win32AppUserModelId) {
		app.setAppUserModelId(envService.product.win32AppUserModelId);
E
Erich Gamma 已提交
136 137
	}

138
	function dispose() {
J
Joao Moreno 已提交
139 140 141
		if (mainIpcServer) {
			mainIpcServer.dispose();
			mainIpcServer = null;
E
Erich Gamma 已提交
142 143
		}

144 145
		sharedProcess.dispose();

B
Benjamin Pasero 已提交
146 147 148
		if (windowsMutex) {
			windowsMutex.release();
		}
149 150 151 152
	}

	// Dispose on app quit
	app.on('will-quit', () => {
J
Joao Moreno 已提交
153
		logService.log('App#will-quit: disposing resources');
154 155 156 157 158 159

		dispose();
	});

	// Dispose on vscode:exit
	ipc.on('vscode:exit', (event, code: number) => {
J
Joao Moreno 已提交
160
		logService.log('IPC#vscode:exit', code);
161 162 163

		dispose();
		process.exit(code); // in main, process.exit === app.exit
E
Erich Gamma 已提交
164 165 166
	});

	// Lifecycle
J
Joao Moreno 已提交
167
	lifecycleService.ready();
E
Erich Gamma 已提交
168 169

	// Load settings
B
Benjamin Pasero 已提交
170
	settingsService.loadSync();
E
Erich Gamma 已提交
171 172

	// Propagate to clients
B
Benjamin Pasero 已提交
173
	windowsService.ready(userEnv);
E
Erich Gamma 已提交
174 175

	// Install Menu
B
Benjamin Pasero 已提交
176 177
	const menu = instantiationService.createInstance(VSCodeMenu);
	menu.ready();
E
Erich Gamma 已提交
178 179

	// Install Tasks
J
Joao Moreno 已提交
180
	if (platform.isWindows && envService.isBuilt) {
E
Erich Gamma 已提交
181 182 183 184 185 186 187 188 189 190 191 192
		app.setUserTasks([
			{
				title: nls.localize('newWindow', "New Window"),
				program: process.execPath,
				arguments: '-n', // force new window
				iconPath: process.execPath,
				iconIndex: 0
			}
		]);
	}

	// Setup auto update
B
Benjamin Pasero 已提交
193
	updateService.initialize();
E
Erich Gamma 已提交
194 195

	// Open our first window
B
Benjamin Pasero 已提交
196
	if (envService.cliArgs['new-window'] && envService.cliArgs.paths.length === 0) {
B
Benjamin Pasero 已提交
197
		windowsService.open({ cli: envService.cliArgs, forceNewWindow: true, forceEmpty: true }); // new window if "-n" was used without paths
B
Benjamin Pasero 已提交
198
	} else if (global.macOpenFiles && global.macOpenFiles.length && (!envService.cliArgs.paths || !envService.cliArgs.paths.length)) {
B
Benjamin Pasero 已提交
199
		windowsService.open({ cli: envService.cliArgs, pathsToOpen: global.macOpenFiles }); // mac: open-file event received on startup
E
Erich Gamma 已提交
200
	} else {
201
		windowsService.open({ cli: envService.cliArgs, forceNewWindow: envService.cliArgs['new-window'], diffMode: envService.cliArgs.diff }); // default: read paths from cli
E
Erich Gamma 已提交
202 203 204
	}
}

J
Joao Moreno 已提交
205 206
function setupIPC(accessor: ServicesAccessor): TPromise<Server> {
	const logService = accessor.get(ILogService);
207
	const environmentService = accessor.get(IEnvironmentService);
J
Joao Moreno 已提交
208
	const envService = accessor.get(IEnvService);
J
Joao Moreno 已提交
209

E
Erich Gamma 已提交
210
	function setup(retry: boolean): TPromise<Server> {
211
		return serve(environmentService.mainIPCHandle).then(server => {
212 213 214 215 216 217
			if (platform.isMacintosh) {
				app.dock.show(); // dock might be hidden at this case due to a retry
			}

			return server;
		}, err => {
E
Erich Gamma 已提交
218
			if (err.code !== 'EADDRINUSE') {
219
				return TPromise.wrapError(err);
E
Erich Gamma 已提交
220 221
			}

222 223 224 225
			// Since we are the second instance, we do not want to show the dock
			if (platform.isMacintosh) {
				app.dock.hide();
			}
B
Benjamin Pasero 已提交
226

227
			// there's a running instance, let's connect to it
228
			return connect(environmentService.mainIPCHandle).then(
229
				client => {
J
Joao Moreno 已提交
230 231

					// Tests from CLI require to be the only instance currently (TODO@Ben support multiple instances and output)
J
Joao Moreno 已提交
232
					if (envService.isTestingFromCli) {
J
Joao Moreno 已提交
233 234 235 236 237 238
						const msg = 'Running extension tests from the command line is currently only supported if no other instance of Code is running.';
						console.error(msg);
						client.dispose();
						return TPromise.wrapError(msg);
					}

J
Joao Moreno 已提交
239
					logService.log('Sending env to running instance...');
E
Erich Gamma 已提交
240

J
Joao Moreno 已提交
241 242
					const channel = client.getChannel<ILaunchChannel>('launch');
					const service = new LaunchChannelClient(channel);
E
Erich Gamma 已提交
243

J
Joao Moreno 已提交
244
					return service.start(envService.cliArgs, process.env)
E
Erich Gamma 已提交
245
						.then(() => client.dispose())
246
						.then(() => TPromise.wrapError('Sent env to running instance. Terminating...'));
E
Erich Gamma 已提交
247 248 249
				},
				err => {
					if (!retry || platform.isWindows || err.code !== 'ECONNREFUSED') {
250
						return TPromise.wrapError(err);
E
Erich Gamma 已提交
251 252 253 254 255 256
					}

					// it happens on Linux and OS X that the pipe is left behind
					// let's delete it, since we can't connect to it
					// and the retry the whole thing
					try {
257
						fs.unlinkSync(environmentService.mainIPCHandle);
E
Erich Gamma 已提交
258
					} catch (e) {
J
Joao Moreno 已提交
259
						logService.log('Fatal error deleting obsolete instance handle', e);
260
						return TPromise.wrapError(e);
E
Erich Gamma 已提交
261 262 263 264 265 266 267 268 269 270 271
					}

					return setup(false);
				}
			);
		});
	}

	return setup(true);
}

J
Joao Moreno 已提交
272 273 274
// TODO: isolate
const services = new ServiceCollection();

J
Joao Moreno 已提交
275 276
services.set(IEnvService, new SyncDescriptor(EnvService));
services.set(IEnvironmentService, new SyncDescriptor(EnvironmentService, parseArgs(process.argv)));
J
Joao Moreno 已提交
277
services.set(ILogService, new SyncDescriptor(MainLogService));
J
Joao Moreno 已提交
278
services.set(IWindowsService, new SyncDescriptor(WindowsManager));
J
Joao Moreno 已提交
279 280
services.set(ILifecycleService, new SyncDescriptor(LifecycleService));
services.set(IStorageService, new SyncDescriptor(StorageService));
J
Joao Moreno 已提交
281 282
services.set(IConfigurationService, new SyncDescriptor(NodeConfigurationService));
services.set(IRequestService, new SyncDescriptor(RequestService));
J
renames  
Joao Moreno 已提交
283 284
services.set(IUpdateService, new SyncDescriptor(UpdateManager));
services.set(ISettingsService, new SyncDescriptor(SettingsManager));
J
Joao Moreno 已提交
285 286 287

const instantiationService = new InstantiationService(services);

288 289 290 291
interface IEnv {
	[key: string]: string;
}

292
function getUnixShellEnvironment(): TPromise<IEnv> {
293
	const promise = new TPromise((c, e) => {
294
		const runAsNode = process.env['ATOM_SHELL_INTERNAL_RUN_AS_NODE'];
295
		const noAttach = process.env['ELECTRON_NO_ATTACH_CONSOLE'];
J
Joao Moreno 已提交
296 297
		const mark = generateUuid().replace(/-/g, '').substr(0, 12);
		const regex = new RegExp(mark + '(.*)' + mark);
298 299

		const env = assign({}, process.env, {
300
			ATOM_SHELL_INTERNAL_RUN_AS_NODE: '1',
301 302 303
			ELECTRON_NO_ATTACH_CONSOLE: '1'
		});

J
Joao Moreno 已提交
304
		const command = `'${process.execPath}' -p '"${ mark }" + JSON.stringify(process.env) + "${ mark }"'`;
305 306 307 308 309 310 311 312 313 314 315 316 317 318 319
		const child = cp.spawn(process.env.SHELL, ['-ilc', command], {
			detached: true,
			stdio: ['ignore', 'pipe', process.stderr],
			env
		});

		const buffers: Buffer[] = [];
		child.on('error', () => c({}));
		child.stdout.on('data', b => buffers.push(b));

		child.on('close', (code: number, signal: any) => {
			if (code !== 0) {
				return e(new Error('Failed to get environment'));
			}

J
Joao Moreno 已提交
320 321 322
			const raw = Buffer.concat(buffers).toString('utf8');
			const match = regex.exec(raw);
			const rawStripped = match ? match[1] : '{}';
323 324

			try {
J
Joao Moreno 已提交
325
				const env = JSON.parse(rawStripped);
326 327

				if (runAsNode) {
328
					env['ATOM_SHELL_INTERNAL_RUN_AS_NODE'] = runAsNode;
329
				} else {
330
					delete env['ATOM_SHELL_INTERNAL_RUN_AS_NODE'];
331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349
				}

				if (noAttach) {
					env['ELECTRON_NO_ATTACH_CONSOLE'] = noAttach;
				} else {
					delete env['ELECTRON_NO_ATTACH_CONSOLE'];
				}

				c(env);
			} catch (err) {
				e(err);
			}
		});
	});

	// swallow errors
	return promise.then(null, () => ({}));
}

350
/**
351 352 353
 * We eed to get the environment from a user's shell.
 * This should only be done when Code itself is not launched
 * from within a shell.
354
 */
355 356
function getShellEnvironment(): TPromise<IEnv> {
	if (process.env['VSCODE_CLI'] === '1') {
357 358 359
		return TPromise.as({});
	}

360
	if (platform.isWindows) {
361 362 363
		return TPromise.as({});
	}

364
	return getUnixShellEnvironment();
365 366 367 368 369 370 371
}

/**
 * Returns the user environment necessary for all Code processes.
 * Such environment needs to be propagated to the renderer/shared
 * processes.
 */
372 373
function getEnvironment(): TPromise<IEnv> {
	return getShellEnvironment().then(shellEnv => {
374
		return instantiationService.invokeFunction(a => {
375 376
			const environmentService = a.get(IEnvironmentService);

377 378
			const instanceEnv = {
				VSCODE_PID: String(process.pid),
379 380
				VSCODE_IPC_HOOK: environmentService.mainIPCHandle,
				VSCODE_SHARED_IPC_HOOK: environmentService.sharedIPCHandle,
381
				VSCODE_NLS_CONFIG: process.env['VSCODE_NLS_CONFIG']
382 383
			};

384
			return assign({}, shellEnv, instanceEnv);
385 386
		});
	});
387 388
}

J
Joao Moreno 已提交
389 390 391 392 393
function createPaths(environmentService: IEnvironmentService): TPromise<any> {
	const paths = [environmentService.appSettingsHome, environmentService.userHome, environmentService.extensionsPath];
	return TPromise.join(paths.map(p => mkdirp(p))) as TPromise<any>;
}

394 395
// On some platforms we need to manually read from the global environment variables
// and assign them to the process environment (e.g. when doubleclick app on Mac)
396 397
getEnvironment().then(env => {
	assign(process.env, env);
398

J
Joao Moreno 已提交
399
	return instantiationService.invokeFunction(a => createPaths(a.get(IEnvironmentService)))
400
		.then(() => instantiationService.invokeFunction(setupIPC))
J
Joao Moreno 已提交
401
		.then(mainIpcServer => instantiationService.invokeFunction(main, mainIpcServer, env));
402
})
J
Joao Moreno 已提交
403
.done(null, err => instantiationService.invokeFunction(quit, err));