windows.ts 34.1 KB
Newer Older
E
Erich Gamma 已提交
1 2 3 4 5 6 7 8 9 10 11 12
/*---------------------------------------------------------------------------------------------
 *  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 events = require('events');
import path = require('path');
import fs = require('fs');

13
import {ipcMain as ipc, app, screen, crashReporter, BrowserWindow, dialog} from 'electron';
E
Erich Gamma 已提交
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

import platform = require('vs/base/common/platform');
import env = require('vs/workbench/electron-main/env');
import window = require('vs/workbench/electron-main/window');
import lifecycle = require('vs/workbench/electron-main/lifecycle');
import nls = require('vs/nls');
import paths = require('vs/base/common/paths');
import arrays = require('vs/base/common/arrays');
import objects = require('vs/base/common/objects');
import storage = require('vs/workbench/electron-main/storage');
import settings = require('vs/workbench/electron-main/settings');
import {Instance as UpdateManager, IUpdate} from 'vs/workbench/electron-main/update-manager';

const eventEmitter = new events.EventEmitter();

const EventTypes = {
	OPEN: 'open',
	CLOSE: 'close',
	READY: 'ready'
};

export function onOpen<T>(clb: (path: window.IPath) => void): () => void {
	eventEmitter.addListener(EventTypes.OPEN, clb);

	return () => eventEmitter.removeListener(EventTypes.OPEN, clb);
}

export function onReady<T>(clb: (win: window.VSCodeWindow) => void): () => void {
	eventEmitter.addListener(EventTypes.READY, clb);

	return () => eventEmitter.removeListener(EventTypes.READY, clb);
}

export function onClose<T>(clb: (remainingWindowCount: number) => void): () => void {
	eventEmitter.addListener(EventTypes.CLOSE, clb);

	return () => eventEmitter.removeListener(EventTypes.CLOSE, clb);
}

enum WindowError {
	UNRESPONSIVE,
	CRASHED
}

export interface IOpenConfiguration {
	cli: env.ICommandLineArguments;
60
	userEnv?: env.IProcessEnvironment;
E
Erich Gamma 已提交
61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82
	pathsToOpen?: string[];
	forceNewWindow?: boolean;
	forceEmpty?: boolean;
	windowToUse?: window.VSCodeWindow;
}

interface IWindowState {
	workspacePath?: string;
	uiState: window.IWindowState;
}

interface IWindowsState {
	lastActiveWindow?: IWindowState;
	lastPluginDevelopmentHostWindow?: IWindowState;
	openedFolders: IWindowState[];
}

export interface IOpenedPathsList {
	folders: string[];
	files: string[];
}

83 84 85 86 87
interface ILogEntry {
	severity: string;
	arguments: any;
}

E
Erich Gamma 已提交
88 89 90 91
export class WindowsManager {

	public static openedPathsListStorageKey = 'openedPathsList';

92
	private static workingDirPickerStorageKey = 'pickerWorkingDir';
E
Erich Gamma 已提交
93 94 95 96
	private static windowsStateStorageKey = 'windowsState';

	private static WINDOWS: window.VSCodeWindow[] = [];

97
	private initialUserEnv: env.IProcessEnvironment;
E
Erich Gamma 已提交
98 99
	private windowsState: IWindowsState;

100
	public ready(initialUserEnv: env.IProcessEnvironment): void {
E
Erich Gamma 已提交
101 102
		this.registerListeners();

103
		this.initialUserEnv = initialUserEnv;
E
Erich Gamma 已提交
104 105 106 107
		this.windowsState = storage.getItem<IWindowsState>(WindowsManager.windowsStateStorageKey) || { openedFolders: [] };
	}

	private registerListeners(): void {
108
		app.on('activate', (event: Event, hasVisibleWindows: boolean) => {
E
Erich Gamma 已提交
109 110 111 112 113 114 115 116 117 118
			env.log('App#activate');

			// Mac only event: reopen last window when we get activated
			if (!hasVisibleWindows) {

				// We want to open the previously opened folder, so we dont pass on the path argument
				let cliArgWithoutPath = objects.clone(env.cliArgs);
				cliArgWithoutPath.pathArguments = [];
				this.windowsState.openedFolders = []; // make sure we do not restore too much

B
Benjamin Pasero 已提交
119
				this.open({ cli: cliArgWithoutPath });
E
Erich Gamma 已提交
120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139
			}
		});

		let macOpenFiles: string[] = [];
		let runningTimeout: number = null;
		app.on('open-file', (event: Event, path: string) => {
			env.log('App#open-file: ', path);
			event.preventDefault();

			// Keep in array because more might come!
			macOpenFiles.push(path);

			// Clear previous handler if any
			if (runningTimeout !== null) {
				clearTimeout(runningTimeout);
				runningTimeout = null;
			}

			// Handle paths delayed in case more are coming!
			runningTimeout = setTimeout(() => {
B
Benjamin Pasero 已提交
140
				this.open({ cli: env.cliArgs, pathsToOpen: macOpenFiles, forceNewWindow: true /* dropping on the dock should force open in a new window */ });
E
Erich Gamma 已提交
141 142 143 144 145
				macOpenFiles = [];
				runningTimeout = null;
			}, 100);
		});

146
		settings.manager.onChange((newSettings) => {
E
Erich Gamma 已提交
147
			this.sendToAll('vscode:optionsChange', JSON.stringify({ globalSettings: newSettings }));
148
		}, this);
E
Erich Gamma 已提交
149 150 151 152 153

		ipc.on('vscode:startCrashReporter', (event: any, config: any) => {
			crashReporter.start(config);
		});

