main.ts 7.0 KB
Newer Older
E
Erich Gamma 已提交
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 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 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229
/*---------------------------------------------------------------------------------------------
 *  Copyright (c) Microsoft Corporation. All rights reserved.
 *  Licensed under the MIT License. See License.txt in the project root for license information.
 *--------------------------------------------------------------------------------------------*/

'use strict';

import app = require('app');
import fs = require('fs');
import dialog = require('dialog');
import shell = require('shell');

import nls = require('vs/nls');
import {assign} from 'vs/base/common/objects';
import platform = require('vs/base/common/platform');
import env = require('vs/workbench/electron-main/env');
import windows = require('vs/workbench/electron-main/windows');
import lifecycle = require('vs/workbench/electron-main/lifecycle');
import menu = require('vs/workbench/electron-main/menus');
import settings = require('vs/workbench/electron-main/settings');
import {Instance as UpdateManager} from 'vs/workbench/electron-main/update-manager';
import {Server, serve, connect} from 'vs/base/node/service.net';
import {getUserEnvironment, IEnv} from 'vs/base/node/env';
import {Promise, TPromise} from 'vs/base/common/winjs.base';
import {GitAskpassService} from 'vs/workbench/parts/git/electron-main/askpassService';
import { spawnSharedProcess } from 'vs/workbench/parts/sharedProcess/node/sharedProcess';

export class LaunchService {
	public start(args: env.ICommandLineArguments): Promise {
		env.log('Received data from other instance', args);

		// Otherwise handle in windows manager
		if (!!args.pluginDevelopmentPath) {
			windows.manager.openPluginDevelopmentHostWindow({ cli: args });
		} else if (args.pathArguments.length === 0 && args.openNewWindow) {
			windows.manager.open({ cli: args, forceNewWindow: true, forceEmpty: true });
		} else if (args.pathArguments.length === 0) {
			windows.manager.focusLastActive(args);
		} else {
			windows.manager.open({ cli: args, forceNewWindow: !args.openInSameWindow });
		}

		return Promise.as(null);
	}
}

// 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
		windows.manager.sendToFocused('vscode:reportError', JSON.stringify(friendlyError));
	}

	console.error('[uncaught exception in main]: ' + err);
	if (err.stack) {
		console.error(err.stack);
	}
});

function quit(error?: Error);
function quit(message?: string);
function quit(arg?: any) {
	if (typeof arg === 'string') {
		env.log(arg)
	} else {
		env.log('Startup error: ' + arg.toString());
	}

	process.exit();
}

function main(ipcServer: Server, userEnv: IEnv): void {
	env.log('### VSCode main.js ###');
	env.log(env.appRoot, env.cliArgs);

	// Register IPC services
	ipcServer.registerService('LaunchService', new LaunchService());
	ipcServer.registerService('GitAskpassService', new GitAskpassService());

	// Used by sub processes to communicate back to the main instance
	process.env['VSCODE_PID'] = '' + process.pid;
	process.env['VSCODE_IPC_HOOK'] = env.mainIPCHandle;
	process.env['VSCODE_SHARED_IPC_HOOK'] = env.sharedIPCHandle;

	// Spawn shared process
	const sharedProcess = spawnSharedProcess();

	// 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.
	if (platform.isWindows) {
		app.setAppUserModelId('Microsoft.VisualStudioCode');
	}

	// Set programStart in the global scope
	global.programStart = env.cliArgs.programStart;

	// Dispose on app quit
	app.on('will-quit', () => {
		env.log('App#dispose: deleting running instance handle');

		if (ipcServer) {
			ipcServer.dispose();
			ipcServer = null;
		}

		sharedProcess.kill();
	});

	// Lifecycle
	lifecycle.manager.ready();

	// Load settings
	settings.manager.load();

	// Propagate to clients
	windows.manager.ready(userEnv);

	// Install Menu
	menu.manager.ready();

	// Install Tasks
	if (platform.isWindows && env.isBuilt) {
		app.setUserTasks([
			{
				title: nls.localize('newWindow', "New Window"),
				program: process.execPath,
				arguments: '-n', // force new window
				iconPath: process.execPath,
				iconIndex: 0
			}
		]);
	}

	// Setup auto update
	UpdateManager.initialize();

	// Open our first window
	if (env.cliArgs.openNewWindow) {
		windows.manager.open({ cli: env.cliArgs, forceNewWindow: true, forceEmpty: true }); // empty window if "-n" was used
	} else if (global.macOpenFiles && global.macOpenFiles.length && (!env.cliArgs.pathArguments || !env.cliArgs.pathArguments.length)) {
		windows.manager.open({ cli: env.cliArgs, pathsToOpen: global.macOpenFiles }); // mac: open-file event received on startup
	} else {
		windows.manager.open({ cli: env.cliArgs }); // default: read paths from cli
	}
}

function timebomb(): Promise {
	if (!env.product.expiryDate || Date.now() <= env.product.expiryDate) {
		return Promise.as(null);
	}

	return new Promise((c, e) => {
		dialog.showMessageBox({
			type: 'warning',
			title: env.product.nameLong,
			message: nls.localize('expired', "Expired"),
			detail: nls.localize('expiredDetail', "This pre-release version of {0} has expired.\n\nPlease visit {1} to download the current release.", env.product.nameLong, env.product.expiryUrl),
			buttons: [nls.localize('quit', "Quit"), nls.localize('openWebSite', "Open Web Site")],
			noLink: true,
		}, (i) => {
			if (i === 1) {
				shell.openExternal(env.product.expiryUrl);
			}

			e('Product expired');
		});
	});
}

function setupIPC(): TPromise<Server> {
	function setup(retry: boolean): TPromise<Server> {
		return serve(env.mainIPCHandle).then(null, err => {
			if (err.code !== 'EADDRINUSE') {
				return Promise.wrapError(err);
			}

			// there's a running instance, let's connect to it
			return connect(env.mainIPCHandle).then(
				client => {
					env.log('Sending env to running instance...');

					const service = client.getService<LaunchService>('LaunchService', LaunchService);

					return service.start(env.cliArgs)
						.then(() => client.dispose())
						.then(() => Promise.wrapError('Sent env to running instance. Terminating...'));
				},
				err => {
					if (!retry || platform.isWindows || err.code !== 'ECONNREFUSED') {
						return Promise.wrapError(err);
					}

					// 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 {
						fs.unlinkSync(env.mainIPCHandle);
					} catch (e) {
						env.log('Fatal error deleting obsolete instance handle', e);
						return Promise.wrapError(e);
					}

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

	return setup(true);
}

getUserEnvironment()
	.then(userEnv => {
		assign(process.env, userEnv);

		return timebomb()
			.then(setupIPC)
			.then(ipcServer => main(ipcServer, userEnv));
	})
	.done(null, quit);