main.ts 13.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';
13
import { IProcessEnvironment, IEnvironmentService, EnvService } from 'vs/code/electron-main/env';
J
Joao Moreno 已提交
14
import { IWindowsService, WindowsManager } from 'vs/code/electron-main/windows';
15 16
import { ILifecycleService, LifecycleService } from 'vs/code/electron-main/lifecycle';
import { VSCodeMenu } from 'vs/code/electron-main/menus';
J
Joao Moreno 已提交
17 18
import { ISettingsService, SettingsManager } from 'vs/code/electron-main/settings';
import { IUpdateService, UpdateManager } from 'vs/code/electron-main/update-manager';
J
Joao Moreno 已提交
19
import { Server as ElectronIPCServer } from 'vs/base/parts/ipc/common/ipc.electron';
J
Joao Moreno 已提交
20 21 22 23 24 25 26 27 28 29 30
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 已提交
31 32
import { ILogService, MainLogService } from 'vs/code/electron-main/log';
import { IStorageService, StorageService } from 'vs/code/electron-main/storage';
33
import * as cp from 'child_process';
J
Joao Moreno 已提交
34
import { generateUuid } from 'vs/base/common/uuid';
J
Joao Moreno 已提交
35 36
import { URLChannel } from 'vs/platform/url/common/urlIpc';
import { URLService } from 'vs/platform/url/electron-main/urlService';
E
Erich Gamma 已提交
37

J
Joao Moreno 已提交
38 39 40 41
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 已提交
42

43
	let exitCode = 0;
E
Erich Gamma 已提交
44
	if (typeof arg === 'string') {
J
Joao Moreno 已提交
45
		logService.log(arg);
E
Erich Gamma 已提交
46
	} else {
47
		exitCode = 1; // signal error to the outside
48 49 50 51 52
		if (arg.stack) {
			console.error(arg.stack);
		} else {
			console.error('Startup error: ' + arg.toString());
		}
E
Erich Gamma 已提交
53 54
	}

55
	process.exit(exitCode); // in main, process.exit === app.exit
E
Erich Gamma 已提交
56 57
}

J
Joao Moreno 已提交
58
function main(accessor: ServicesAccessor, mainIpcServer: Server, userEnv: IProcessEnvironment): void {
J
Joao Moreno 已提交
59 60
	const instantiationService = accessor.get(IInstantiationService);
	const logService = accessor.get(ILogService);
J
renames  
Joao Moreno 已提交
61
	const envService = accessor.get(IEnvironmentService);
B
Benjamin Pasero 已提交
62
	const windowsService = accessor.get(IWindowsService);
J
Joao Moreno 已提交
63
	const lifecycleService = accessor.get(ILifecycleService);
B
Benjamin Pasero 已提交
64 65
	const updateService = accessor.get(IUpdateService);
	const settingsService = accessor.get(ISettingsService);
J
Joao Moreno 已提交
66 67 68 69 70 71 72 73 74 75 76 77

	// 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 已提交
78
			windowsService.sendToFocused('vscode:reportError', JSON.stringify(friendlyError));
J
Joao Moreno 已提交
79
		}
J
Joao Moreno 已提交
80

J
Joao Moreno 已提交
81 82 83 84 85
		console.error('[uncaught exception in main]: ' + err);
		if (err.stack) {
			console.error(err.stack);
		}
	});
J
Joao Moreno 已提交
86

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

J
Joao Moreno 已提交
90 91 92
	// Setup Windows mutex
	let windowsMutex: Mutex = null;
	try {
B
Benjamin Pasero 已提交
93
		const Mutex = (<any>require.__$__nodeRequire('windows-mutex')).Mutex;
J
Joao Moreno 已提交
94
		windowsMutex = new Mutex(envService.product.win32MutexName);
J
Joao Moreno 已提交
95 96 97 98
	} catch (e) {
		// noop
	}

J
Joao Moreno 已提交
99
	// Register Main IPC services
J
Joao Moreno 已提交
100
	const launchService = instantiationService.createInstance(LaunchService);
J
Joao Moreno 已提交
101
	const launchChannel = new LaunchChannel(launchService);
J
Joao Moreno 已提交
102
	mainIpcServer.registerChannel('launch', launchChannel);
J
Joao Moreno 已提交
103 104 105

	const askpassService = new GitAskpassService();
	const askpassChannel = new AskpassChannel(askpassService);
J
Joao Moreno 已提交
106 107 108 109 110 111 112
	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 已提交
113
	const urlChannel = instantiationService.createInstance(URLChannel, urlService);
J
Joao Moreno 已提交
114
	electronIpcServer.registerChannel('url', urlChannel);
E
Erich Gamma 已提交
115 116

	// Spawn shared process
J
Joao Moreno 已提交
117 118 119 120
	const sharedProcess = spawnSharedProcess({
		allowOutput: !envService.isBuilt || envService.cliArgs.verboseLogging,
		debugPort: envService.isBuilt ? null : 5871
	});