B
Benjamin Pasero 已提交
154
		ipc.on('vscode:windowOpen', (event, paths: string[], forceNewWindow?: boolean) => {
E
Erich Gamma 已提交
155 156 157
			env.log('IPC#vscode-windowOpen: ', paths);

			if (paths && paths.length) {
B
Benjamin Pasero 已提交
158
				this.open({ cli: env.cliArgs, pathsToOpen: paths, forceNewWindow: forceNewWindow });
E
Erich Gamma 已提交
159 160 161
			}
		});

B
Benjamin Pasero 已提交
162
		ipc.on('vscode:workbenchLoaded', (event, windowId: number) => {
E
Erich Gamma 已提交
163 164 165 166 167 168 169 170 171 172 173
			env.log('IPC#vscode-workbenchLoaded');

			let win = this.getWindowById(windowId);
			if (win) {
				win.setReady();

				// Event
				eventEmitter.emit(EventTypes.READY, win);
			}
		});

B
Benjamin Pasero 已提交
174
		ipc.on('vscode:openFilePicker', () => {
E
Erich Gamma 已提交
175 176
			env.log('IPC#vscode-openFilePicker');

B
Benjamin Pasero 已提交
177
			this.openFilePicker();
E
Erich Gamma 已提交
178 179
		});

B
Benjamin Pasero 已提交
180
		ipc.on('vscode:openFolderPicker', () => {
E
Erich Gamma 已提交
181 182
			env.log('IPC#vscode-openFolderPicker');

B
Benjamin Pasero 已提交
183
			this.openFolderPicker();
E
Erich Gamma 已提交
184 185
		});

B
Benjamin Pasero 已提交
186
		ipc.on('vscode:closeFolder', (event, windowId: number) => {
E
Erich Gamma 已提交
187 188 189 190
			env.log('IPC#vscode-closeFolder');

			let win = this.getWindowById(windowId);
			if (win) {
B
Benjamin Pasero 已提交
191
				this.open({ cli: env.cliArgs, forceEmpty: true, windowToUse: win });
E
Erich Gamma 已提交
192 193 194
			}
		});

B
Benjamin Pasero 已提交
195
		ipc.on('vscode:openNewWindow', () => {
E
Erich Gamma 已提交
196 197
			env.log('IPC#vscode-openNewWindow');

B
Benjamin Pasero 已提交
198
			this.openNewWindow();
E
Erich Gamma 已提交
199 200
		});

B
Benjamin Pasero 已提交
201
		ipc.on('vscode:openFileFolderPicker', () => {
E
Erich Gamma 已提交
202 203
			env.log('IPC#vscode-openFileFolderPicker');

B
Benjamin Pasero 已提交
204
			this.openFolderPicker();
E
Erich Gamma 已提交
205 206
		});

B
Benjamin Pasero 已提交
207
		ipc.on('vscode:reloadWindow', (event, windowId: number) => {
E
Erich Gamma 已提交
208 209 210 211 212 213 214 215
			env.log('IPC#vscode:reloadWindow');

			let vscodeWindow = this.getWindowById(windowId);
			if (vscodeWindow) {
				this.reload(vscodeWindow);
			}
		});

B
Benjamin Pasero 已提交
216
		ipc.on('vscode:toggleFullScreen', (event, windowId: number) => {
E
Erich Gamma 已提交
217 218 219 220 221 222 223 224
			env.log('IPC#vscode:toggleFullScreen');

			let vscodeWindow = this.getWindowById(windowId);
			if (vscodeWindow) {
				vscodeWindow.toggleFullScreen();
			}
		});

B
Benjamin Pasero 已提交
225
		ipc.on('vscode:toggleMenuBar', (event, windowId: number) => {
226 227 228 229 230 231 232 233 234 235 236
			env.log('IPC#vscode:toggleMenuBar');

			// Update in settings
			let menuBarHidden = storage.getItem(window.VSCodeWindow.menuBarHiddenKey, false);
			let newMenuBarHidden = !menuBarHidden;
			storage.setItem(window.VSCodeWindow.menuBarHiddenKey, newMenuBarHidden);

			// Update across windows
			WindowsManager.WINDOWS.forEach(w => w.setMenuBarVisibility(!newMenuBarHidden));
		});

E
Erich Gamma 已提交
237 238
		ipc.on('vscode:changeTheme', (event, theme: string) => {
			this.sendToAll('vscode:changeTheme', theme);
239
			storage.setItem(window.VSCodeWindow.themeStorageKey, theme);
E
Erich Gamma 已提交
240 241
		});

B
Benjamin Pasero 已提交
242
		ipc.on('vscode:broadcast', (event, windowId: number, target: string, broadcast: { channel: string; payload: any; }) => {
E
Erich Gamma 已提交
243
			if (broadcast.channel && broadcast.payload) {
244
				if (target) {
B
Benjamin Pasero 已提交
245
					const otherWindowsWithTarget = WindowsManager.WINDOWS.filter(w => w.id !== windowId && typeof w.openedWorkspacePath === 'string');
246 247 248 249 250
					const directTargetMatch = otherWindowsWithTarget.filter(w => this.isPathEqual(target, w.openedWorkspacePath));
					const parentTargetMatch = otherWindowsWithTarget.filter(w => paths.isEqualOrParent(target, w.openedWorkspacePath));

					const targetWindow = directTargetMatch.length ? directTargetMatch[0] : parentTargetMatch[0]; // prefer direct match over parent match
					if (targetWindow) {
251 252 253 254 255
						targetWindow.send('vscode:broadcast', broadcast);
					}
				} else {
					this.sendToAll('vscode:broadcast', broadcast, [windowId]);
				}
E
Erich Gamma 已提交
256
			}
257 258
		});

B
Benjamin Pasero 已提交
259
		ipc.on('vscode:log', (event, logEntry: ILogEntry) => {
260 261 262 263 264 265 266 267 268 269
			let args = [];
			try {
				let parsed = JSON.parse(logEntry.arguments);
				args.push(...Object.getOwnPropertyNames(parsed).map(o => parsed[o]));
			} catch (error) {
				args.push(logEntry.arguments);
			}

			console[logEntry.severity].apply(console, args);
		});
