windows.ts 45.4 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 path from 'path';
B
Benjamin Pasero 已提交
9
import * as os from 'os';
B
Benjamin Pasero 已提交
10
import * as fs from 'original-fs';
J
Joao Moreno 已提交
11 12 13
import * as platform from 'vs/base/common/platform';
import * as nls from 'vs/nls';
import * as paths from 'vs/base/common/paths';
14
import * as types from 'vs/base/common/types';
J
Joao Moreno 已提交
15
import * as arrays from 'vs/base/common/arrays';
16
import { assign, mixin } from 'vs/base/common/objects';
J
Joao Moreno 已提交
17
import { EventEmitter } from 'events';
18
import { IBackupService } from 'vs/platform/backup/common/backup';
19
import { IEnvironmentService } from 'vs/platform/environment/common/environment';
J
Joao Moreno 已提交
20
import { IStorageService } from 'vs/code/electron-main/storage';
21
import { IPath, VSCodeWindow, ReadyState, IWindowConfiguration, IWindowState as ISingleWindowState, defaultWindowState, IWindowSettings } from 'vs/code/electron-main/window';
J
Joao Moreno 已提交
22
import { ipcMain as ipc, app, screen, crashReporter, BrowserWindow, dialog } from 'electron';
23
import { IPathWithLineAndColumn, parseLineAndColumnAware } from 'vs/code/electron-main/paths';
24
import { ILifecycleService } from 'vs/code/electron-main/lifecycle';
25
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
J
Joao Moreno 已提交
26
import { IUpdateService, IUpdate } from 'vs/code/electron-main/update-manager';
B
Benjamin Pasero 已提交
27
import { ILogService } from 'vs/code/electron-main/log';
S
Sandeep Somavarapu 已提交
28
import { IWindowEventService } from 'vs/code/common/windows';
J
Johannes Rieken 已提交
29
import { createDecorator, IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
S
Sandeep Somavarapu 已提交
30
import CommonEvent, { Emitter } from 'vs/base/common/event';
B
Benjamin Pasero 已提交
31
import product from 'vs/platform/product';
B
Benjamin Pasero 已提交
32
import { ParsedArgs } from 'vs/platform/environment/node/argv';
E
Erich Gamma 已提交
33 34 35 36 37 38 39 40 41 42 43 44 45

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

enum WindowError {
	UNRESPONSIVE,
	CRASHED
}

export interface IOpenConfiguration {
B
Benjamin Pasero 已提交
46
	cli: ParsedArgs;
47
	userEnv?: platform.IProcessEnvironment;
E
Erich Gamma 已提交
48
	pathsToOpen?: string[];
49
	preferNewWindow?: boolean;
E
Erich Gamma 已提交
50 51
	forceNewWindow?: boolean;
	forceEmpty?: boolean;
J
Joao Moreno 已提交
52
	windowToUse?: VSCodeWindow;
53
	diffMode?: boolean;
54
	restoreBackups?: boolean;
E
Erich Gamma 已提交
55 56 57 58
}

interface IWindowState {
	workspacePath?: string;
J
Joao Moreno 已提交
59
	uiState: ISingleWindowState;
E
Erich Gamma 已提交
60 61 62 63 64 65 66 67
}

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

68
export interface IRecentPathsList {
E
Erich Gamma 已提交
69 70 71 72
	folders: string[];
	files: string[];
}

73 74 75 76 77
interface ILogEntry {
	severity: string;
	arguments: any;
}

78 79 80
interface INativeOpenDialogOptions {
	pickFolders?: boolean;
	pickFiles?: boolean;
81 82
	path?: string;
	forceNewWindow?: boolean;
83 84
}

85 86 87 88 89 90
const ReopenFoldersSetting = {
	ALL: 'all',
	ONE: 'one',
	NONE: 'none'
};

J
renames  
Joao Moreno 已提交
91
export const IWindowsService = createDecorator<IWindowsService>('windowsService');
J
Joao Moreno 已提交
92

J
renames  
Joao Moreno 已提交
93
export interface IWindowsService {
94
	_serviceBrand: any;
J
Joao Moreno 已提交
95 96 97

	// TODO make proper events
	// events
J
Joao Moreno 已提交
98 99
	onOpen(clb: (path: IPath) => void): () => void;
	onReady(clb: (win: VSCodeWindow) => void): () => void;
J
Joao Moreno 已提交
100
	onClose(clb: (id: number) => void): () => void;
S
Sandeep Somavarapu 已提交
101 102
	onNewWindowOpen: CommonEvent<number>;
	onWindowFocus: CommonEvent<number>;
J
Joao Moreno 已提交
103 104

	// methods
105
	ready(initialUserEnv: platform.IProcessEnvironment): void;
B
Benjamin Pasero 已提交
106
	reload(win: VSCodeWindow, cli?: ParsedArgs): void;
J
Joao Moreno 已提交
107
	open(openConfig: IOpenConfiguration): VSCodeWindow[];
J
Joao Moreno 已提交
108 109 110 111
	openPluginDevelopmentHostWindow(openConfig: IOpenConfiguration): void;
	openFileFolderPicker(forceNewWindow?: boolean): void;
	openFilePicker(forceNewWindow?: boolean): void;
	openFolderPicker(forceNewWindow?: boolean): void;
112
	openAccessibilityOptions(): void;
B
Benjamin Pasero 已提交
113
	focusLastActive(cli: ParsedArgs): VSCodeWindow;
J
Joao Moreno 已提交
114 115
	getLastActiveWindow(): VSCodeWindow;
	findWindow(workspacePath: string, filePath?: string, extensionDevelopmentPath?: string): VSCodeWindow;
J
Joao Moreno 已提交
116 117 118
	openNewWindow(): void;
	sendToFocused(channel: string, ...args: any[]): void;
	sendToAll(channel: string, payload: any, windowIdsToIgnore?: number[]): void;
J
Joao Moreno 已提交
119 120 121
	getFocusedWindow(): VSCodeWindow;
	getWindowById(windowId: number): VSCodeWindow;
	getWindows(): VSCodeWindow[];
J
Joao Moreno 已提交
122
	getWindowCount(): number;
123 124 125
	getRecentPathsList(): IRecentPathsList;
	removeFromRecentPathsList(path: string);
	clearRecentPathsList(): void;
J
Joao Moreno 已提交
126 127
}

S
Sandeep Somavarapu 已提交
128 129 130 131
export class WindowEventService implements IWindowEventService {

	_serviceBrand: any;

J
Johannes Rieken 已提交
132
	constructor( @IWindowsService private windowsService: IWindowsService) { }
S
Sandeep Somavarapu 已提交
133 134 135 136 137 138 139 140 141 142

	public get onWindowFocus(): CommonEvent<number> {
		return this.windowsService.onWindowFocus;
	}

	public get onNewWindowOpen(): CommonEvent<number> {
		return this.windowsService.onNewWindowOpen;
	}
}

J
renames  
Joao Moreno 已提交
143
export class WindowsManager implements IWindowsService {
J
Joao Moreno 已提交
144

145
	_serviceBrand: any;
E
Erich Gamma 已提交
146

147
	private static MAX_TOTAL_RECENT_ENTRIES = 100;
148

149
	private static recentPathsListStorageKey = 'openedPathsList';
150
	private static workingDirPickerStorageKey = 'pickerWorkingDir';
E
Erich Gamma 已提交
151 152
	private static windowsStateStorageKey = 'windowsState';

J
Joao Moreno 已提交
153
	private static WINDOWS: VSCodeWindow[] = [];
E
Erich Gamma 已提交
154

J
Joao Moreno 已提交
155
	private eventEmitter = new EventEmitter();
156
	private initialUserEnv: platform.IProcessEnvironment;
E
Erich Gamma 已提交
157 158
	private windowsState: IWindowsState;

S
Sandeep Somavarapu 已提交
159 160 161 162 163 164
	private _onFocus = new Emitter<number>();
	onWindowFocus: CommonEvent<number> = this._onFocus.event;

	private _onNewWindow = new Emitter<number>();
	onNewWindowOpen: CommonEvent<number> = this._onNewWindow.event;

J
Joao Moreno 已提交
165 166 167
	constructor(
		@IInstantiationService private instantiationService: IInstantiationService,
		@ILogService private logService: ILogService,
J
Joao Moreno 已提交
168
		@IStorageService private storageService: IStorageService,
169
		@IEnvironmentService private environmentService: IEnvironmentService,
J
Joao Moreno 已提交
170
		@ILifecycleService private lifecycleService: ILifecycleService,
B
Benjamin Pasero 已提交
171
		@IUpdateService private updateService: IUpdateService,
172 173
		@IConfigurationService private configurationService: IConfigurationService,
		@IBackupService private backupService: IBackupService
174
	) { }
J
Joao Moreno 已提交
175

J
Joao Moreno 已提交
176
	onOpen(clb: (path: IPath) => void): () => void {
J
Joao Moreno 已提交
177 178 179 180 181
		this.eventEmitter.addListener(EventTypes.OPEN, clb);

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

J
Joao Moreno 已提交
182
	onReady(clb: (win: VSCodeWindow) => void): () => void {
J
Joao Moreno 已提交
183 184 185 186 187 188 189 190 191 192 193
		this.eventEmitter.addListener(EventTypes.READY, clb);

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

	onClose(clb: (id: number) => void): () => void {
		this.eventEmitter.addListener(EventTypes.CLOSE, clb);

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

194
	public ready(initialUserEnv: platform.IProcessEnvironment): void {
E
Erich Gamma 已提交
195 196
		this.registerListeners();

197
		this.initialUserEnv = initialUserEnv;
J
Joao Moreno 已提交
198
		this.windowsState = this.storageService.getItem<IWindowsState>(WindowsManager.windowsStateStorageKey) || { openedFolders: [] };
E
Erich Gamma 已提交
199 200 201
	}

	private registerListeners(): void {
202
		app.on('activate', (event: Event, hasVisibleWindows: boolean) => {
J
Joao Moreno 已提交
203
			this.logService.log('App#activate');
E
Erich Gamma 已提交
204

G
Giorgos Retsinas 已提交
205
			// Mac only event: open new window when we get activated
E
Erich Gamma 已提交
206
			if (!hasVisibleWindows) {
G
Giorgos Retsinas 已提交
207
				this.openNewWindow();
E
Erich Gamma 已提交
208 209 210 211 212 213
			}
		});

		let macOpenFiles: string[] = [];
		let runningTimeout: number = null;
		app.on('open-file', (event: Event, path: string) => {
J
Joao Moreno 已提交
214
			this.logService.log('App#open-file: ', path);
E
Erich Gamma 已提交
215 216 217 218 219 220 221 222 223 224 225 226 227
			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(() => {
228
				this.open({ cli: this.environmentService.args, pathsToOpen: macOpenFiles, preferNewWindow: true /* dropping on the dock prefers to open in a new window */ });
E
Erich Gamma 已提交
229 230 231 232 233 234
				macOpenFiles = [];
				runningTimeout = null;
			}, 100);
		});

		ipc.on('vscode:startCrashReporter', (event: any, config: any) => {
J
Joao Moreno 已提交
235
			this.logService.log('IPC#vscode:startCrashReporter');
236

E
Erich Gamma 已提交
237 238 239
			crashReporter.start(config);
		});

B
Benjamin Pasero 已提交
240
		ipc.on('vscode:windowOpen', (event, paths: string[], forceNewWindow?: boolean) => {
J
Joao Moreno 已提交
241
			this.logService.log('IPC#vscode-windowOpen: ', paths);
E
Erich Gamma 已提交
242 243

			if (paths && paths.length) {
244
				this.open({ cli: this.environmentService.args, pathsToOpen: paths, forceNewWindow: forceNewWindow });
E
Erich Gamma 已提交
245 246 247
			}
		});

B
Benjamin Pasero 已提交
248
		ipc.on('vscode:workbenchLoaded', (event, windowId: number) => {
J
Joao Moreno 已提交
249
			this.logService.log('IPC#vscode-workbenchLoaded');
E
Erich Gamma 已提交
250

B
Benjamin Pasero 已提交
251
			const win = this.getWindowById(windowId);
E
Erich Gamma 已提交
252 253 254 255
			if (win) {
				win.setReady();

				// Event
J
Joao Moreno 已提交
256
				this.eventEmitter.emit(EventTypes.READY, win);
E
Erich Gamma 已提交
257 258 259
			}
		});

260
		ipc.on('vscode:openFilePicker', (event, forceNewWindow?: boolean, path?: string) => {
J
Joao Moreno 已提交
261
			this.logService.log('IPC#vscode-openFilePicker');
E
Erich Gamma 已提交
262

263
			this.openFilePicker(forceNewWindow, path);
E
Erich Gamma 已提交
264 265
		});

266
		ipc.on('vscode:openFolderPicker', (event, forceNewWindow?: boolean) => {
J
Joao Moreno 已提交
267
			this.logService.log('IPC#vscode-openFolderPicker');
E
Erich Gamma 已提交
268

269 270 271 272
			this.openFolderPicker(forceNewWindow);
		});

		ipc.on('vscode:openFileFolderPicker', (event, forceNewWindow?: boolean) => {
J
Joao Moreno 已提交
273
			this.logService.log('IPC#vscode-openFileFolderPicker');
274 275

			this.openFileFolderPicker(forceNewWindow);
E
Erich Gamma 已提交
276 277
		});

B
Benjamin Pasero 已提交
278
		ipc.on('vscode:closeFolder', (event, windowId: number) => {
J
Joao Moreno 已提交
279
			this.logService.log('IPC#vscode-closeFolder');
E
Erich Gamma 已提交
280

B
Benjamin Pasero 已提交
281
			const win = this.getWindowById(windowId);
E
Erich Gamma 已提交
282
			if (win) {
283
				this.open({ cli: this.environmentService.args, forceEmpty: true, windowToUse: win });
E
Erich Gamma 已提交
284 285 286
			}
		});

B
Benjamin Pasero 已提交
287
		ipc.on('vscode:openNewWindow', () => {
J
Joao Moreno 已提交
288
			this.logService.log('IPC#vscode-openNewWindow');
E
Erich Gamma 已提交
289

B
Benjamin Pasero 已提交
290
			this.openNewWindow();
E
Erich Gamma 已提交
291 292
		});

B
Benjamin Pasero 已提交
293
		ipc.on('vscode:reloadWindow', (event, windowId: number) => {
J
Joao Moreno 已提交
294
			this.logService.log('IPC#vscode:reloadWindow');
E
Erich Gamma 已提交
295

B
Benjamin Pasero 已提交
296
			const vscodeWindow = this.getWindowById(windowId);
E
Erich Gamma 已提交
297 298 299 300 301
			if (vscodeWindow) {
				this.reload(vscodeWindow);
			}
		});

B
Benjamin Pasero 已提交
302
		ipc.on('vscode:toggleFullScreen', (event, windowId: number) => {
J
Joao Moreno 已提交
303
			this.logService.log('IPC#vscode:toggleFullScreen');
E
Erich Gamma 已提交
304

B
Benjamin Pasero 已提交
305
			const vscodeWindow = this.getWindowById(windowId);
E
Erich Gamma 已提交
306 307 308 309 310
			if (vscodeWindow) {
				vscodeWindow.toggleFullScreen();
			}
		});

311
		ipc.on('vscode:setFullScreen', (event, windowId: number, fullscreen: boolean) => {
J
Joao Moreno 已提交
312
			this.logService.log('IPC#vscode:setFullScreen');
313

B
Benjamin Pasero 已提交
314
			const vscodeWindow = this.getWindowById(windowId);
315 316 317 318 319 320
			if (vscodeWindow) {
				vscodeWindow.win.setFullScreen(fullscreen);
			}
		});

		ipc.on('vscode:toggleDevTools', (event, windowId: number) => {
J
Joao Moreno 已提交
321
			this.logService.log('IPC#vscode:toggleDevTools');
322

B
Benjamin Pasero 已提交
323
			const vscodeWindow = this.getWindowById(windowId);
324 325 326 327 328 329
			if (vscodeWindow) {
				vscodeWindow.win.webContents.toggleDevTools();
			}
		});

		ipc.on('vscode:openDevTools', (event, windowId: number) => {
J
Joao Moreno 已提交
330
			this.logService.log('IPC#vscode:openDevTools');
331

B
Benjamin Pasero 已提交
332
			const vscodeWindow = this.getWindowById(windowId);
333 334 335 336 337 338 339
			if (vscodeWindow) {
				vscodeWindow.win.webContents.openDevTools();
				vscodeWindow.win.show();
			}
		});

		ipc.on('vscode:setRepresentedFilename', (event, windowId: number, fileName: string) => {
J
Joao Moreno 已提交
340
			this.logService.log('IPC#vscode:setRepresentedFilename');
341

B
Benjamin Pasero 已提交
342
			const vscodeWindow = this.getWindowById(windowId);
343 344 345 346 347 348
			if (vscodeWindow) {
				vscodeWindow.win.setRepresentedFilename(fileName);
			}
		});

		ipc.on('vscode:setMenuBarVisibility', (event, windowId: number, visibility: boolean) => {
J
Joao Moreno 已提交
349
			this.logService.log('IPC#vscode:setMenuBarVisibility');
350

B
Benjamin Pasero 已提交
351
			const vscodeWindow = this.getWindowById(windowId);
352 353 354 355 356 357
			if (vscodeWindow) {
				vscodeWindow.win.setMenuBarVisibility(visibility);
			}
		});

		ipc.on('vscode:flashFrame', (event, windowId: number) => {
J
Joao Moreno 已提交
358
			this.logService.log('IPC#vscode:flashFrame');
359

B
Benjamin Pasero 已提交
360
			const vscodeWindow = this.getWindowById(windowId);
361 362 363 364 365
			if (vscodeWindow) {
				vscodeWindow.win.flashFrame(!vscodeWindow.win.isFocused());
			}
		});

366 367 368
		ipc.on('vscode:openRecent', (event, windowId: number) => {
			this.logService.log('IPC#vscode:openRecent');

B
Benjamin Pasero 已提交
369
			const vscodeWindow = this.getWindowById(windowId);
370
			if (vscodeWindow) {
371
				const recents = this.getRecentPathsList(vscodeWindow.config.workspacePath, vscodeWindow.config.filesToOpen);
372 373 374 375 376

				vscodeWindow.send('vscode:openRecent', recents.files, recents.folders);
			}
		});

377
		ipc.on('vscode:focusWindow', (event, windowId: number) => {
J
Joao Moreno 已提交
378
			this.logService.log('IPC#vscode:focusWindow');
379

B
Benjamin Pasero 已提交
380
			const vscodeWindow = this.getWindowById(windowId);
381 382 383 384 385
			if (vscodeWindow) {
				vscodeWindow.win.focus();
			}
		});

386 387 388 389 390 391 392 393 394
		ipc.on('vscode:showWindow', (event, windowId: number) => {
			this.logService.log('IPC#vscode:showWindow');

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

395
		ipc.on('vscode:setDocumentEdited', (event, windowId: number, edited: boolean) => {
J
Joao Moreno 已提交
396
			this.logService.log('IPC#vscode:setDocumentEdited');
397

B
Benjamin Pasero 已提交
398
			const vscodeWindow = this.getWindowById(windowId);
399 400 401 402 403
			if (vscodeWindow && vscodeWindow.win.isDocumentEdited() !== edited) {
				vscodeWindow.win.setDocumentEdited(edited);
			}
		});

B
Benjamin Pasero 已提交
404
		ipc.on('vscode:toggleMenuBar', (event, windowId: number) => {
J
Joao Moreno 已提交
405
			this.logService.log('IPC#vscode:toggleMenuBar');
406 407

			// Update in settings
B
Benjamin Pasero 已提交
408 409
			const menuBarHidden = this.storageService.getItem(VSCodeWindow.menuBarHiddenKey, false);
			const newMenuBarHidden = !menuBarHidden;
J
Joao Moreno 已提交
410
			this.storageService.setItem(VSCodeWindow.menuBarHiddenKey, newMenuBarHidden);
411 412 413

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

			// Inform user if menu bar is now hidden
			if (newMenuBarHidden) {
B
Benjamin Pasero 已提交
417
				const vscodeWindow = this.getWindowById(windowId);
418 419 420 421
				if (vscodeWindow) {
					vscodeWindow.send('vscode:showInfoMessage', nls.localize('hiddenMenuBar', "You can still access the menu bar by pressing the **Alt** key."));
				}
			}
422 423
		});

B
Benjamin Pasero 已提交
424
		ipc.on('vscode:broadcast', (event, windowId: number, target: string, broadcast: { channel: string; payload: any; }) => {
425
			if (broadcast.channel && !types.isUndefinedOrNull(broadcast.payload)) {
J
Joao Moreno 已提交
426
				this.logService.log('IPC#vscode:broadcast', target, broadcast.channel, broadcast.payload);
B
Benjamin Pasero 已提交
427

428 429 430 431
				// Handle specific events on main side
				this.onBroadcast(broadcast.channel, broadcast.payload);

				// Send to windows
432
				if (target) {
B
Benjamin Pasero 已提交
433
					const otherWindowsWithTarget = WindowsManager.WINDOWS.filter(w => w.id !== windowId && typeof w.openedWorkspacePath === 'string');
434 435 436 437 438
					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) {
439 440 441 442 443
						targetWindow.send('vscode:broadcast', broadcast);
					}
				} else {
					this.sendToAll('vscode:broadcast', broadcast, [windowId]);
				}
E
Erich Gamma 已提交
444
			}
445 446
		});

B
Benjamin Pasero 已提交
447
		ipc.on('vscode:log', (event, logEntry: ILogEntry) => {
B
Benjamin Pasero 已提交
448
			const args = [];
449
			try {
B
Benjamin Pasero 已提交
450
				const parsed = JSON.parse(logEntry.arguments);
451 452 453 454 455 456 457
				args.push(...Object.getOwnPropertyNames(parsed).map(o => parsed[o]));
			} catch (error) {
				args.push(logEntry.arguments);
			}

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

459
		ipc.on('vscode:closeExtensionHostWindow', (event, extensionDevelopmentPath: string) => {
J
Joao Moreno 已提交
460
			this.logService.log('IPC#vscode:closeExtensionHostWindow', extensionDevelopmentPath);
B
Benjamin Pasero 已提交
461

462 463 464 465 466 467
			const windowOnExtension = this.findWindow(null, null, extensionDevelopmentPath);
			if (windowOnExtension) {
				windowOnExtension.win.close();
			}
		});

468
		ipc.on('vscode:switchWindow', (event, windowId: number) => {
469 470
			const windows = this.getWindows();
			const window = this.getWindowById(windowId);
471
			window.send('vscode:switchWindow', windows.map(w => {
J
Johannes Rieken 已提交
472
				return { path: w.openedWorkspacePath, title: w.win.getTitle(), id: w.id };
473 474 475
			}));
		});

B
Benjamin Pasero 已提交
476
		this.updateService.on('update-downloaded', (update: IUpdate) => {
E
Erich Gamma 已提交
477 478 479 480 481 482 483 484 485
			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 已提交
486
		ipc.on('vscode:update-apply', () => {
J
Joao Moreno 已提交
487
			this.logService.log('IPC#vscode:update-apply');
E
Erich Gamma 已提交
488

B
Benjamin Pasero 已提交
489 490
			if (this.updateService.availableUpdate) {
				this.updateService.availableUpdate.quitAndUpdate();
E
Erich Gamma 已提交
491 492 493
			}
		});

B
Benjamin Pasero 已提交
494
		this.updateService.on('update-not-available', (explicit: boolean) => {
E
Erich Gamma 已提交
495 496 497 498 499 500 501
			this.sendToFocused('vscode:telemetry', { eventName: 'update:notAvailable', data: { explicit } });

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

J
Joao Moreno 已提交
502
		this.updateService.on('update-available', (url: string, version: string) => {
J
Joao Moreno 已提交
503
			if (url) {
J
Joao Moreno 已提交
504
				this.sendToFocused('vscode:update-available', url, version);
J
Joao Moreno 已提交
505 506 507
			}
		});

J
Joao Moreno 已提交
508
		this.lifecycleService.onBeforeQuit(() => {
E
Erich Gamma 已提交
509 510 511 512 513 514 515 516

			// 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
J
Joao Moreno 已提交
517
			this.windowsState.openedFolders = WindowsManager.WINDOWS.filter(w => w.readyState === ReadyState.READY && !!w.openedWorkspacePath && !w.isPluginDevelopmentHost).map(w => {
E
Erich Gamma 已提交
518 519 520
				return <IWindowState>{
					workspacePath: w.openedWorkspacePath,
					uiState: w.serializeWindowState()
B
Benjamin Pasero 已提交
521
				};
E
Erich Gamma 已提交
522 523 524 525
			});
		});

		app.on('will-quit', () => {
J
Joao Moreno 已提交
526
			this.storageService.setItem(WindowsManager.windowsStateStorageKey, this.windowsState);
E
Erich Gamma 已提交
527
		});
528 529

		let loggedStartupTimes = false;
J
Joao Moreno 已提交
530
		this.onReady(window => {
531 532 533 534 535 536
			if (loggedStartupTimes) {
				return; // only for the first window
			}

			loggedStartupTimes = true;

B
Benjamin Pasero 已提交
537
			this.logStartupTimes(window);
538
		});
E
Erich Gamma 已提交
539 540
	}

B
Benjamin Pasero 已提交
541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558
	private logStartupTimes(window: VSCodeWindow): void {
		let totalmem: number;
		let cpus: { count: number; speed: number; model: string; };

		try {
			totalmem = os.totalmem();

			const rawCpus = os.cpus();
			if (rawCpus && rawCpus.length > 0) {
				cpus = { count: rawCpus.length, speed: rawCpus[0].speed, model: rawCpus[0].model };
			}
		} catch (error) {
			this.logService.log(error); // be on the safe side with these hardware method calls
		}

		window.send('vscode:telemetry', { eventName: 'startupTime', data: { ellapsed: Date.now() - global.vscodeStart }, totalmem, cpus });
	}

559 560 561
	private onBroadcast(event: string, payload: any): void {

		// Theme changes
562 563
		if (event === 'vscode:changeColorTheme' && typeof payload === 'string') {
			this.storageService.setItem(VSCodeWindow.colorThemeStorageKey, payload);
564 565 566
		}
	}

B
Benjamin Pasero 已提交
567
	public reload(win: VSCodeWindow, cli?: ParsedArgs): void {
E
Erich Gamma 已提交
568 569

		// Only reload when the window has not vetoed this
570
		this.lifecycleService.unload(win).done(veto => {
E
Erich Gamma 已提交
571 572 573 574 575 576
			if (!veto) {
				win.reload(cli);
			}
		});
	}

J
Joao Moreno 已提交
577 578
	public open(openConfig: IOpenConfiguration): VSCodeWindow[] {
		let iPathsToOpen: IPath[];
B
Benjamin Pasero 已提交
579
		const usedWindows: VSCodeWindow[] = [];
E
Erich Gamma 已提交
580 581 582

		// Find paths from provided paths if any
		if (openConfig.pathsToOpen && openConfig.pathsToOpen.length > 0) {
583
			iPathsToOpen = openConfig.pathsToOpen.map(pathToOpen => {
B
Benjamin Pasero 已提交
584
				const iPath = this.toIPath(pathToOpen, false, openConfig.cli && openConfig.cli.goto);
E
Erich Gamma 已提交
585 586 587

				// Warn if the requested path to open does not exist
				if (!iPath) {
B
Benjamin Pasero 已提交
588
					const options: Electron.ShowMessageBoxOptions = {
B
Benjamin Pasero 已提交
589
						title: product.nameLong,
E
Erich Gamma 已提交
590 591 592 593 594 595 596
						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
					};

B
Benjamin Pasero 已提交
597
					const activeWindow = BrowserWindow.getFocusedWindow();
E
Erich Gamma 已提交
598
					if (activeWindow) {
599
						dialog.showMessageBox(activeWindow, options);
E
Erich Gamma 已提交
600
					} else {
601
						dialog.showMessageBox(options);
E
Erich Gamma 已提交
602 603 604 605 606 607 608 609 610 611
					}
				}

				return iPath;
			});

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

			if (iPathsToOpen.length === 0) {
612
				return null; // indicate to outside that open failed
E
Erich Gamma 已提交
613 614 615 616 617 618 619 620 621 622
			}
		}

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

		// Otherwise infer from command line arguments
		else {
B
Benjamin Pasero 已提交
623
			const ignoreFileNotFound = openConfig.cli._.length > 0; // we assume the user wants to create this file from command line
E
Erich Gamma 已提交
624 625 626
			iPathsToOpen = this.cliToPaths(openConfig.cli, ignoreFileNotFound);
		}

627
		// Add any existing backup workspaces
628
		if (openConfig.restoreBackups) {
629
			this.backupService.getWorkspaceBackupPathsSync().forEach(ws => {
630 631 632 633
				iPathsToOpen.push(this.toIPath(ws));
			});
			// Get rid of duplicates
			iPathsToOpen = arrays.distinct(iPathsToOpen, path => {
634
				return platform.isLinux ? path.workspacePath : path.workspacePath.toLowerCase();
635
			});
636 637
		}

J
Joao Moreno 已提交
638 639
		let filesToOpen: IPath[] = [];
		let filesToDiff: IPath[] = [];
640 641 642
		let foldersToOpen = iPathsToOpen.filter(iPath => iPath.workspacePath && !iPath.filePath);
		let emptyToOpen = iPathsToOpen.filter(iPath => !iPath.workspacePath && !iPath.filePath);
		let filesToCreate = iPathsToOpen.filter(iPath => !!iPath.filePath && iPath.createFilePath);
643 644

		// Diff mode needs special care
645
		const candidates = iPathsToOpen.filter(iPath => !!iPath.filePath && !iPath.createFilePath);
646 647 648 649 650 651 652
		if (openConfig.diffMode) {
			if (candidates.length === 2) {
				filesToDiff = candidates;
			} else {
				emptyToOpen = [Object.create(null)]; // improper use of diffMode, open empty
			}

653
			foldersToOpen = []; // diff is always in empty workspace
B
Benjamin Pasero 已提交
654
			filesToCreate = []; // diff ignores other files that do not exist
655 656 657 658
		} else {
			filesToOpen = candidates;
		}

J
Joao Moreno 已提交
659
		let configuration: IWindowConfiguration;
E
Erich Gamma 已提交
660

661
		// Handle files to open/diff or to create when we dont open a folder
662
		if (!foldersToOpen.length && (filesToOpen.length > 0 || filesToCreate.length > 0 || filesToDiff.length > 0)) {
E
Erich Gamma 已提交
663

B
Benjamin Pasero 已提交
664
			// const the user settings override how files are open in a new window or same window unless we are forced
665 666 667 668 669 670
			let openFilesInNewWindow: boolean;
			if (openConfig.forceNewWindow) {
				openFilesInNewWindow = true;
			} else {
				openFilesInNewWindow = openConfig.preferNewWindow;
				if (openFilesInNewWindow && !openConfig.cli.extensionDevelopmentPath) { // can be overriden via settings (not for PDE though!)
671 672 673 674
					const windowConfig = this.configurationService.getConfiguration<IWindowSettings>('window');
					if (windowConfig && !windowConfig.openFilesInNewWindow) {
						openFilesInNewWindow = false; // do not open in new window if user configured this explicitly
					}
675
				}
E
Erich Gamma 已提交
676 677 678
			}

			// Open Files in last instance if any and flag tells us so
B
Benjamin Pasero 已提交
679
			const lastActiveWindow = this.getLastActiveWindow();
E
Erich Gamma 已提交
680
			if (!openFilesInNewWindow && lastActiveWindow) {
B
Benjamin Pasero 已提交
681
				lastActiveWindow.focus();
682
				lastActiveWindow.ready().then(readyWindow => {
683
					readyWindow.send('vscode:openFiles', { filesToOpen, filesToCreate, filesToDiff });
E
Erich Gamma 已提交
684
				});
685 686

				usedWindows.push(lastActiveWindow);
E
Erich Gamma 已提交
687 688 689 690
			}

			// Otherwise open instance with files
			else {
691
				configuration = this.toConfiguration(this.getWindowUserEnv(openConfig), openConfig.cli, null, filesToOpen, filesToCreate, filesToDiff);
B
Benjamin Pasero 已提交
692
				const browserWindow = this.openInBrowserWindow(configuration, true /* new window */);
693
				usedWindows.push(browserWindow);
E
Erich Gamma 已提交
694 695 696 697 698 699

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

		// Handle folders to open
700
		let openInNewWindow = openConfig.preferNewWindow || openConfig.forceNewWindow;
E
Erich Gamma 已提交
701 702 703
		if (foldersToOpen.length > 0) {

			// Check for existing instances
704
			const windowsOnWorkspacePath = arrays.coalesce(foldersToOpen.map(iPath => this.findWindow(iPath.workspacePath)));
E
Erich Gamma 已提交
705
			if (windowsOnWorkspacePath.length > 0) {
B
Benjamin Pasero 已提交
706
				const browserWindow = windowsOnWorkspacePath[0];
707
				browserWindow.focus(); // just focus one of them
708
				browserWindow.ready().then(readyWindow => {
709
					readyWindow.send('vscode:openFiles', { filesToOpen, filesToCreate, filesToDiff });
E
Erich Gamma 已提交
710 711
				});

712 713
				usedWindows.push(browserWindow);

E
Erich Gamma 已提交
714 715 716
				// Reset these because we handled them
				filesToOpen = [];
				filesToCreate = [];
717
				filesToDiff = [];
E
Erich Gamma 已提交
718

719
				openInNewWindow = true; // any other folders to open must open in new window then
E
Erich Gamma 已提交
720 721 722
			}

			// Open remaining ones
723 724
			foldersToOpen.forEach(folderToOpen => {
				if (windowsOnWorkspacePath.some(win => this.isPathEqual(win.openedWorkspacePath, folderToOpen.workspacePath))) {
E
Erich Gamma 已提交
725 726 727
					return; // ignore folders that are already open
				}

728
				configuration = this.toConfiguration(this.getWindowUserEnv(openConfig), openConfig.cli, folderToOpen.workspacePath, filesToOpen, filesToCreate, filesToDiff);
B
Benjamin Pasero 已提交
729
				const browserWindow = this.openInBrowserWindow(configuration, openInNewWindow, openInNewWindow ? void 0 : openConfig.windowToUse);
730
				usedWindows.push(browserWindow);
E
Erich Gamma 已提交
731 732 733 734

				// Reset these because we handled them
				filesToOpen = [];
				filesToCreate = [];
735
				filesToDiff = [];
E
Erich Gamma 已提交
736

737
				openInNewWindow = true; // any other folders to open must open in new window then
E
Erich Gamma 已提交
738 739 740 741 742 743
			});
		}

		// Handle empty
		if (emptyToOpen.length > 0) {
			emptyToOpen.forEach(() => {
B
Benjamin Pasero 已提交
744 745
				const configuration = this.toConfiguration(this.getWindowUserEnv(openConfig), openConfig.cli);
				const browserWindow = this.openInBrowserWindow(configuration, openInNewWindow, openInNewWindow ? void 0 : openConfig.windowToUse);
746
				usedWindows.push(browserWindow);
E
Erich Gamma 已提交
747

748
				openInNewWindow = true; // any other folders to open must open in new window then
E
Erich Gamma 已提交
749 750 751
			});
		}

752
		// Remember in recent document list (unless this opens for extension development)
753
		// Also do not add paths when files are opened for diffing, only if opened individually
754
		if (!usedWindows.some(w => w.isPluginDevelopmentHost) && !openConfig.cli.diff) {
755 756 757 758 759 760 761
			iPathsToOpen.forEach(iPath => {
				if (iPath.filePath || iPath.workspacePath) {
					app.addRecentDocument(iPath.filePath || iPath.workspacePath);
					this.addToRecentPathsList(iPath.filePath || iPath.workspacePath, !!iPath.filePath);
				}
			});
		}
E
Erich Gamma 已提交
762 763

		// Emit events
764
		iPathsToOpen.forEach(iPath => this.eventEmitter.emit(EventTypes.OPEN, iPath));
E
Erich Gamma 已提交
765

766
		// Add to backups
767
		this.backupService.pushWorkspaceBackupPathsSync(iPathsToOpen.map((path) => {
768 769 770
			return path.workspacePath;
		}));

771
		return arrays.distinct(usedWindows);
E
Erich Gamma 已提交
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 819 820 821 822 823 824 825 826 827 828 829 830 831 832 833 834 835 836 837 838 839 840 841 842 843 844 845 846
	private addToRecentPathsList(path?: string, isFile?: boolean): void {
		if (!path) {
			return;
		}

		const mru = this.getRecentPathsList();
		if (isFile) {
			mru.files.unshift(path);
			mru.files = arrays.distinct(mru.files, (f) => platform.isLinux ? f : f.toLowerCase());
		} else {
			mru.folders.unshift(path);
			mru.folders = arrays.distinct(mru.folders, (f) => platform.isLinux ? f : f.toLowerCase());
		}

		// Make sure its bounded
		mru.folders = mru.folders.slice(0, WindowsManager.MAX_TOTAL_RECENT_ENTRIES);
		mru.files = mru.files.slice(0, WindowsManager.MAX_TOTAL_RECENT_ENTRIES);

		this.storageService.setItem(WindowsManager.recentPathsListStorageKey, mru);
	}

	public removeFromRecentPathsList(path: string): void {
		const mru = this.getRecentPathsList();

		let index = mru.files.indexOf(path);
		if (index >= 0) {
			mru.files.splice(index, 1);
		}

		index = mru.folders.indexOf(path);
		if (index >= 0) {
			mru.folders.splice(index, 1);
		}

		this.storageService.setItem(WindowsManager.recentPathsListStorageKey, mru);
	}

	public clearRecentPathsList(): void {
		this.storageService.setItem(WindowsManager.recentPathsListStorageKey, { folders: [], files: [] });
		app.clearRecentDocuments();
	}

	public getRecentPathsList(workspacePath?: string, filesToOpen?: IPath[]): IRecentPathsList {
		let files: string[];
		let folders: string[];

		// Get from storage
		const storedRecents = this.storageService.getItem<IRecentPathsList>(WindowsManager.recentPathsListStorageKey);
		if (storedRecents) {
			files = storedRecents.files || [];
			folders = storedRecents.folders || [];
		} else {
			files = [];
			folders = [];
		}

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

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

		// Clear those dupes
		files = arrays.distinct(files);
		folders = arrays.distinct(folders);

		return { files, folders };
	}

847
	private getWindowUserEnv(openConfig: IOpenConfiguration): platform.IProcessEnvironment {
848 849 850
		return assign({}, this.initialUserEnv, openConfig.userEnv || {});
	}

E
Erich Gamma 已提交
851 852 853 854 855
	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.
856
		let res = WindowsManager.WINDOWS.filter(w => w.config && this.isPathEqual(w.config.extensionDevelopmentPath, openConfig.cli.extensionDevelopmentPath));
E
Erich Gamma 已提交
857 858
		if (res && res.length === 1) {
			this.reload(res[0], openConfig.cli);
B
Benjamin Pasero 已提交
859
			res[0].focus(); // make sure it gets focus and is restored
E
Erich Gamma 已提交
860 861 862 863

			return;
		}

864
		// Fill in previously opened workspace unless an explicit path is provided and we are not unit testing
B
Benjamin Pasero 已提交
865
		if (openConfig.cli._.length === 0 && !openConfig.cli.extensionTestsPath) {
B
Benjamin Pasero 已提交
866
			const workspaceToOpen = this.windowsState.lastPluginDevelopmentHostWindow && this.windowsState.lastPluginDevelopmentHostWindow.workspacePath;
E
Erich Gamma 已提交
867
			if (workspaceToOpen) {
B
Benjamin Pasero 已提交
868
				openConfig.cli._ = [workspaceToOpen];
E
Erich Gamma 已提交
869 870 871 872
			}
		}

		// Make sure we are not asked to open a path that is already opened
B
Benjamin Pasero 已提交
873 874
		if (openConfig.cli._.length > 0) {
			res = WindowsManager.WINDOWS.filter(w => w.openedWorkspacePath && openConfig.cli._.indexOf(w.openedWorkspacePath) >= 0);
E
Erich Gamma 已提交
875
			if (res.length) {
B
Benjamin Pasero 已提交
876
				openConfig.cli._ = [];
E
Erich Gamma 已提交
877 878 879 880
			}
		}

		// Open it
B
Benjamin Pasero 已提交
881
		this.open({ cli: openConfig.cli, forceNewWindow: true, forceEmpty: openConfig.cli._.length === 0 });
E
Erich Gamma 已提交
882 883
	}

B
Benjamin Pasero 已提交
884
	private toConfiguration(userEnv: platform.IProcessEnvironment, cli: ParsedArgs, workspacePath?: string, filesToOpen?: IPath[], filesToCreate?: IPath[], filesToDiff?: IPath[]): IWindowConfiguration {
B
Benjamin Pasero 已提交
885
		const configuration: IWindowConfiguration = mixin({}, cli); // inherit all properties from CLI
886
		configuration.appRoot = this.environmentService.appRoot;
B
Benjamin Pasero 已提交
887 888
		configuration.execPath = process.execPath;
		configuration.userEnv = userEnv;
E
Erich Gamma 已提交
889 890 891
		configuration.workspacePath = workspacePath;
		configuration.filesToOpen = filesToOpen;
		configuration.filesToCreate = filesToCreate;
892
		configuration.filesToDiff = filesToDiff;
E
Erich Gamma 已提交
893 894 895 896

		return configuration;
	}

J
Joao Moreno 已提交
897
	private toIPath(anyPath: string, ignoreFileNotFound?: boolean, gotoLineMode?: boolean): IPath {
E
Erich Gamma 已提交
898 899 900 901
		if (!anyPath) {
			return null;
		}

902
		let parsedPath: IPathWithLineAndColumn;
E
Erich Gamma 已提交
903
		if (gotoLineMode) {
J
Joao Moreno 已提交
904
			parsedPath = parseLineAndColumnAware(anyPath);
E
Erich Gamma 已提交
905 906 907
			anyPath = parsedPath.path;
		}

B
Benjamin Pasero 已提交
908
		const candidate = path.normalize(anyPath);
E
Erich Gamma 已提交
909
		try {
B
Benjamin Pasero 已提交
910
			const candidateStat = fs.statSync(candidate);
E
Erich Gamma 已提交
911 912 913 914 915
			if (candidateStat) {
				return candidateStat.isFile() ?
					{
						filePath: candidate,
						lineNumber: gotoLineMode ? parsedPath.line : void 0,
916
						columnNumber: gotoLineMode ? parsedPath.column : void 0
E
Erich Gamma 已提交
917 918 919 920 921 922 923 924 925 926 927 928
					} :
					{ workspacePath: candidate };
			}
		} catch (error) {
			if (ignoreFileNotFound) {
				return { filePath: candidate, createFilePath: true }; // assume this is a file that does not yet exist
			}
		}

		return null;
	}

B
Benjamin Pasero 已提交
929
	private cliToPaths(cli: ParsedArgs, ignoreFileNotFound?: boolean): IPath[] {
E
Erich Gamma 已提交
930 931 932

		// Check for pass in candidate or last opened path
		let candidates: string[] = [];
B
Benjamin Pasero 已提交
933 934
		if (cli._.length > 0) {
			candidates = cli._;
E
Erich Gamma 已提交
935 936 937 938
		}

		// No path argument, check settings for what to do now
		else {
939 940 941 942
			let reopenFolders: string;
			if (this.lifecycleService.wasUpdated) {
				reopenFolders = ReopenFoldersSetting.ALL; // always reopen all folders when an update was applied
			} else {
943 944
				const windowConfig = this.configurationService.getConfiguration<IWindowSettings>('window');
				reopenFolders = (windowConfig && windowConfig.reopenFolders) || ReopenFoldersSetting.ONE;
945 946
			}

B
Benjamin Pasero 已提交
947
			const lastActiveFolder = this.windowsState.lastActiveWindow && this.windowsState.lastActiveWindow.workspacePath;
E
Erich Gamma 已提交
948 949

			// Restore all
950
			if (reopenFolders === ReopenFoldersSetting.ALL) {
B
Benjamin Pasero 已提交
951
				const lastOpenedFolders = this.windowsState.openedFolders.map(o => o.workspacePath);
E
Erich Gamma 已提交
952 953 954 955 956 957 958 959 960 961 962

				// 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
963
			else if (lastActiveFolder && (reopenFolders === ReopenFoldersSetting.ONE || reopenFolders !== ReopenFoldersSetting.NONE)) {
E
Erich Gamma 已提交
964 965 966 967
				candidates.push(lastActiveFolder);
			}
		}

968
		const iPaths = candidates.map(candidate => this.toIPath(candidate, ignoreFileNotFound, cli.goto)).filter(path => !!path);
E
Erich Gamma 已提交
969 970 971 972 973 974 975 976
		if (iPaths.length > 0) {
			return iPaths;
		}

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

J
Joao Moreno 已提交
977 978
	private openInBrowserWindow(configuration: IWindowConfiguration, forceNewWindow?: boolean, windowToUse?: VSCodeWindow): VSCodeWindow {
		let vscodeWindow: VSCodeWindow;
E
Erich Gamma 已提交
979 980 981 982 983

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

			if (vscodeWindow) {
B
Benjamin Pasero 已提交
984
				vscodeWindow.focus();
E
Erich Gamma 已提交
985 986 987 988 989
			}
		}

		// New window
		if (!vscodeWindow) {
990 991
			const windowConfig = this.configurationService.getConfiguration<IWindowSettings>('window');

J
Joao Moreno 已提交
992
			vscodeWindow = this.instantiationService.createInstance(VSCodeWindow, {
993
				state: this.getNewWindowState(configuration),
994
				extensionDevelopmentPath: configuration.extensionDevelopmentPath,
995
				allowFullscreen: this.lifecycleService.wasUpdated || (windowConfig && windowConfig.restoreFullscreen)
996 997
			});

E
Erich Gamma 已提交
998 999 1000
			WindowsManager.WINDOWS.push(vscodeWindow);

			// Window Events
1001 1002
			vscodeWindow.win.webContents.removeAllListeners('devtools-reload-page'); // remove built in listener so we can handle this on our own
			vscodeWindow.win.webContents.on('devtools-reload-page', () => this.reload(vscodeWindow));
1003 1004
			vscodeWindow.win.webContents.on('crashed', () => this.onWindowError(vscodeWindow, WindowError.CRASHED));
			vscodeWindow.win.on('unresponsive', () => this.onWindowError(vscodeWindow, WindowError.UNRESPONSIVE));
E
Erich Gamma 已提交
1005 1006
			vscodeWindow.win.on('close', () => this.onBeforeWindowClose(vscodeWindow));
			vscodeWindow.win.on('closed', () => this.onWindowClosed(vscodeWindow));
S
Sandeep Somavarapu 已提交
1007
			vscodeWindow.win.on('focus', () => this._onFocus.fire(vscodeWindow.id));
E
Erich Gamma 已提交
1008

S
Sandeep Somavarapu 已提交
1009
			this._onNewWindow.fire(vscodeWindow.id);
E
Erich Gamma 已提交
1010
			// Lifecycle
J
Joao Moreno 已提交
1011
			this.lifecycleService.registerWindow(vscodeWindow);
E
Erich Gamma 已提交
1012 1013 1014 1015 1016 1017 1018
		}

		// 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.
B
Benjamin Pasero 已提交
1019
			const currentWindowConfig = vscodeWindow.config;
A
Alex Dima 已提交
1020 1021
			if (!configuration.extensionDevelopmentPath && currentWindowConfig && !!currentWindowConfig.extensionDevelopmentPath) {
				configuration.extensionDevelopmentPath = currentWindowConfig.extensionDevelopmentPath;
B
Benjamin Pasero 已提交
1022
				configuration.verbose = currentWindowConfig.verbose;
1023
				configuration.debugBrkPluginHost = currentWindowConfig.debugBrkPluginHost;
1024
				configuration.debugPluginHost = currentWindowConfig.debugPluginHost;
B
Benjamin Pasero 已提交
1025
				configuration.extensionHomePath = currentWindowConfig.extensionHomePath;
E
Erich Gamma 已提交
1026 1027 1028 1029
			}
		}

		// Only load when the window has not vetoed this
1030
		this.lifecycleService.unload(vscodeWindow).done(veto => {
E
Erich Gamma 已提交
1031 1032 1033 1034 1035 1036
			if (!veto) {

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

		return vscodeWindow;
E
Erich Gamma 已提交
1039 1040
	}

J
Joao Moreno 已提交
1041
	private getNewWindowState(configuration: IWindowConfiguration): ISingleWindowState {
E
Erich Gamma 已提交
1042 1043

		// plugin development host Window - load from stored settings if any
A
Alex Dima 已提交
1044
		if (!!configuration.extensionDevelopmentPath && this.windowsState.lastPluginDevelopmentHostWindow) {
E
Erich Gamma 已提交
1045 1046 1047 1048 1049
			return this.windowsState.lastPluginDevelopmentHostWindow.uiState;
		}

		// Known Folder - load from stored settings if any
		if (configuration.workspacePath) {
B
Benjamin Pasero 已提交
1050
			const stateForWorkspace = this.windowsState.openedFolders.filter(o => this.isPathEqual(o.workspacePath, configuration.workspacePath)).map(o => o.uiState);
E
Erich Gamma 已提交
1051 1052 1053 1054 1055 1056
			if (stateForWorkspace.length) {
				return stateForWorkspace[0];
			}
		}

		// First Window
B
Benjamin Pasero 已提交
1057
		const lastActive = this.getLastActiveWindow();
E
Erich Gamma 已提交
1058 1059 1060 1061 1062 1063 1064 1065 1066
		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
1067
		let displayToUse: Electron.Display;
B
Benjamin Pasero 已提交
1068
		const displays = screen.getAllDisplays();
E
Erich Gamma 已提交
1069 1070 1071 1072 1073 1074 1075 1076 1077 1078 1079

		// 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) {
B
Benjamin Pasero 已提交
1080
				const cursorPoint = screen.getCursorScreenPoint();
E
Erich Gamma 已提交
1081 1082 1083 1084 1085 1086 1087 1088 1089 1090 1091 1092 1093 1094
				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];
			}
		}

B
Benjamin Pasero 已提交
1095
		const defaultState = defaultWindowState();
E
Erich Gamma 已提交
1096 1097 1098 1099 1100 1101
		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);
	}

J
Joao Moreno 已提交
1102
	private ensureNoOverlap(state: ISingleWindowState): ISingleWindowState {
E
Erich Gamma 已提交
1103 1104 1105 1106
		if (WindowsManager.WINDOWS.length === 0) {
			return state;
		}

1107 1108
		const existingWindowBounds = WindowsManager.WINDOWS.map(win => win.getBounds());
		while (existingWindowBounds.some(b => b.x === state.x || b.y === state.y)) {
E
Erich Gamma 已提交
1109 1110 1111 1112 1113 1114 1115
			state.x += 30;
			state.y += 30;
		}

		return state;
	}

1116
	public openFileFolderPicker(forceNewWindow?: boolean): void {
1117
		this.doPickAndOpen({ pickFolders: true, pickFiles: true, forceNewWindow });
1118 1119
	}

1120 1121
	public openFilePicker(forceNewWindow?: boolean, path?: string): void {
		this.doPickAndOpen({ pickFiles: true, forceNewWindow, path });
1122 1123 1124
	}

	public openFolderPicker(forceNewWindow?: boolean): void {
1125
		this.doPickAndOpen({ pickFolders: true, forceNewWindow });
E
Erich Gamma 已提交
1126 1127
	}

1128 1129 1130 1131 1132 1133 1134 1135 1136 1137 1138 1139 1140 1141 1142 1143
	public openAccessibilityOptions(): void {
		let win = new BrowserWindow({
			alwaysOnTop: true,
			skipTaskbar: true,
			resizable: false,
			width: 450,
			height: 300,
			show: true,
			title: nls.localize('accessibilityOptionsWindowTitle', "Accessibility Options")
		});

		win.setMenuBarVisibility(false);

		win.loadURL('chrome://accessibility');
	}

1144
	private doPickAndOpen(options: INativeOpenDialogOptions): void {
1145
		this.getFileOrFolderPaths(options, (paths: string[]) => {
E
Erich Gamma 已提交
1146
			if (paths && paths.length) {
1147
				this.open({ cli: this.environmentService.args, pathsToOpen: paths, forceNewWindow: options.forceNewWindow });
E
Erich Gamma 已提交
1148 1149 1150 1151
			}
		});
	}

1152
	private getFileOrFolderPaths(options: INativeOpenDialogOptions, clb: (paths: string[]) => void): void {
B
Benjamin Pasero 已提交
1153 1154
		const workingDir = options.path || this.storageService.getItem<string>(WindowsManager.workingDirPickerStorageKey);
		const focussedWindow = this.getFocusedWindow();
E
Erich Gamma 已提交
1155

B
Benjamin Pasero 已提交
1156
		let pickerProperties: ('openFile' | 'openDirectory' | 'multiSelections' | 'createDirectory')[];
1157
		if (options.pickFiles && options.pickFolders) {
E
Erich Gamma 已提交
1158 1159
			pickerProperties = ['multiSelections', 'openDirectory', 'openFile', 'createDirectory'];
		} else {
1160
			pickerProperties = ['multiSelections', options.pickFolders ? 'openDirectory' : 'openFile', 'createDirectory'];
E
Erich Gamma 已提交
1161 1162
		}

1163
		dialog.showOpenDialog(focussedWindow && focussedWindow.win, {
E
Erich Gamma 已提交
1164 1165
			defaultPath: workingDir,
			properties: pickerProperties
1166
		}, paths => {
E
Erich Gamma 已提交
1167 1168 1169
			if (paths && paths.length > 0) {

				// Remember path in storage for next time
J
Joao Moreno 已提交
1170
				this.storageService.setItem(WindowsManager.workingDirPickerStorageKey, path.dirname(paths[0]));
E
Erich Gamma 已提交
1171 1172 1173 1174 1175 1176 1177 1178 1179

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

B
Benjamin Pasero 已提交
1180
	public focusLastActive(cli: ParsedArgs): VSCodeWindow {
B
Benjamin Pasero 已提交
1181
		const lastActive = this.getLastActiveWindow();
E
Erich Gamma 已提交
1182
		if (lastActive) {
B
Benjamin Pasero 已提交
1183
			lastActive.focus();
1184 1185

			return lastActive;
E
Erich Gamma 已提交
1186 1187 1188
		}

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

		return res && res[0];
E
Erich Gamma 已提交
1193 1194
	}

J
Joao Moreno 已提交
1195
	public getLastActiveWindow(): VSCodeWindow {
E
Erich Gamma 已提交
1196
		if (WindowsManager.WINDOWS.length) {
1197 1198
			const lastFocussedDate = Math.max.apply(Math, WindowsManager.WINDOWS.map(w => w.lastFocusTime));
			const res = WindowsManager.WINDOWS.filter(w => w.lastFocusTime === lastFocussedDate);
E
Erich Gamma 已提交
1199 1200 1201 1202 1203 1204 1205 1206
			if (res && res.length) {
				return res[0];
			}
		}

		return null;
	}

J
Joao Moreno 已提交
1207
	public findWindow(workspacePath: string, filePath?: string, extensionDevelopmentPath?: string): VSCodeWindow {
E
Erich Gamma 已提交
1208 1209 1210
		if (WindowsManager.WINDOWS.length) {

			// Sort the last active window to the front of the array of windows to test
B
Benjamin Pasero 已提交
1211 1212
			const windowsToTest = WindowsManager.WINDOWS.slice(0);
			const lastActiveWindow = this.getLastActiveWindow();
E
Erich Gamma 已提交
1213 1214 1215 1216 1217 1218
			if (lastActiveWindow) {
				windowsToTest.splice(windowsToTest.indexOf(lastActiveWindow), 1);
				windowsToTest.unshift(lastActiveWindow);
			}

			// Find it
1219
			const res = windowsToTest.filter(w => {
E
Erich Gamma 已提交
1220 1221

				// match on workspace
1222
				if (typeof w.openedWorkspacePath === 'string' && (this.isPathEqual(w.openedWorkspacePath, workspacePath))) {
E
Erich Gamma 已提交
1223 1224 1225 1226
					return true;
				}

				// match on file
B
Benjamin Pasero 已提交
1227
				if (typeof w.openedFilePath === 'string' && this.isPathEqual(w.openedFilePath, filePath)) {
E
Erich Gamma 已提交
1228 1229 1230 1231 1232 1233 1234 1235
					return true;
				}

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

1236 1237 1238 1239 1240
				// match on extension development path
				if (typeof extensionDevelopmentPath === 'string' && w.extensionDevelopmentPath === extensionDevelopmentPath) {
					return true;
				}

E
Erich Gamma 已提交
1241 1242 1243 1244 1245 1246 1247 1248 1249 1250 1251 1252
				return false;
			});

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

		return null;
	}

	public openNewWindow(): void {
1253
		this.open({ cli: this.environmentService.args, forceNewWindow: true, forceEmpty: true });
E
Erich Gamma 已提交
1254 1255 1256 1257 1258 1259
	}

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

		if (focusedWindow) {
1260
			focusedWindow.sendWhenReady(channel, ...args);
E
Erich Gamma 已提交
1261 1262 1263 1264
		}
	}

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

1270
			w.sendWhenReady(channel, payload);
E
Erich Gamma 已提交
1271 1272 1273
		});
	}

J
Joao Moreno 已提交
1274
	public getFocusedWindow(): VSCodeWindow {
B
Benjamin Pasero 已提交
1275
		const win = BrowserWindow.getFocusedWindow();
E
Erich Gamma 已提交
1276 1277 1278 1279 1280 1281 1282
		if (win) {
			return this.getWindowById(win.id);
		}

		return null;
	}

J
Joao Moreno 已提交
1283
	public getWindowById(windowId: number): VSCodeWindow {
1284
		const res = WindowsManager.WINDOWS.filter(w => w.id === windowId);
E
Erich Gamma 已提交
1285 1286 1287 1288 1289 1290 1291
		if (res && res.length === 1) {
			return res[0];
		}

		return null;
	}

J
Joao Moreno 已提交
1292
	public getWindows(): VSCodeWindow[] {
E
Erich Gamma 已提交
1293 1294 1295 1296 1297 1298 1299
		return WindowsManager.WINDOWS;
	}

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

J
Joao Moreno 已提交
1300
	private onWindowError(vscodeWindow: VSCodeWindow, error: WindowError): void {
E
Erich Gamma 已提交
1301 1302 1303 1304
		console.error(error === WindowError.CRASHED ? '[VS Code]: render process crashed!' : '[VS Code]: detected unresponsive');

		// Unresponsive
		if (error === WindowError.UNRESPONSIVE) {
1305
			dialog.showMessageBox(vscodeWindow.win, {
B
Benjamin Pasero 已提交
1306
				title: product.nameLong,
E
Erich Gamma 已提交
1307
				type: 'warning',
B
Benjamin Pasero 已提交
1308
				buttons: [nls.localize('reopen', "Reopen"), nls.localize('wait', "Keep Waiting"), nls.localize('close', "Close")],
1309
				message: nls.localize('appStalled', "The window is no longer responding"),
B
Benjamin Pasero 已提交
1310
				detail: nls.localize('appStalledDetail', "You can reopen or close the window or keep waiting."),
E
Erich Gamma 已提交
1311
				noLink: true
1312
			}, result => {
E
Erich Gamma 已提交
1313
				if (result === 0) {
1314 1315
					vscodeWindow.reload();
				} else if (result === 2) {
1316
					this.onBeforeWindowClose(vscodeWindow); // 'close' event will not be fired on destroy(), so run it manually
1317
					vscodeWindow.win.destroy(); // make sure to destroy the window as it is unresponsive
E
Erich Gamma 已提交
1318 1319 1320 1321 1322 1323
				}
			});
		}

		// Crashed
		else {
1324
			dialog.showMessageBox(vscodeWindow.win, {
B
Benjamin Pasero 已提交
1325
				title: product.nameLong,
E
Erich Gamma 已提交
1326
				type: 'warning',
B
Benjamin Pasero 已提交
1327
				buttons: [nls.localize('reopen', "Reopen"), nls.localize('close', "Close")],
1328
				message: nls.localize('appCrashed', "The window has crashed"),
B
Benjamin Pasero 已提交
1329
				detail: nls.localize('appCrashedDetail', "We are sorry for the inconvenience! You can reopen the window to continue where you left off."),
E
Erich Gamma 已提交
1330
				noLink: true
1331
			}, result => {
1332 1333 1334
				if (result === 0) {
					vscodeWindow.reload();
				} else if (result === 1) {
1335
					this.onBeforeWindowClose(vscodeWindow); // 'close' event will not be fired on destroy(), so run it manually
1336 1337
					vscodeWindow.win.destroy(); // make sure to destroy the window as it has crashed
				}
E
Erich Gamma 已提交
1338 1339 1340 1341
			});
		}
	}

J
Joao Moreno 已提交
1342 1343
	private onBeforeWindowClose(win: VSCodeWindow): void {
		if (win.readyState !== ReadyState.READY) {
E
Erich Gamma 已提交
1344 1345 1346 1347
			return; // only persist windows that are fully loaded
		}

		// On Window close, update our stored state of this window
B
Benjamin Pasero 已提交
1348
		const state: IWindowState = { workspacePath: win.openedWorkspacePath, uiState: win.serializeWindowState() };
E
Erich Gamma 已提交
1349 1350 1351 1352 1353 1354
		if (win.isPluginDevelopmentHost) {
			this.windowsState.lastPluginDevelopmentHostWindow = state;
		} else {
			this.windowsState.lastActiveWindow = state;

			this.windowsState.openedFolders.forEach(o => {
B
Benjamin Pasero 已提交
1355
				if (this.isPathEqual(o.workspacePath, win.openedWorkspacePath)) {
E
Erich Gamma 已提交
1356 1357 1358 1359 1360 1361
					o.uiState = state.uiState;
				}
			});
		}
	}

J
Joao Moreno 已提交
1362
	private onWindowClosed(win: VSCodeWindow): void {
E
Erich Gamma 已提交
1363 1364 1365 1366 1367

		// Tell window
		win.dispose();

		// Remove from our list so that Electron can clean it up
B
Benjamin Pasero 已提交
1368
		const index = WindowsManager.WINDOWS.indexOf(win);
E
Erich Gamma 已提交
1369 1370 1371
		WindowsManager.WINDOWS.splice(index, 1);

		// Emit
J
Joao Moreno 已提交
1372
		this.eventEmitter.emit(EventTypes.CLOSE, win.id);
E
Erich Gamma 已提交
1373
	}
B
Benjamin Pasero 已提交
1374 1375 1376 1377 1378 1379 1380 1381 1382 1383 1384 1385 1386 1387 1388 1389 1390 1391 1392 1393 1394 1395 1396 1397

	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;
	}
1398
}