E
Erich Gamma 已提交
121 122 123 124 125

	// 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 已提交
126 127
	if (platform.isWindows && envService.product.win32AppUserModelId) {
		app.setAppUserModelId(envService.product.win32AppUserModelId);
E
Erich Gamma 已提交
128 129 130
	}

	// Set programStart in the global scope
J
Joao Moreno 已提交
131
	global.programStart = envService.cliArgs.programStart;
E
Erich Gamma 已提交
132

133
	function dispose() {
J
Joao Moreno 已提交
134 135 136
		if (mainIpcServer) {
			mainIpcServer.dispose();
			mainIpcServer = null;
E
Erich Gamma 已提交
137 138
		}

139 140
		sharedProcess.dispose();

B
Benjamin Pasero 已提交
141 142 143
		if (windowsMutex) {
			windowsMutex.release();
		}
144 145 146 147
	}

	// Dispose on app quit
	app.on('will-quit', () => {
J
Joao Moreno 已提交
148
		logService.log('App#will-quit: disposing resources');
149 150 151 152 153 154

		dispose();
	});

	// Dispose on vscode:exit
	ipc.on('vscode:exit', (event, code: number) => {
J
Joao Moreno 已提交
155
		logService.log('IPC#vscode:exit', code);
156 157 158

		dispose();
		process.exit(code); // in main, process.exit === app.exit
E
Erich Gamma 已提交
159 160 161
	});

	// Lifecycle
J
Joao Moreno 已提交
162
	lifecycleService.ready();
E
Erich Gamma 已提交
163 164

	// Load settings
B
Benjamin Pasero 已提交
165
	settingsService.loadSync();
E
Erich Gamma 已提交
166 167

	// Propagate to clients
B
Benjamin Pasero 已提交
168
	windowsService.ready(userEnv);
E
Erich Gamma 已提交
169 170

	// Install Menu
B
Benjamin Pasero 已提交
171 172
	const menu = instantiationService.createInstance(VSCodeMenu);
	menu.ready();
E
Erich Gamma 已提交
173 174

	// Install Tasks
J
Joao Moreno 已提交
175
	if (platform.isWindows && envService.isBuilt) {
E
Erich Gamma 已提交
176 177 178 179 180 181 182 183 184 185 186 187
		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 已提交
188
	updateService.initialize();
E
Erich Gamma 已提交
189 190

	// Open our first window
J
Joao Moreno 已提交
191
	if (envService.cliArgs.openNewWindow && envService.cliArgs.pathArguments.length === 0) {
B
Benjamin Pasero 已提交
192
		windowsService.open({ cli: envService.cliArgs, forceNewWindow: true, forceEmpty: true }); // new window if "-n" was used without paths
J
Joao Moreno 已提交
193
	} else if (global.macOpenFiles && global.macOpenFiles.length && (!envService.cliArgs.pathArguments || !envService.cliArgs.pathArguments.length)) {
B
Benjamin Pasero 已提交
194
		windowsService.open({ cli: envService.cliArgs, pathsToOpen: global.macOpenFiles }); // mac: open-file event received on startup
E
Erich Gamma 已提交
195
	} else {
B
Benjamin Pasero 已提交
196
		windowsService.open({ cli: envService.cliArgs, forceNewWindow: envService.cliArgs.openNewWindow, diffMode: envService.cliArgs.diffMode }); // default: read paths from cli
E
Erich Gamma 已提交
197 198 199
	}
}