E
Erich Gamma 已提交
270

B
Benjamin Pasero 已提交
271
		ipc.on('vscode:exit', (event, code: number) => {
272 273 274
			process.exit(code);
		});

E
Erich Gamma 已提交
275 276 277 278 279 280 281 282 283 284
		UpdateManager.on('update-downloaded', (update: IUpdate) => {
			this.sendToFocused('vscode:telemetry', { eventName: 'update:downloaded', data: { version: update.version } });

			this.sendToAll('vscode:update-downloaded', JSON.stringify({
				releaseNotes: update.releaseNotes,
				version: update.version,
				date: update.date
			}));
		});

B
Benjamin Pasero 已提交
285
		ipc.on('vscode:update-apply', () => {
E
Erich Gamma 已提交
286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313
			env.log('IPC#vscode:update-apply');

			if (UpdateManager.availableUpdate) {
				UpdateManager.availableUpdate.quitAndUpdate();
			}
		});

		UpdateManager.on('update-not-available', (explicit: boolean) => {
			this.sendToFocused('vscode:telemetry', { eventName: 'update:notAvailable', data: { explicit } });

			if (explicit) {
				this.sendToFocused('vscode:update-not-available', '');
			}
		});

		lifecycle.onBeforeQuit(() => {

			// 0-1 window open: Do not keep the list but just rely on the active window to be stored
			if (WindowsManager.WINDOWS.length < 2) {
				this.windowsState.openedFolders = [];
				return;
			}

			// 2-N windows open: Keep a list of windows that are opened on a specific folder to restore it in the next session as needed
			this.windowsState.openedFolders = WindowsManager.WINDOWS.filter(w => w.readyState === window.ReadyState.READY && !!w.openedWorkspacePath && !w.isPluginDevelopmentHost).map(w => {
				return <IWindowState>{
					workspacePath: w.openedWorkspacePath,
					uiState: w.serializeWindowState()
B
Benjamin Pasero 已提交
314
				};
E
Erich Gamma 已提交
315 316 317 318 319 320
			});
		});

		app.on('will-quit', () => {
			storage.setItem(WindowsManager.windowsStateStorageKey, this.windowsState);
		});
321 322 323 324 325 326 327 328 329 330 331

		let loggedStartupTimes = false;
		onReady(window => {
			if (loggedStartupTimes) {
				return; // only for the first window
			}

			loggedStartupTimes = true;

			window.send('vscode:telemetry', { eventName: 'startupTime', data: { ellapsed: Date.now() - global.vscodeStart } });
		});
