main.ts 12.7 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);
113 114 115 116
	const urlChannel = new URLChannel(urlService, id => {
		const window = windowsService.getFocusedWindow() || windowsService.getLastActiveWindow();
		return window ? window.id === id : false;
	});
J
Joao Moreno 已提交
117
	electronIpcServer.registerChannel('url', urlChannel);
E
Erich Gamma 已提交
118 119 120

	// Used by sub processes to communicate back to the main instance
	process.env['VSCODE_PID'] = '' + process.pid;
J
Joao Moreno 已提交
121 122
	process.env['VSCODE_IPC_HOOK'] = envService.mainIPCHandle;
	process.env['VSCODE_SHARED_IPC_HOOK'] = envService.sharedIPCHandle;
E
Erich Gamma 已提交
123 124

	// Spawn shared process
J
Joao Moreno 已提交
125 126 127 128
	const sharedProcess = spawnSharedProcess({
		allowOutput: !envService.isBuilt || envService.cliArgs.verboseLogging,
		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
	}

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

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

147 148
		sharedProcess.dispose();

B
Benjamin Pasero 已提交
149 150 151
		if (windowsMutex) {
			windowsMutex.release();
		}
152 153 154 155
	}

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

		dispose();
	});

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

		dispose();
		process.exit(code); // in main, process.exit === app.exit
E
Erich Gamma 已提交
167 168 169
	});

	// Lifecycle
J
Joao Moreno 已提交
170
	lifecycleService.ready();
E
Erich Gamma 已提交
171 172

	// Load settings
B
Benjamin Pasero 已提交
173
	settingsService.loadSync();
E
Erich Gamma 已提交
174 175

	// Propagate to clients
B
Benjamin Pasero 已提交
176
	windowsService.ready(userEnv);
E
Erich Gamma 已提交
177 178

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

	// Install Tasks
J
Joao Moreno 已提交
183
	if (platform.isWindows && envService.isBuilt) {
E
Erich Gamma 已提交
184 185 186 187 188 189 190 191 192 193 194 195
		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 已提交
196
	updateService.initialize();
E
Erich Gamma 已提交
197 198

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

J
Joao Moreno 已提交
208 209
function setupIPC(accessor: ServicesAccessor): TPromise<Server> {
	const logService = accessor.get(ILogService);
J
renames  
Joao Moreno 已提交
210
	const envService = accessor.get(IEnvironmentService);
J
Joao Moreno 已提交
211

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

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

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

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

					// Tests from CLI require to be the only instance currently (TODO@Ben support multiple instances and output)
J
Joao Moreno 已提交
234
					if (envService.isTestingFromCli) {
J
Joao Moreno 已提交
235 236 237 238 239 240
						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 已提交
241
					logService.log('Sending env to running instance...');
E
Erich Gamma 已提交
242

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

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

					// 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 已提交
259
						fs.unlinkSync(envService.mainIPCHandle);
E
Erich Gamma 已提交
260
					} catch (e) {
J
Joao Moreno 已提交
261
						logService.log('Fatal error deleting obsolete instance handle', e);
262
						return TPromise.wrapError(e);
E
Erich Gamma 已提交
263 264 265 266 267 268 269 270 271 272 273
					}

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

	return setup(true);
}

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

J
renames  
Joao Moreno 已提交
277
services.set(IEnvironmentService, new SyncDescriptor(EnvService));
J
Joao Moreno 已提交
278
services.set(ILogService, new SyncDescriptor(MainLogService));
J
Joao Moreno 已提交
279
services.set(IWindowsService, new SyncDescriptor(WindowsManager));
J
Joao Moreno 已提交
280 281
services.set(ILifecycleService, new SyncDescriptor(LifecycleService));
services.set(IStorageService, new SyncDescriptor(StorageService));
J
renames  
Joao Moreno 已提交
282 283
services.set(IUpdateService, new SyncDescriptor(UpdateManager));
services.set(ISettingsService, new SyncDescriptor(SettingsManager));
J
Joao Moreno 已提交
284 285 286

const instantiationService = new InstantiationService(services);

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

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

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

J
Joao Moreno 已提交
303
		const command = `'${process.execPath}' -p '"${ mark }" + JSON.stringify(process.env) + "${ mark }"'`;
304 305 306 307 308 309 310 311 312 313 314 315 316 317 318
		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 已提交
319 320 321
			const raw = Buffer.concat(buffers).toString('utf8');
			const match = regex.exec(raw);
			const rawStripped = match ? match[1] : '{}';
322 323

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

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

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

349 350 351 352
function getUserEnvironment(): TPromise<IEnv> {
	return platform.isWindows ? TPromise.as({}) : getUnixUserEnvironment();
}

353 354
// 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)
E
Erich Gamma 已提交
355 356
getUserEnvironment()
	.then(userEnv => {
357 358 359 360
		if (process.env['VSCODE_CLI'] !== '1') {
			assign(process.env, userEnv);
		}

361 362 363
		// Make sure the NLS Config travels to the rendered process
		// See also https://github.com/Microsoft/vscode/issues/4558
		userEnv['VSCODE_NLS_CONFIG'] = process.env['VSCODE_NLS_CONFIG'];
J
Joao Moreno 已提交
364

J
Joao Moreno 已提交
365
		return instantiationService.invokeFunction(a => a.get(IEnvironmentService).createPaths())
J
Joao Moreno 已提交
366
			.then(() => instantiationService.invokeFunction(setupIPC))
J
Joao Moreno 已提交
367
			.then(mainIpcServer => instantiationService.invokeFunction(main, mainIpcServer, userEnv));
E
Erich Gamma 已提交
368
	})
J
Joao Moreno 已提交
369
	.done(null, err => instantiationService.invokeFunction(quit, err));