J
Joao Moreno 已提交
200 201
function setupIPC(accessor: ServicesAccessor): TPromise<Server> {
	const logService = accessor.get(ILogService);
J
renames  
Joao Moreno 已提交
202
	const envService = accessor.get(IEnvironmentService);
J
Joao Moreno 已提交
203

E
Erich Gamma 已提交
204
	function setup(retry: boolean): TPromise<Server> {
J
Joao Moreno 已提交
205
		return serve(envService.mainIPCHandle).then(server => {
206 207 208 209 210 211
			if (platform.isMacintosh) {
				app.dock.show(); // dock might be hidden at this case due to a retry
			}

			return server;
		}, err => {
E
Erich Gamma 已提交
212
			if (err.code !== 'EADDRINUSE') {
213
				return TPromise.wrapError(err);
E
Erich Gamma 已提交
214 215
			}

216 217 218 219
			// Since we are the second instance, we do not want to show the dock
			if (platform.isMacintosh) {
				app.dock.hide();
			}
B
Benjamin Pasero 已提交
220

221
			// there's a running instance, let's connect to it
J
Joao Moreno 已提交
222
			return connect(envService.mainIPCHandle).then(
223
				client => {
J
Joao Moreno 已提交
224 225

					// Tests from CLI require to be the only instance currently (TODO@Ben support multiple instances and output)
J
Joao Moreno 已提交
226
					if (envService.isTestingFromCli) {
J
Joao Moreno 已提交
227 228 229 230 231 232
						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 已提交
233
					logService.log('Sending env to running instance...');
E
Erich Gamma 已提交
234

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

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

					// 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 {
J
Joao Moreno 已提交
251
						fs.unlinkSync(envService.mainIPCHandle);
E
Erich Gamma 已提交
252
					} catch (e) {
J
Joao Moreno 已提交
253
						logService.log('Fatal error deleting obsolete instance handle', e);
254
						return TPromise.wrapError(e);
E
Erich Gamma 已提交
255 256 257 258 259 260 261 262 263 264 265
					}

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

	return setup(true);
}

J
Joao Moreno 已提交
266 267 268
// TODO: isolate
const services = new ServiceCollection();

J
renames  
Joao Moreno 已提交
269
services.set(IEnvironmentService, new SyncDescriptor(EnvService));
J
Joao Moreno 已提交
270
services.set(ILogService, new SyncDescriptor(MainLogService));
J
Joao Moreno 已提交
271
services.set(IWindowsService, new SyncDescriptor(WindowsManager));
J
Joao Moreno 已提交
272 273
services.set(ILifecycleService, new SyncDescriptor(LifecycleService));
services.set(IStorageService, new SyncDescriptor(StorageService));
J
renames  
Joao Moreno 已提交
274 275
services.set(IUpdateService, new SyncDescriptor(UpdateManager));
services.set(ISettingsService, new SyncDescriptor(SettingsManager));
J
Joao Moreno 已提交
276 277 278

const instantiationService = new InstantiationService(services);

279 280 281 282
interface IEnv {
	[key: string]: string;
}

283
function getUnixShellEnvironment(): TPromise<IEnv> {
284
	const promise = new TPromise((c, e) => {
285
		const runAsNode = process.env['ATOM_SHELL_INTERNAL_RUN_AS_NODE'];
286
		const noAttach = process.env['ELECTRON_NO_ATTACH_CONSOLE'];
J
Joao Moreno 已提交
287 288
		const mark = generateUuid().replace(/-/g, '').substr(0, 12);
		const regex = new RegExp(mark + '(.*)' + mark);
289 290

		const env = assign({}, process.env, {
291
			ATOM_SHELL_INTERNAL_RUN_AS_NODE: '1',
292 293 294
			ELECTRON_NO_ATTACH_CONSOLE: '1'
		});

J
Joao Moreno 已提交
295
		const command = `'${process.execPath}' -p '"${ mark }" + JSON.stringify(process.env) + "${ mark }"'`;
296 297 298 299 300 301 302 303 304 305 306 307 308 309 310
		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 已提交
311 312 313
			const raw = Buffer.concat(buffers).toString('utf8');
			const match = regex.exec(raw);
			const rawStripped = match ? match[1] : '{}';
314 315

			try {
J
Joao Moreno 已提交
316
				const env = JSON.parse(rawStripped);
317 318

				if (runAsNode) {
319
					env['ATOM_SHELL_INTERNAL_RUN_AS_NODE'] = runAsNode;
320
				} else {
321
					delete env['ATOM_SHELL_INTERNAL_RUN_AS_NODE'];
322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340
				}

				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, () => ({}));
}

341
/**
342 343 344
 * 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.
345
 */
346 347
function getShellEnvironment(): TPromise<IEnv> {
	if (process.env['VSCODE_CLI'] === '1') {
348 349 350
		return TPromise.as({});
	}

351
	if (platform.isWindows) {
352 353 354
		return TPromise.as({});
	}

355
	return getUnixShellEnvironment();
356 357 358 359 360 361 362
}

/**
 * Returns the user environment necessary for all Code processes.
 * Such environment needs to be propagated to the renderer/shared
 * processes.
 */
363 364
function getEnvironment(): TPromise<IEnv> {
	return getShellEnvironment().then(shellEnv => {
365 366 367 368 369
		return instantiationService.invokeFunction(a => {
			const envService = a.get(IEnvironmentService);
			const instanceEnv = {
				VSCODE_PID: String(process.pid),
				VSCODE_IPC_HOOK: envService.mainIPCHandle,
370 371
				VSCODE_SHARED_IPC_HOOK: envService.sharedIPCHandle,
				VSCODE_NLS_CONFIG: process.env['VSCODE_NLS_CONFIG']
372 373
			};

374
			return assign({}, shellEnv, instanceEnv);
375 376
		});
	});
377 378
}

379 380
// 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)
381 382
getEnvironment().then(env => {
	assign(process.env, env);
383 384 385

	return instantiationService.invokeFunction(a => a.get(IEnvironmentService).createPaths())
		.then(() => instantiationService.invokeFunction(setupIPC))
J
Joao Moreno 已提交
386
		.then(mainIpcServer => instantiationService.invokeFunction(main, mainIpcServer, env));
387
})
J
Joao Moreno 已提交
388
.done(null, err => instantiationService.invokeFunction(quit, err));