E
Erich Gamma 已提交
332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364
	}

	public reload(win: window.VSCodeWindow, cli?: env.ICommandLineArguments): void {

		// Only reload when the window has not vetoed this
		lifecycle.manager.unload(win).done((veto) => {
			if (!veto) {
				win.reload(cli);
			}
		});
	}

	public open(openConfig: IOpenConfiguration): boolean {
		let iPathsToOpen: window.IPath[];

		// Find paths from provided paths if any
		if (openConfig.pathsToOpen && openConfig.pathsToOpen.length > 0) {
			iPathsToOpen = openConfig.pathsToOpen.map((pathToOpen) => {
				let iPath = this.toIPath(pathToOpen, false, openConfig.cli && openConfig.cli.gotoLineMode);

				// Warn if the requested path to open does not exist
				if (!iPath) {
					let options = {
						title: env.product.nameLong,
						type: 'info',
						buttons: [nls.localize('ok', "OK")],
						message: nls.localize('pathNotExistTitle', "Path does not exist"),
						detail: nls.localize('pathNotExistDetail', "The path '{0}' does not seem to exist anymore on disk.", pathToOpen),
						noLink: true
					};

					let activeWindow = BrowserWindow.getFocusedWindow();
					if (activeWindow) {
365
						dialog.showMessageBox(activeWindow, options);
E
Erich Gamma 已提交
366
					} else {
367
						dialog.showMessageBox(options);
E
Erich Gamma 已提交
368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405
					}
				}

				return iPath;
			});

			// get rid of nulls
			iPathsToOpen = arrays.coalesce(iPathsToOpen);

			if (iPathsToOpen.length === 0) {
				return false; // indicate to outside that open failed
			}
		}

		// Check for force empty
		else if (openConfig.forceEmpty) {
			iPathsToOpen = [Object.create(null)];
		}

		// Otherwise infer from command line arguments
		else {
			let ignoreFileNotFound = openConfig.cli.pathArguments.length > 0; // we assume the user wants to create this file from command line
			iPathsToOpen = this.cliToPaths(openConfig.cli, ignoreFileNotFound);
		}

		let filesToOpen = iPathsToOpen.filter((iPath) => !!iPath.filePath && !iPath.createFilePath && !iPath.installExtensionPath);
		let filesToCreate = iPathsToOpen.filter((iPath) => !!iPath.filePath && iPath.createFilePath && !iPath.installExtensionPath);
		let foldersToOpen = iPathsToOpen.filter((iPath) => iPath.workspacePath && !iPath.filePath && !iPath.installExtensionPath);
		let emptyToOpen = iPathsToOpen.filter((iPath) => !iPath.workspacePath && !iPath.filePath && !iPath.installExtensionPath);
		let extensionsToInstall = iPathsToOpen.filter((iPath) => iPath.installExtensionPath).map(ipath => ipath.filePath);

		let configuration: window.IWindowConfiguration;

		// Handle files to open or to create when we dont open a folder
		if (!foldersToOpen.length && (filesToOpen.length > 0 || filesToCreate.length > 0 || extensionsToInstall.length > 0)) {

			// Let the user settings override how files are open in a new window or same window
			let openFilesInNewWindow = openConfig.forceNewWindow;
A
Alex Dima 已提交
406
			if (openFilesInNewWindow && !openConfig.cli.extensionDevelopmentPath) { // can be overriden via settings (not for PDE though!)
B
Benjamin Pasero 已提交
407
				openFilesInNewWindow = settings.manager.getValue('window.openFilesInNewWindow', openFilesInNewWindow);
E
Erich Gamma 已提交
408 409 410 411 412
			}

			// Open Files in last instance if any and flag tells us so
			let lastActiveWindow = this.getLastActiveWindow();
			if (!openFilesInNewWindow && lastActiveWindow) {
B
Benjamin Pasero 已提交
413
				lastActiveWindow.focus();
E
Erich Gamma 已提交
414
				lastActiveWindow.ready().then((readyWindow) => {
415
					readyWindow.send('vscode:openFiles', {
E
Erich Gamma 已提交
416 417 418 419 420
						filesToOpen: filesToOpen,
						filesToCreate: filesToCreate
					});

					if (extensionsToInstall.length) {
421
						readyWindow.send('vscode:installExtensions', { extensionsToInstall });
E
Erich Gamma 已提交
422 423 424 425 426 427
					}
				});
			}

			// Otherwise open instance with files
			else {
428
				configuration = this.toConfiguration(openConfig.userEnv || this.initialUserEnv, openConfig.cli, null, filesToOpen, filesToCreate, extensionsToInstall);
E
Erich Gamma 已提交
429 430 431 432 433 434 435 436 437 438 439 440
				this.openInBrowserWindow(configuration, true /* new window */);

				openConfig.forceNewWindow = true; // any other folders to open must open in new window then
			}
		}

		// Handle folders to open
		if (foldersToOpen.length > 0) {

			// Check for existing instances
			let windowsOnWorkspacePath = arrays.coalesce(foldersToOpen.map((iPath) => this.findWindow(iPath.workspacePath)));
			if (windowsOnWorkspacePath.length > 0) {
B
Benjamin Pasero 已提交
441
				windowsOnWorkspacePath[0].focus(); // just focus one of them
E
Erich Gamma 已提交
442
				windowsOnWorkspacePath[0].ready().then((readyWindow) => {
443
					readyWindow.send('vscode:openFiles', {
E
Erich Gamma 已提交
444 445 446 447 448
						filesToOpen: filesToOpen,
						filesToCreate: filesToCreate
					});

					if (extensionsToInstall.length) {
449
						readyWindow.send('vscode:installExtensions', { extensionsToInstall });
E
Erich Gamma 已提交
450 451 452 453 454 455 456 457 458 459 460 461 462
					}
				});

				// Reset these because we handled them
				filesToOpen = [];
				filesToCreate = [];
				extensionsToInstall = [];

				openConfig.forceNewWindow = true; // any other folders to open must open in new window then
			}

			// Open remaining ones
			foldersToOpen.forEach((folderToOpen) => {
B
Benjamin Pasero 已提交
463
				if (windowsOnWorkspacePath.some((win) => this.isPathEqual(win.openedWorkspacePath, folderToOpen.workspacePath))) {
E
Erich Gamma 已提交
464 465 466
					return; // ignore folders that are already open
				}

467
				configuration = this.toConfiguration(openConfig.userEnv || this.initialUserEnv, openConfig.cli, folderToOpen.workspacePath, filesToOpen, filesToCreate, extensionsToInstall);
E
Erich Gamma 已提交
468 469 470 471 472 473 474 475 476 477 478 479 480 481
				this.openInBrowserWindow(configuration, openConfig.forceNewWindow, openConfig.forceNewWindow ? void 0 : openConfig.windowToUse);

				// Reset these because we handled them
				filesToOpen = [];
				filesToCreate = [];
				extensionsToInstall = [];

				openConfig.forceNewWindow = true; // any other folders to open must open in new window then
			});
		}

		// Handle empty
		if (emptyToOpen.length > 0) {
			emptyToOpen.forEach(() => {
482
				let configuration = this.toConfiguration(openConfig.userEnv || this.initialUserEnv, openConfig.cli);
E
Erich Gamma 已提交
483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506
				this.openInBrowserWindow(configuration, openConfig.forceNewWindow, openConfig.forceNewWindow ? void 0 : openConfig.windowToUse);

				openConfig.forceNewWindow = true; // any other folders to open must open in new window then
			});
		}

		// Remember in recent document list
		iPathsToOpen.forEach((iPath) => {
			if (iPath.filePath || iPath.workspacePath) {
				app.addRecentDocument(iPath.filePath || iPath.workspacePath);
			}
		});

		// Emit events
		iPathsToOpen.forEach((iPath) => eventEmitter.emit(EventTypes.OPEN, iPath));

		return true;
	}

	public openPluginDevelopmentHostWindow(openConfig: IOpenConfiguration): void {

		// Reload an existing plugin development host window on the same path
		// We currently do not allow more than one extension development window
		// on the same plugin path.
A
Alex Dima 已提交
507
		let res = WindowsManager.WINDOWS.filter((w) => w.config && this.isPathEqual(w.config.extensionDevelopmentPath, openConfig.cli.extensionDevelopmentPath));
E
Erich Gamma 已提交
508 509
		if (res && res.length === 1) {
			this.reload(res[0], openConfig.cli);
B
Benjamin Pasero 已提交
510
			res[0].focus(); // make sure it gets focus and is restored
E
Erich Gamma 已提交
511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534

			return;
		}

		// Fill in previously opened workspace unless an explicit path is provided
		if (openConfig.cli.pathArguments.length === 0) {
			let workspaceToOpen = this.windowsState.lastPluginDevelopmentHostWindow && this.windowsState.lastPluginDevelopmentHostWindow.workspacePath;
			if (workspaceToOpen) {
				openConfig.cli.pathArguments = [workspaceToOpen];
			}
		}

		// Make sure we are not asked to open a path that is already opened
		if (openConfig.cli.pathArguments.length > 0) {
			res = WindowsManager.WINDOWS.filter((w) => w.openedWorkspacePath && openConfig.cli.pathArguments.indexOf(w.openedWorkspacePath) >= 0);
			if (res.length) {
				openConfig.cli.pathArguments = [];
			}
		}

		// Open it
		this.open({ cli: openConfig.cli, forceNewWindow: true, forceEmpty: openConfig.cli.pathArguments.length === 0 });
	}

535
	private toConfiguration(userEnv: env.IProcessEnvironment, cli: env.ICommandLineArguments, workspacePath?: string, filesToOpen?: window.IPath[], filesToCreate?: window.IPath[], extensionsToInstall?: string[]): window.IWindowConfiguration {
E
Erich Gamma 已提交
536 537 538 539 540 541 542
		let configuration: window.IWindowConfiguration = objects.mixin({}, cli); // inherit all properties from CLI
		configuration.execPath = process.execPath;
		configuration.workspacePath = workspacePath;
		configuration.filesToOpen = filesToOpen;
		configuration.filesToCreate = filesToCreate;
		configuration.extensionsToInstall = extensionsToInstall;
		configuration.appName = env.product.nameLong;
J
Joao Moreno 已提交
543 544
		configuration.applicationName = env.product.applicationName;
		configuration.darwinBundleIdentifier = env.product.darwinBundleIdentifier;
E
Erich Gamma 已提交
545 546 547 548 549 550
		configuration.appRoot = env.appRoot;
		configuration.version = env.version;
		configuration.commitHash = env.product.commit;
		configuration.appSettingsHome = env.appSettingsHome;
		configuration.appSettingsPath = env.appSettingsPath;
		configuration.appKeybindingsPath = env.appKeybindingsPath;
A
Alex Dima 已提交
551
		configuration.userExtensionsHome = env.userExtensionsHome;
E
Erich Gamma 已提交
552 553 554 555 556 557 558 559 560 561 562
		configuration.sharedIPCHandle = env.sharedIPCHandle;
		configuration.isBuilt = env.isBuilt;
		configuration.crashReporter = env.product.crashReporter;
		configuration.extensionsGallery = env.product.extensionsGallery;
		configuration.welcomePage = env.product.welcomePage;
		configuration.productDownloadUrl = env.product.downloadUrl;
		configuration.releaseNotesUrl = env.product.releaseNotesUrl;
		configuration.updateFeedUrl = UpdateManager.feedUrl;
		configuration.updateChannel = UpdateManager.channel;
		configuration.recentPaths = this.getRecentlyOpenedPaths(workspacePath, filesToOpen);
		configuration.aiConfig = env.product.aiConfig;
S
Sofian Hnaide 已提交
563
		configuration.sendASmile = env.product.sendASmile;
E
Erich Gamma 已提交
564
		configuration.enableTelemetry = env.product.enableTelemetry;
565
		configuration.userEnv = userEnv;
E
Erich Gamma 已提交
566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589

		return configuration;
	}

	private getRecentlyOpenedPaths(workspacePath?: string, filesToOpen?: window.IPath[]): string[] {

		// Get from storage
		let openedPathsList = storage.getItem<IOpenedPathsList>(WindowsManager.openedPathsListStorageKey);
		if (!openedPathsList) {
			openedPathsList = { folders: [], files: [] };
		}

		let recentPaths = openedPathsList.folders.concat(openedPathsList.files);

		// Add currently files to open to the beginning if any
		if (filesToOpen) {
			recentPaths.unshift(...filesToOpen.map(f => f.filePath));
		}

		// Add current workspace path to beginning if set
		if (workspacePath) {
			recentPaths.unshift(workspacePath);
		}

590
		// Clear those dupes
E
Erich Gamma 已提交
591 592
		recentPaths = arrays.distinct(recentPaths);

593
		// Make sure it is bounded
B
Benjamin Pasero 已提交
594
		return recentPaths.slice(0, 10);
E
Erich Gamma 已提交
595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660 661 662 663 664 665 666 667 668 669 670 671 672 673 674 675 676 677
	}

	private toIPath(anyPath: string, ignoreFileNotFound?: boolean, gotoLineMode?: boolean): window.IPath {
		if (!anyPath) {
			return null;
		}

		let parsedPath: env.IParsedPath;
		if (gotoLineMode) {
			parsedPath = env.parseLineAndColumnAware(anyPath);
			anyPath = parsedPath.path;
		}

		let candidate = path.normalize(anyPath);
		try {
			let candidateStat = fs.statSync(candidate);
			if (candidateStat) {
				return candidateStat.isFile() ?
					{
						filePath: candidate,
						lineNumber: gotoLineMode ? parsedPath.line : void 0,
						columnNumber: gotoLineMode ? parsedPath.column : void 0,
						installExtensionPath: /\.vsix$/i.test(candidate)
					} :
					{ workspacePath: candidate };
			}
		} catch (error) {
			if (ignoreFileNotFound) {
				return { filePath: candidate, createFilePath: true }; // assume this is a file that does not yet exist
			}
		}

		return null;
	}

	private cliToPaths(cli: env.ICommandLineArguments, ignoreFileNotFound?: boolean): window.IPath[] {

		// Check for pass in candidate or last opened path
		let candidates: string[] = [];
		if (cli.pathArguments.length > 0) {
			candidates = cli.pathArguments;
		}

		// No path argument, check settings for what to do now
		else {
			let reopenFolders = settings.manager.getValue('window.reopenFolders', 'one');
			let lastActiveFolder = this.windowsState.lastActiveWindow && this.windowsState.lastActiveWindow.workspacePath;

			// Restore all
			if (reopenFolders === 'all') {
				let lastOpenedFolders = this.windowsState.openedFolders.map(o => o.workspacePath);

				// If we have a last active folder, move it to the end
				if (lastActiveFolder) {
					lastOpenedFolders.splice(lastOpenedFolders.indexOf(lastActiveFolder), 1);
					lastOpenedFolders.push(lastActiveFolder);
				}

				candidates.push(...lastOpenedFolders);
			}

			// Restore last active
			else if (lastActiveFolder && (reopenFolders === 'one' || reopenFolders !== 'none')) {
				candidates.push(lastActiveFolder);
			}
		}

		let iPaths = candidates.map((candidate) => this.toIPath(candidate, ignoreFileNotFound, cli.gotoLineMode)).filter((path) => !!path);
		if (iPaths.length > 0) {
			return iPaths;
		}

		// No path provided, return empty to open empty
		return [Object.create(null)];
	}

	private openInBrowserWindow(configuration: window.IWindowConfiguration, forceNewWindow?: boolean, windowToUse?: window.VSCodeWindow): void {
		let vscodeWindow: window.VSCodeWindow;

		if (!forceNewWindow) {
			vscodeWindow = windowToUse || this.getLastActiveWindow();

			if (vscodeWindow) {
B
Benjamin Pasero 已提交
678
				vscodeWindow.focus();
E
Erich Gamma 已提交
679 680 681 682 683
			}
		}

		// New window
		if (!vscodeWindow) {
684 685
			vscodeWindow = new window.VSCodeWindow({
				state: this.getNewWindowState(configuration),
A
Alex Dima 已提交
686
				isPluginDevelopmentHost: !!configuration.extensionDevelopmentPath
687 688
			});

E
Erich Gamma 已提交
689 690 691
			WindowsManager.WINDOWS.push(vscodeWindow);

			// Window Events
692 693
			vscodeWindow.win.webContents.on('crashed', () => this.onWindowError(vscodeWindow, WindowError.CRASHED));
			vscodeWindow.win.on('unresponsive', () => this.onWindowError(vscodeWindow, WindowError.UNRESPONSIVE));
E
Erich Gamma 已提交
694 695 696 697 698 699 700 701 702 703 704 705 706
			vscodeWindow.win.on('close', () => this.onBeforeWindowClose(vscodeWindow));
			vscodeWindow.win.on('closed', () => this.onWindowClosed(vscodeWindow));

			// Lifecycle
			lifecycle.manager.registerWindow(vscodeWindow);
		}

		// Existing window
		else {

			// Some configuration things get inherited if the window is being reused and we are
			// in plugin development host mode. These options are all development related.
			let currentWindowConfig = vscodeWindow.config;
A
Alex Dima 已提交
707 708
			if (!configuration.extensionDevelopmentPath && currentWindowConfig && !!currentWindowConfig.extensionDevelopmentPath) {
				configuration.extensionDevelopmentPath = currentWindowConfig.extensionDevelopmentPath;
E
Erich Gamma 已提交
709 710 711 712
				configuration.verboseLogging = currentWindowConfig.verboseLogging;
				configuration.logPluginHostCommunication = currentWindowConfig.logPluginHostCommunication;
				configuration.debugBrkPluginHost = currentWindowConfig.debugBrkPluginHost;
				configuration.debugPluginHostPort = currentWindowConfig.debugPluginHostPort;
713
				configuration.pluginHomePath = currentWindowConfig.pluginHomePath;
E
Erich Gamma 已提交
714 715 716 717 718 719 720 721 722 723 724 725 726 727 728 729
			}
		}

		// Only load when the window has not vetoed this
		lifecycle.manager.unload(vscodeWindow).done((veto) => {
			if (!veto) {

				// Load it
				vscodeWindow.load(configuration);
			}
		});
	}

	private getNewWindowState(configuration: window.IWindowConfiguration): window.IWindowState {

		// plugin development host Window - load from stored settings if any
A
Alex Dima 已提交
730
		if (!!configuration.extensionDevelopmentPath && this.windowsState.lastPluginDevelopmentHostWindow) {
E
Erich Gamma 已提交
731 732 733 734 735
			return this.windowsState.lastPluginDevelopmentHostWindow.uiState;
		}

		// Known Folder - load from stored settings if any
		if (configuration.workspacePath) {
B
Benjamin Pasero 已提交
736
			let stateForWorkspace = this.windowsState.openedFolders.filter(o => this.isPathEqual(o.workspacePath, configuration.workspacePath)).map(o => o.uiState);
E
Erich Gamma 已提交
737 738 739 740 741 742 743 744 745 746 747 748 749 750 751 752
			if (stateForWorkspace.length) {
				return stateForWorkspace[0];
			}
		}

		// First Window
		let lastActive = this.getLastActiveWindow();
		if (!lastActive && this.windowsState.lastActiveWindow) {
			return this.windowsState.lastActiveWindow.uiState;
		}

		//
		// In any other case, we do not have any stored settings for the window state, so we come up with something smart
		//

		// We want the new window to open on the same display that the last active one is in
753
		let displayToUse: Electron.Display;
E
Erich Gamma 已提交
754 755 756 757 758 759 760 761 762 763 764 765 766 767 768 769 770 771 772 773 774 775 776 777 778 779 780 781 782 783 784 785 786 787 788 789 790 791 792 793 794 795 796 797 798 799 800 801 802 803 804 805 806 807 808 809 810 811 812 813 814 815 816 817 818
		let displays = screen.getAllDisplays();

		// Single Display
		if (displays.length === 1) {
			displayToUse = displays[0];
		}

		// Multi Display
		else {

			// on mac there is 1 menu per window so we need to use the monitor where the cursor currently is
			if (platform.isMacintosh) {
				let cursorPoint = screen.getCursorScreenPoint();
				displayToUse = screen.getDisplayNearestPoint(cursorPoint);
			}

			// if we have a last active window, use that display for the new window
			if (!displayToUse && lastActive) {
				displayToUse = screen.getDisplayMatching(lastActive.getBounds());
			}

			// fallback to first display
			if (!displayToUse) {
				displayToUse = displays[0];
			}
		}

		let defaultState = window.defaultWindowState();
		defaultState.x = displayToUse.bounds.x + (displayToUse.bounds.width / 2) - (defaultState.width / 2);
		defaultState.y = displayToUse.bounds.y + (displayToUse.bounds.height / 2) - (defaultState.height / 2);

		return this.ensureNoOverlap(defaultState);
	}

	private ensureNoOverlap(state: window.IWindowState): window.IWindowState {
		if (WindowsManager.WINDOWS.length === 0) {
			return state;
		}

		let existingWindowBounds = WindowsManager.WINDOWS.map((win) => win.getBounds());
		while (existingWindowBounds.some((b) => b.x === state.x || b.y === state.y)) {
			state.x += 30;
			state.y += 30;
		}

		return state;
	}

	public openFilePicker(): void {
		this.getFileOrFolderPaths(false, (paths: string[]) => {
			if (paths && paths.length) {
				this.open({ cli: env.cliArgs, pathsToOpen: paths });
			}
		});
	}

	public openFolderPicker(): void {
		this.getFileOrFolderPaths(true, (paths: string[]) => {
			if (paths && paths.length) {
				this.open({ cli: env.cliArgs, pathsToOpen: paths });
			}
		});
	}

	private getFileOrFolderPaths(isFolder: boolean, clb: (paths: string[]) => void): void {
819
		let workingDir = storage.getItem<string>(WindowsManager.workingDirPickerStorageKey);
E
Erich Gamma 已提交
820 821 822 823 824 825 826 827 828
		let focussedWindow = this.getFocusedWindow();

		let pickerProperties: string[];
		if (platform.isMacintosh) {
			pickerProperties = ['multiSelections', 'openDirectory', 'openFile', 'createDirectory'];
		} else {
			pickerProperties = ['multiSelections', isFolder ? 'openDirectory' : 'openFile', 'createDirectory'];
		}

829
		dialog.showOpenDialog(focussedWindow && focussedWindow.win, {
E
Erich Gamma 已提交
830 831 832 833 834 835
			defaultPath: workingDir,
			properties: pickerProperties
		}, (paths) => {
			if (paths && paths.length > 0) {

				// Remember path in storage for next time
836
				storage.setItem(WindowsManager.workingDirPickerStorageKey, path.dirname(paths[0]));
E
Erich Gamma 已提交
837 838 839 840 841 842 843 844 845 846 847 848

				// Return
				clb(paths);
			} else {
				clb(void (0));
			}
		});
	}

	public focusLastActive(cli: env.ICommandLineArguments): void {
		let lastActive = this.getLastActiveWindow();
		if (lastActive) {
B
Benjamin Pasero 已提交
849
			lastActive.focus();
E
Erich Gamma 已提交
850 851 852 853 854 855 856 857 858 859 860 861 862 863 864 865 866 867 868 869 870
		}

		// No window - open new one
		else {
			this.windowsState.openedFolders = []; // make sure we do not open too much
			this.open({ cli: cli });
		}
	}

	public getLastActiveWindow(): window.VSCodeWindow {
		if (WindowsManager.WINDOWS.length) {
			let lastFocussedDate = Math.max.apply(Math, WindowsManager.WINDOWS.map((w) => w.lastFocusTime));
			let res = WindowsManager.WINDOWS.filter((w) => w.lastFocusTime === lastFocussedDate);
			if (res && res.length) {
				return res[0];
			}
		}

		return null;
	}

871
	public findWindow(workspacePath: string, filePath?: string): window.VSCodeWindow {
E
Erich Gamma 已提交
872 873 874 875 876 877 878 879 880 881 882 883 884 885
		if (WindowsManager.WINDOWS.length) {

			// Sort the last active window to the front of the array of windows to test
			let windowsToTest = WindowsManager.WINDOWS.slice(0);
			let lastActiveWindow = this.getLastActiveWindow();
			if (lastActiveWindow) {
				windowsToTest.splice(windowsToTest.indexOf(lastActiveWindow), 1);
				windowsToTest.unshift(lastActiveWindow);
			}

			// Find it
			let res = windowsToTest.filter((w) => {

				// match on workspace
886
				if (typeof w.openedWorkspacePath === 'string' && (this.isPathEqual(w.openedWorkspacePath, workspacePath))) {
E
Erich Gamma 已提交
887 888 889 890
					return true;
				}

				// match on file
B
Benjamin Pasero 已提交
891
				if (typeof w.openedFilePath === 'string' && this.isPathEqual(w.openedFilePath, filePath)) {
E
Erich Gamma 已提交
892 893 894 895 896 897 898 899 900 901 902 903 904 905 906 907 908 909 910 911 912 913 914 915 916 917 918
					return true;
				}

				// match on file path
				if (typeof w.openedWorkspacePath === 'string' && filePath && paths.isEqualOrParent(filePath, w.openedWorkspacePath)) {
					return true;
				}

				return false;
			});

			if (res && res.length) {
				return res[0];
			}
		}

		return null;
	}

	public openNewWindow(): void {
		this.open({ cli: env.cliArgs, forceNewWindow: true, forceEmpty: true });
	}

	public sendToFocused(channel: string, ...args: any[]): void {
		const focusedWindow = this.getFocusedWindow() || this.getLastActiveWindow();

		if (focusedWindow) {
919
			focusedWindow.sendWhenReady(channel, ...args);
E
Erich Gamma 已提交
920 921 922 923 924
		}
	}

	public sendToAll(channel: string, payload: any, windowIdsToIgnore?: number[]): void {
		WindowsManager.WINDOWS.forEach((w) => {
B
Benjamin Pasero 已提交
925
			if (windowIdsToIgnore && windowIdsToIgnore.indexOf(w.id) >= 0) {
E
Erich Gamma 已提交
926 927 928
				return; // do not send if we are instructed to ignore it
			}

929
			w.sendWhenReady(channel, payload);
E
Erich Gamma 已提交
930 931 932 933 934 935 936 937 938 939 940 941 942
		});
	}

	public getFocusedWindow(): window.VSCodeWindow {
		let win = BrowserWindow.getFocusedWindow();
		if (win) {
			return this.getWindowById(win.id);
		}

		return null;
	}

	public getWindowById(windowId: number): window.VSCodeWindow {
B
Benjamin Pasero 已提交
943
		let res = WindowsManager.WINDOWS.filter((w) => w.id === windowId);
E
Erich Gamma 已提交
944 945 946 947 948 949 950 951 952 953 954 955 956 957 958
		if (res && res.length === 1) {
			return res[0];
		}

		return null;
	}

	public getWindows(): window.VSCodeWindow[] {
		return WindowsManager.WINDOWS;
	}

	public getWindowCount(): number {
		return WindowsManager.WINDOWS.length;
	}

959
	private onWindowError(vscodeWindow: window.VSCodeWindow, error: WindowError): void {
E
Erich Gamma 已提交
960 961 962 963
		console.error(error === WindowError.CRASHED ? '[VS Code]: render process crashed!' : '[VS Code]: detected unresponsive');

		// Unresponsive
		if (error === WindowError.UNRESPONSIVE) {
964
			dialog.showMessageBox(vscodeWindow.win, {
E
Erich Gamma 已提交
965 966
				title: env.product.nameLong,
				type: 'warning',
B
Benjamin Pasero 已提交
967
				buttons: [nls.localize('reopen', "Reopen"), nls.localize('wait', "Keep Waiting"), nls.localize('close', "Close")],
968
				message: nls.localize('appStalled', "The window is no longer responding"),
B
Benjamin Pasero 已提交
969
				detail: nls.localize('appStalledDetail', "You can reopen or close the window or keep waiting."),
E
Erich Gamma 已提交
970 971 972
				noLink: true
			}, (result) => {
				if (result === 0) {
973 974
					vscodeWindow.reload();
				} else if (result === 2) {
975
					this.onBeforeWindowClose(vscodeWindow); // 'close' event will not be fired on destroy(), so run it manually
976
					vscodeWindow.win.destroy(); // make sure to destroy the window as it is unresponsive
E
Erich Gamma 已提交
977 978 979 980 981 982
				}
			});
		}

		// Crashed
		else {
983
			dialog.showMessageBox(vscodeWindow.win, {
E
Erich Gamma 已提交
984 985
				title: env.product.nameLong,
				type: 'warning',
B
Benjamin Pasero 已提交
986
				buttons: [nls.localize('reopen', "Reopen"), nls.localize('close', "Close")],
987
				message: nls.localize('appCrashed', "The window has crashed"),
B
Benjamin Pasero 已提交
988
				detail: nls.localize('appCrashedDetail', "We are sorry for the inconvenience! You can reopen the window to continue where you left off."),
E
Erich Gamma 已提交
989 990
				noLink: true
			}, (result) => {
991 992 993
				if (result === 0) {
					vscodeWindow.reload();
				} else if (result === 1) {
994
					this.onBeforeWindowClose(vscodeWindow); // 'close' event will not be fired on destroy(), so run it manually
995 996
					vscodeWindow.win.destroy(); // make sure to destroy the window as it has crashed
				}
E
Erich Gamma 已提交
997 998 999 1000 1001 1002 1003 1004 1005 1006 1007 1008 1009 1010 1011 1012 1013
			});
		}
	}

	private onBeforeWindowClose(win: window.VSCodeWindow): void {
		if (win.readyState !== window.ReadyState.READY) {
			return; // only persist windows that are fully loaded
		}

		// On Window close, update our stored state of this window
		let state: IWindowState = { workspacePath: win.openedWorkspacePath, uiState: win.serializeWindowState() };
		if (win.isPluginDevelopmentHost) {
			this.windowsState.lastPluginDevelopmentHostWindow = state;
		} else {
			this.windowsState.lastActiveWindow = state;

			this.windowsState.openedFolders.forEach(o => {
B
Benjamin Pasero 已提交
1014
				if (this.isPathEqual(o.workspacePath, win.openedWorkspacePath)) {
E
Erich Gamma 已提交
1015 1016 1017 1018 1019 1020 1021 1022 1023 1024 1025 1026 1027 1028 1029 1030 1031 1032
					o.uiState = state.uiState;
				}
			});
		}
	}

	private onWindowClosed(win: window.VSCodeWindow): void {

		// Tell window
		win.dispose();

		// Remove from our list so that Electron can clean it up
		let index = WindowsManager.WINDOWS.indexOf(win);
		WindowsManager.WINDOWS.splice(index, 1);

		// Emit
		eventEmitter.emit(EventTypes.CLOSE, WindowsManager.WINDOWS.length);
	}
B
Benjamin Pasero 已提交
1033 1034 1035 1036 1037 1038 1039 1040 1041 1042 1043 1044 1045 1046 1047 1048 1049 1050 1051 1052 1053 1054 1055 1056

	private isPathEqual(pathA: string, pathB: string): boolean {
		if (pathA === pathB) {
			return true;
		}

		if (!pathA || !pathB) {
			return false;
		}

		pathA = path.normalize(pathA);
		pathB = path.normalize(pathB);

		if (pathA === pathB) {
			return true;
		}

		if (!platform.isLinux) {
			pathA = pathA.toLowerCase();
			pathB = pathB.toLowerCase();
		}

		return pathA === pathB;
	}
E
Erich Gamma 已提交
1057 1058 1059
}

export const manager = new WindowsManager();