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

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

enum WindowError {
	UNRESPONSIVE,
	CRASHED
}

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

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

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

65
export interface IRecentPathsList {
E
Erich Gamma 已提交
66 67 68 69
	folders: string[];
	files: string[];
}

70 71 72 73 74
interface ILogEntry {
	severity: string;
	arguments: any;
}

75 76 77
interface INativeOpenDialogOptions {
	pickFolders?: boolean;
	pickFiles?: boolean;
78 79
	path?: string;
	forceNewWindow?: boolean;
80 81
}

82 83 84 85 86 87
const ReopenFoldersSetting = {
	ALL: 'all',
	ONE: 'one',
	NONE: 'none'
};

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

J
renames  
Joao Moreno 已提交
90
export interface IWindowsService {
91
	_serviceBrand: any;
J
Joao Moreno 已提交
92 93 94

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

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

S
Sandeep Somavarapu 已提交
125 126 127 128 129 130 131 132 133 134 135 136 137 138 139
export class WindowEventService implements IWindowEventService {

	_serviceBrand: any;

	constructor(@IWindowsService private windowsService: IWindowsService) { }

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

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

J
renames  
Joao Moreno 已提交
140
export class WindowsManager implements IWindowsService {
J
Joao Moreno 已提交
141

142
	_serviceBrand: any;
E
Erich Gamma 已提交
143

144
	private static MAX_TOTAL_RECENT_ENTRIES = 100;
145

146
	private static recentPathsListStorageKey = 'openedPathsList';
147
	private static workingDirPickerStorageKey = 'pickerWorkingDir';
E
Erich Gamma 已提交
148 149
	private static windowsStateStorageKey = 'windowsState';

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

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

S
Sandeep Somavarapu 已提交
156 157 158 159 160 161
	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 已提交
162 163 164
	constructor(
		@IInstantiationService private instantiationService: IInstantiationService,
		@ILogService private logService: ILogService,
J
Joao Moreno 已提交
165
		@IStorageService private storageService: IStorageService,
J
Joao Moreno 已提交
166
		@IEnvService private envService: IEnvService,
167
		@IEnvironmentService private environmentService: IEnvironmentService,
J
Joao Moreno 已提交
168
		@ILifecycleService private lifecycleService: ILifecycleService,
B
Benjamin Pasero 已提交
169
		@IUpdateService private updateService: IUpdateService,
170 171
		@IConfigurationService private configurationService: IConfigurationService
	) { }
J
Joao Moreno 已提交
172

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

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

J
Joao Moreno 已提交
179
	onReady(clb: (win: VSCodeWindow) => void): () => void {
J
Joao Moreno 已提交
180 181 182 183 184 185 186 187 188 189 190
		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);
	}

191
	public ready(initialUserEnv: platform.IProcessEnvironment): void {
E
Erich Gamma 已提交
192 193
		this.registerListeners();

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

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

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

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

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

E
Erich Gamma 已提交
234 235 236
			crashReporter.start(config);
		});

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

			if (paths && paths.length) {
J
Joao Moreno 已提交
241
				this.open({ cli: this.envService.cliArgs, pathsToOpen: paths, forceNewWindow: forceNewWindow });
E
Erich Gamma 已提交
242 243 244
			}
		});

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

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

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

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

260
			this.openFilePicker(forceNewWindow, path);
E
Erich Gamma 已提交
261 262
		});

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

266 267 268 269
			this.openFolderPicker(forceNewWindow);
		});

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

			this.openFileFolderPicker(forceNewWindow);
E
Erich Gamma 已提交
273 274
		});

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

B
Benjamin Pasero 已提交
278
			const win = this.getWindowById(windowId);
E
Erich Gamma 已提交
279
			if (win) {
J
Joao Moreno 已提交
280
				this.open({ cli: this.envService.cliArgs, forceEmpty: true, windowToUse: win });
E
Erich Gamma 已提交
281 282 283
			}
		});

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

B
Benjamin Pasero 已提交
287
			this.openNewWindow();
E
Erich Gamma 已提交
288 289
		});

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

363 364 365
		ipc.on('vscode:openRecent', (event, windowId: number) => {
			this.logService.log('IPC#vscode:openRecent');

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

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

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

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

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

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

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

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

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

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

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

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

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

425 426 427 428
				// Handle specific events on main side
				this.onBroadcast(broadcast.channel, broadcast.payload);

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

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

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

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

459 460 461 462 463 464
			const windowOnExtension = this.findWindow(null, null, extensionDevelopmentPath);
			if (windowOnExtension) {
				windowOnExtension.win.close();
			}
		});

465
		ipc.on('vscode:switchWindow', (event, windowId: number) => {
466 467
			const windows = this.getWindows();
			const window = this.getWindowById(windowId);
468 469
			window.send('vscode:switchWindow', windows.map(w => {
				return {path: w.openedWorkspacePath, title: w.win.getTitle(), id: w.id};
470 471 472
			}));
		});

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

B
Benjamin Pasero 已提交
486 487
			if (this.updateService.availableUpdate) {
				this.updateService.availableUpdate.quitAndUpdate();
E
Erich Gamma 已提交
488 489 490
			}
		});

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

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

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

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

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

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

		let loggedStartupTimes = false;
J
Joao Moreno 已提交
527
		this.onReady(window => {
528 529 530 531 532 533 534 535
			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 已提交
536 537
	}

538 539 540
	private onBroadcast(event: string, payload: any): void {

		// Theme changes
541 542
		if (event === 'vscode:changeColorTheme' && typeof payload === 'string') {
			this.storageService.setItem(VSCodeWindow.colorThemeStorageKey, payload);
543 544 545
		}
	}

B
Benjamin Pasero 已提交
546
	public reload(win: VSCodeWindow, cli?: ParsedArgs): void {
E
Erich Gamma 已提交
547 548

		// Only reload when the window has not vetoed this
549
		this.lifecycleService.unload(win).done(veto => {
E
Erich Gamma 已提交
550 551 552 553 554 555
			if (!veto) {
				win.reload(cli);
			}
		});
	}

J
Joao Moreno 已提交
556 557
	public open(openConfig: IOpenConfiguration): VSCodeWindow[] {
		let iPathsToOpen: IPath[];
B
Benjamin Pasero 已提交
558
		const usedWindows: VSCodeWindow[] = [];
E
Erich Gamma 已提交
559 560 561

		// Find paths from provided paths if any
		if (openConfig.pathsToOpen && openConfig.pathsToOpen.length > 0) {
562
			iPathsToOpen = openConfig.pathsToOpen.map(pathToOpen => {
B
Benjamin Pasero 已提交
563
				const iPath = this.toIPath(pathToOpen, false, openConfig.cli && openConfig.cli.goto);
E
Erich Gamma 已提交
564 565 566

				// Warn if the requested path to open does not exist
				if (!iPath) {
B
Benjamin Pasero 已提交
567
					const options: Electron.ShowMessageBoxOptions = {
B
Benjamin Pasero 已提交
568
						title: product.nameLong,
E
Erich Gamma 已提交
569 570 571 572 573 574 575
						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 已提交
576
					const activeWindow = BrowserWindow.getFocusedWindow();
E
Erich Gamma 已提交
577
					if (activeWindow) {
578
						dialog.showMessageBox(activeWindow, options);
E
Erich Gamma 已提交
579
					} else {
580
						dialog.showMessageBox(options);
E
Erich Gamma 已提交
581 582 583 584 585 586 587 588 589 590
					}
				}

				return iPath;
			});

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

			if (iPathsToOpen.length === 0) {
591
				return null; // indicate to outside that open failed
E
Erich Gamma 已提交
592 593 594 595 596 597 598 599 600 601
			}
		}

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

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

J
Joao Moreno 已提交
606 607
		let filesToOpen: IPath[] = [];
		let filesToDiff: IPath[] = [];
608 609 610
		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);
611 612

		// Diff mode needs special care
613
		const candidates = iPathsToOpen.filter(iPath => !!iPath.filePath && !iPath.createFilePath);
614 615 616 617 618 619 620
		if (openConfig.diffMode) {
			if (candidates.length === 2) {
				filesToDiff = candidates;
			} else {
				emptyToOpen = [Object.create(null)]; // improper use of diffMode, open empty
			}

621
			foldersToOpen = []; // diff is always in empty workspace
B
Benjamin Pasero 已提交
622
			filesToCreate = []; // diff ignores other files that do not exist
623 624 625 626
		} else {
			filesToOpen = candidates;
		}

J
Joao Moreno 已提交
627
		let configuration: IWindowConfiguration;
E
Erich Gamma 已提交
628

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

B
Benjamin Pasero 已提交
632
			// const the user settings override how files are open in a new window or same window unless we are forced
633 634 635 636 637 638
			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!)
639 640 641 642
					const windowConfig = this.configurationService.getConfiguration<IWindowSettings>('window');
					if (windowConfig && !windowConfig.openFilesInNewWindow) {
						openFilesInNewWindow = false; // do not open in new window if user configured this explicitly
					}
643
				}
E
Erich Gamma 已提交
644 645 646
			}

			// Open Files in last instance if any and flag tells us so
B
Benjamin Pasero 已提交
647
			const lastActiveWindow = this.getLastActiveWindow();
E
Erich Gamma 已提交
648
			if (!openFilesInNewWindow && lastActiveWindow) {
B
Benjamin Pasero 已提交
649
				lastActiveWindow.focus();
650
				lastActiveWindow.ready().then(readyWindow => {
651
					readyWindow.send('vscode:openFiles', { filesToOpen, filesToCreate, filesToDiff });
E
Erich Gamma 已提交
652
				});
653 654

				usedWindows.push(lastActiveWindow);
E
Erich Gamma 已提交
655 656 657 658
			}

			// Otherwise open instance with files
			else {
659
				configuration = this.toConfiguration(this.getWindowUserEnv(openConfig), openConfig.cli, null, filesToOpen, filesToCreate, filesToDiff);
B
Benjamin Pasero 已提交
660
				const browserWindow = this.openInBrowserWindow(configuration, true /* new window */);
661
				usedWindows.push(browserWindow);
E
Erich Gamma 已提交
662 663 664 665 666 667

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

		// Handle folders to open
668
		let openInNewWindow = openConfig.preferNewWindow || openConfig.forceNewWindow;
E
Erich Gamma 已提交
669 670 671
		if (foldersToOpen.length > 0) {

			// Check for existing instances
672
			const windowsOnWorkspacePath = arrays.coalesce(foldersToOpen.map(iPath => this.findWindow(iPath.workspacePath)));
E
Erich Gamma 已提交
673
			if (windowsOnWorkspacePath.length > 0) {
B
Benjamin Pasero 已提交
674
				const browserWindow = windowsOnWorkspacePath[0];
675
				browserWindow.focus(); // just focus one of them
676
				browserWindow.ready().then(readyWindow => {
677
					readyWindow.send('vscode:openFiles', { filesToOpen, filesToCreate, filesToDiff });
E
Erich Gamma 已提交
678 679
				});

680 681
				usedWindows.push(browserWindow);

E
Erich Gamma 已提交
682 683 684
				// Reset these because we handled them
				filesToOpen = [];
				filesToCreate = [];
685
				filesToDiff = [];
E
Erich Gamma 已提交
686

687
				openInNewWindow = true; // any other folders to open must open in new window then
E
Erich Gamma 已提交
688 689 690
			}

			// Open remaining ones
691 692
			foldersToOpen.forEach(folderToOpen => {
				if (windowsOnWorkspacePath.some(win => this.isPathEqual(win.openedWorkspacePath, folderToOpen.workspacePath))) {
E
Erich Gamma 已提交
693 694 695
					return; // ignore folders that are already open
				}

696
				configuration = this.toConfiguration(this.getWindowUserEnv(openConfig), openConfig.cli, folderToOpen.workspacePath, filesToOpen, filesToCreate, filesToDiff);
B
Benjamin Pasero 已提交
697
				const browserWindow = this.openInBrowserWindow(configuration, openInNewWindow, openInNewWindow ? void 0 : openConfig.windowToUse);
698
				usedWindows.push(browserWindow);
E
Erich Gamma 已提交
699 700 701 702

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

705
				openInNewWindow = true; // any other folders to open must open in new window then
E
Erich Gamma 已提交
706 707 708 709 710 711
			});
		}

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

716
				openInNewWindow = true; // any other folders to open must open in new window then
E
Erich Gamma 已提交
717 718 719
			});
		}

720
		// Remember in recent document list (unless this opens for extension development)
721
		// Also do not add paths when files are opened for diffing, only if opened individually
722
		if (!usedWindows.some(w => w.isPluginDevelopmentHost) && !openConfig.cli.diff) {
723 724 725 726 727 728 729
			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 已提交
730 731

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

734
		return arrays.distinct(usedWindows);
E
Erich Gamma 已提交
735 736
	}

737 738 739 740 741 742 743 744 745 746 747 748 749 750 751 752 753 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
	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 };
	}

810
	private getWindowUserEnv(openConfig: IOpenConfiguration): platform.IProcessEnvironment {
811 812 813
		return assign({}, this.initialUserEnv, openConfig.userEnv || {});
	}

E
Erich Gamma 已提交
814 815 816 817 818
	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.
819
		let res = WindowsManager.WINDOWS.filter(w => w.config && this.isPathEqual(w.config.extensionDevelopmentPath, openConfig.cli.extensionDevelopmentPath));
E
Erich Gamma 已提交
820 821
		if (res && res.length === 1) {
			this.reload(res[0], openConfig.cli);
B
Benjamin Pasero 已提交
822
			res[0].focus(); // make sure it gets focus and is restored
E
Erich Gamma 已提交
823 824 825 826

			return;
		}

827
		// Fill in previously opened workspace unless an explicit path is provided and we are not unit testing
B
Benjamin Pasero 已提交
828
		if (openConfig.cli._.length === 0 && !openConfig.cli.extensionTestsPath) {
B
Benjamin Pasero 已提交
829
			const workspaceToOpen = this.windowsState.lastPluginDevelopmentHostWindow && this.windowsState.lastPluginDevelopmentHostWindow.workspacePath;
E
Erich Gamma 已提交
830
			if (workspaceToOpen) {
B
Benjamin Pasero 已提交
831
				openConfig.cli._ = [workspaceToOpen];
E
Erich Gamma 已提交
832 833 834 835
			}
		}

		// Make sure we are not asked to open a path that is already opened
B
Benjamin Pasero 已提交
836 837
		if (openConfig.cli._.length > 0) {
			res = WindowsManager.WINDOWS.filter(w => w.openedWorkspacePath && openConfig.cli._.indexOf(w.openedWorkspacePath) >= 0);
E
Erich Gamma 已提交
838
			if (res.length) {
B
Benjamin Pasero 已提交
839
				openConfig.cli._ = [];
E
Erich Gamma 已提交
840 841 842 843
			}
		}

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

B
Benjamin Pasero 已提交
847
	private toConfiguration(userEnv: platform.IProcessEnvironment, cli: ParsedArgs, workspacePath?: string, filesToOpen?: IPath[], filesToCreate?: IPath[], filesToDiff?: IPath[]): IWindowConfiguration {
B
Benjamin Pasero 已提交
848
		const configuration: IWindowConfiguration = mixin({}, cli); // inherit all properties from CLI
849
		configuration.appRoot = this.environmentService.appRoot;
B
Benjamin Pasero 已提交
850 851
		configuration.execPath = process.execPath;
		configuration.userEnv = userEnv;
E
Erich Gamma 已提交
852 853 854
		configuration.workspacePath = workspacePath;
		configuration.filesToOpen = filesToOpen;
		configuration.filesToCreate = filesToCreate;
855
		configuration.filesToDiff = filesToDiff;
E
Erich Gamma 已提交
856 857 858 859

		return configuration;
	}

J
Joao Moreno 已提交
860
	private toIPath(anyPath: string, ignoreFileNotFound?: boolean, gotoLineMode?: boolean): IPath {
E
Erich Gamma 已提交
861 862 863 864
		if (!anyPath) {
			return null;
		}

J
Joao Moreno 已提交
865
		let parsedPath: IParsedPath;
E
Erich Gamma 已提交
866
		if (gotoLineMode) {
J
Joao Moreno 已提交
867
			parsedPath = parseLineAndColumnAware(anyPath);
E
Erich Gamma 已提交
868 869 870
			anyPath = parsedPath.path;
		}

B
Benjamin Pasero 已提交
871
		const candidate = path.normalize(anyPath);
E
Erich Gamma 已提交
872
		try {
B
Benjamin Pasero 已提交
873
			const candidateStat = fs.statSync(candidate);
E
Erich Gamma 已提交
874 875 876 877 878
			if (candidateStat) {
				return candidateStat.isFile() ?
					{
						filePath: candidate,
						lineNumber: gotoLineMode ? parsedPath.line : void 0,
879
						columnNumber: gotoLineMode ? parsedPath.column : void 0
E
Erich Gamma 已提交
880 881 882 883 884 885 886 887 888 889 890 891
					} :
					{ 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 已提交
892
	private cliToPaths(cli: ParsedArgs, ignoreFileNotFound?: boolean): IPath[] {
E
Erich Gamma 已提交
893 894 895

		// Check for pass in candidate or last opened path
		let candidates: string[] = [];
B
Benjamin Pasero 已提交
896 897
		if (cli._.length > 0) {
			candidates = cli._;
E
Erich Gamma 已提交
898 899 900 901
		}

		// No path argument, check settings for what to do now
		else {
902 903 904 905
			let reopenFolders: string;
			if (this.lifecycleService.wasUpdated) {
				reopenFolders = ReopenFoldersSetting.ALL; // always reopen all folders when an update was applied
			} else {
906 907
				const windowConfig = this.configurationService.getConfiguration<IWindowSettings>('window');
				reopenFolders = (windowConfig && windowConfig.reopenFolders) || ReopenFoldersSetting.ONE;
908 909
			}

B
Benjamin Pasero 已提交
910
			const lastActiveFolder = this.windowsState.lastActiveWindow && this.windowsState.lastActiveWindow.workspacePath;
E
Erich Gamma 已提交
911 912

			// Restore all
913
			if (reopenFolders === ReopenFoldersSetting.ALL) {
B
Benjamin Pasero 已提交
914
				const lastOpenedFolders = this.windowsState.openedFolders.map(o => o.workspacePath);
E
Erich Gamma 已提交
915 916 917 918 919 920 921 922 923 924 925

				// 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
926
			else if (lastActiveFolder && (reopenFolders === ReopenFoldersSetting.ONE || reopenFolders !== ReopenFoldersSetting.NONE)) {
E
Erich Gamma 已提交
927 928 929 930
				candidates.push(lastActiveFolder);
			}
		}

931
		const iPaths = candidates.map(candidate => this.toIPath(candidate, ignoreFileNotFound, cli.goto)).filter(path => !!path);
E
Erich Gamma 已提交
932 933 934 935 936 937 938 939
		if (iPaths.length > 0) {
			return iPaths;
		}

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

J
Joao Moreno 已提交
940 941
	private openInBrowserWindow(configuration: IWindowConfiguration, forceNewWindow?: boolean, windowToUse?: VSCodeWindow): VSCodeWindow {
		let vscodeWindow: VSCodeWindow;
E
Erich Gamma 已提交
942 943 944 945 946

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

			if (vscodeWindow) {
B
Benjamin Pasero 已提交
947
				vscodeWindow.focus();
E
Erich Gamma 已提交
948 949 950 951 952
			}
		}

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

J
Joao Moreno 已提交
955
			vscodeWindow = this.instantiationService.createInstance(VSCodeWindow, {
956
				state: this.getNewWindowState(configuration),
957
				extensionDevelopmentPath: configuration.extensionDevelopmentPath,
958
				allowFullscreen: this.lifecycleService.wasUpdated || (windowConfig && windowConfig.restoreFullscreen)
959 960
			});

E
Erich Gamma 已提交
961 962 963
			WindowsManager.WINDOWS.push(vscodeWindow);

			// Window Events
964 965
			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));
966 967
			vscodeWindow.win.webContents.on('crashed', () => this.onWindowError(vscodeWindow, WindowError.CRASHED));
			vscodeWindow.win.on('unresponsive', () => this.onWindowError(vscodeWindow, WindowError.UNRESPONSIVE));
E
Erich Gamma 已提交
968 969
			vscodeWindow.win.on('close', () => this.onBeforeWindowClose(vscodeWindow));
			vscodeWindow.win.on('closed', () => this.onWindowClosed(vscodeWindow));
S
Sandeep Somavarapu 已提交
970
			vscodeWindow.win.on('focus', () => this._onFocus.fire(vscodeWindow.id));
E
Erich Gamma 已提交
971

S
Sandeep Somavarapu 已提交
972
			this._onNewWindow.fire(vscodeWindow.id);
E
Erich Gamma 已提交
973
			// Lifecycle
J
Joao Moreno 已提交
974
			this.lifecycleService.registerWindow(vscodeWindow);
E
Erich Gamma 已提交
975 976 977 978 979 980 981
		}

		// 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 已提交
982
			const currentWindowConfig = vscodeWindow.config;
A
Alex Dima 已提交
983 984
			if (!configuration.extensionDevelopmentPath && currentWindowConfig && !!currentWindowConfig.extensionDevelopmentPath) {
				configuration.extensionDevelopmentPath = currentWindowConfig.extensionDevelopmentPath;
B
Benjamin Pasero 已提交
985
				configuration.verbose = currentWindowConfig.verbose;
986
				configuration.debugBrkPluginHost = currentWindowConfig.debugBrkPluginHost;
987
				configuration.debugPluginHost = currentWindowConfig.debugPluginHost;
B
Benjamin Pasero 已提交
988
				configuration.extensionHomePath = currentWindowConfig.extensionHomePath;
E
Erich Gamma 已提交
989 990 991 992
			}
		}

		// Only load when the window has not vetoed this
993
		this.lifecycleService.unload(vscodeWindow).done(veto => {
E
Erich Gamma 已提交
994 995 996 997 998 999
			if (!veto) {

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

		return vscodeWindow;
E
Erich Gamma 已提交
1002 1003
	}

J
Joao Moreno 已提交
1004
	private getNewWindowState(configuration: IWindowConfiguration): ISingleWindowState {
E
Erich Gamma 已提交
1005 1006

		// plugin development host Window - load from stored settings if any
A
Alex Dima 已提交
1007
		if (!!configuration.extensionDevelopmentPath && this.windowsState.lastPluginDevelopmentHostWindow) {
E
Erich Gamma 已提交
1008 1009 1010 1011 1012
			return this.windowsState.lastPluginDevelopmentHostWindow.uiState;
		}

		// Known Folder - load from stored settings if any
		if (configuration.workspacePath) {
B
Benjamin Pasero 已提交
1013
			const stateForWorkspace = this.windowsState.openedFolders.filter(o => this.isPathEqual(o.workspacePath, configuration.workspacePath)).map(o => o.uiState);
E
Erich Gamma 已提交
1014 1015 1016 1017 1018 1019
			if (stateForWorkspace.length) {
				return stateForWorkspace[0];
			}
		}

		// First Window
B
Benjamin Pasero 已提交
1020
		const lastActive = this.getLastActiveWindow();
E
Erich Gamma 已提交
1021 1022 1023 1024 1025 1026 1027 1028 1029
		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
1030
		let displayToUse: Electron.Display;
B
Benjamin Pasero 已提交
1031
		const displays = screen.getAllDisplays();
E
Erich Gamma 已提交
1032 1033 1034 1035 1036 1037 1038 1039 1040 1041 1042

		// 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 已提交
1043
				const cursorPoint = screen.getCursorScreenPoint();
E
Erich Gamma 已提交
1044 1045 1046 1047 1048 1049 1050 1051 1052 1053 1054 1055 1056 1057
				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 已提交
1058
		const defaultState = defaultWindowState();
E
Erich Gamma 已提交
1059 1060 1061 1062 1063 1064
		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 已提交
1065
	private ensureNoOverlap(state: ISingleWindowState): ISingleWindowState {
E
Erich Gamma 已提交
1066 1067 1068 1069
		if (WindowsManager.WINDOWS.length === 0) {
			return state;
		}

1070 1071
		const existingWindowBounds = WindowsManager.WINDOWS.map(win => win.getBounds());
		while (existingWindowBounds.some(b => b.x === state.x || b.y === state.y)) {
E
Erich Gamma 已提交
1072 1073 1074 1075 1076 1077 1078
			state.x += 30;
			state.y += 30;
		}

		return state;
	}

1079
	public openFileFolderPicker(forceNewWindow?: boolean): void {
1080
		this.doPickAndOpen({ pickFolders: true, pickFiles: true, forceNewWindow });
1081 1082
	}

1083 1084
	public openFilePicker(forceNewWindow?: boolean, path?: string): void {
		this.doPickAndOpen({ pickFiles: true, forceNewWindow, path });
1085 1086 1087
	}

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

1091 1092 1093 1094 1095 1096 1097 1098 1099 1100 1101 1102 1103 1104 1105 1106
	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');
	}

1107
	private doPickAndOpen(options: INativeOpenDialogOptions): void {
1108
		this.getFileOrFolderPaths(options, (paths: string[]) => {
E
Erich Gamma 已提交
1109
			if (paths && paths.length) {
1110
				this.open({ cli: this.envService.cliArgs, pathsToOpen: paths, forceNewWindow: options.forceNewWindow });
E
Erich Gamma 已提交
1111 1112 1113 1114
			}
		});
	}

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

B
Benjamin Pasero 已提交
1119
		let pickerProperties: ('openFile' | 'openDirectory' | 'multiSelections' | 'createDirectory')[];
1120
		if (options.pickFiles && options.pickFolders) {
E
Erich Gamma 已提交
1121 1122
			pickerProperties = ['multiSelections', 'openDirectory', 'openFile', 'createDirectory'];
		} else {
1123
			pickerProperties = ['multiSelections', options.pickFolders ? 'openDirectory' : 'openFile', 'createDirectory'];
E
Erich Gamma 已提交
1124 1125
		}

1126
		dialog.showOpenDialog(focussedWindow && focussedWindow.win, {
E
Erich Gamma 已提交
1127 1128
			defaultPath: workingDir,
			properties: pickerProperties
1129
		}, paths => {
E
Erich Gamma 已提交
1130 1131 1132
			if (paths && paths.length > 0) {

				// Remember path in storage for next time
J
Joao Moreno 已提交
1133
				this.storageService.setItem(WindowsManager.workingDirPickerStorageKey, path.dirname(paths[0]));
E
Erich Gamma 已提交
1134 1135 1136 1137 1138 1139 1140 1141 1142

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

B
Benjamin Pasero 已提交
1143
	public focusLastActive(cli: ParsedArgs): VSCodeWindow {
B
Benjamin Pasero 已提交
1144
		const lastActive = this.getLastActiveWindow();
E
Erich Gamma 已提交
1145
		if (lastActive) {
B
Benjamin Pasero 已提交
1146
			lastActive.focus();
1147 1148

			return lastActive;
E
Erich Gamma 已提交
1149 1150 1151
		}

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

		return res && res[0];
E
Erich Gamma 已提交
1156 1157
	}

J
Joao Moreno 已提交
1158
	public getLastActiveWindow(): VSCodeWindow {
E
Erich Gamma 已提交
1159
		if (WindowsManager.WINDOWS.length) {
1160 1161
			const lastFocussedDate = Math.max.apply(Math, WindowsManager.WINDOWS.map(w => w.lastFocusTime));
			const res = WindowsManager.WINDOWS.filter(w => w.lastFocusTime === lastFocussedDate);
E
Erich Gamma 已提交
1162 1163 1164 1165 1166 1167 1168 1169
			if (res && res.length) {
				return res[0];
			}
		}

		return null;
	}

J
Joao Moreno 已提交
1170
	public findWindow(workspacePath: string, filePath?: string, extensionDevelopmentPath?: string): VSCodeWindow {
E
Erich Gamma 已提交
1171 1172 1173
		if (WindowsManager.WINDOWS.length) {

			// Sort the last active window to the front of the array of windows to test
B
Benjamin Pasero 已提交
1174 1175
			const windowsToTest = WindowsManager.WINDOWS.slice(0);
			const lastActiveWindow = this.getLastActiveWindow();
E
Erich Gamma 已提交
1176 1177 1178 1179 1180 1181
			if (lastActiveWindow) {
				windowsToTest.splice(windowsToTest.indexOf(lastActiveWindow), 1);
				windowsToTest.unshift(lastActiveWindow);
			}

			// Find it
1182
			const res = windowsToTest.filter(w => {
E
Erich Gamma 已提交
1183 1184

				// match on workspace
1185
				if (typeof w.openedWorkspacePath === 'string' && (this.isPathEqual(w.openedWorkspacePath, workspacePath))) {
E
Erich Gamma 已提交
1186 1187 1188 1189
					return true;
				}

				// match on file
B
Benjamin Pasero 已提交
1190
				if (typeof w.openedFilePath === 'string' && this.isPathEqual(w.openedFilePath, filePath)) {
E
Erich Gamma 已提交
1191 1192 1193 1194 1195 1196 1197 1198
					return true;
				}

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

1199 1200 1201 1202 1203
				// match on extension development path
				if (typeof extensionDevelopmentPath === 'string' && w.extensionDevelopmentPath === extensionDevelopmentPath) {
					return true;
				}

E
Erich Gamma 已提交
1204 1205 1206 1207 1208 1209 1210 1211 1212 1213 1214 1215
				return false;
			});

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

		return null;
	}

	public openNewWindow(): void {
J
Joao Moreno 已提交
1216
		this.open({ cli: this.envService.cliArgs, forceNewWindow: true, forceEmpty: true });
E
Erich Gamma 已提交
1217 1218 1219 1220 1221 1222
	}

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

		if (focusedWindow) {
1223
			focusedWindow.sendWhenReady(channel, ...args);
E
Erich Gamma 已提交
1224 1225 1226 1227
		}
	}

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

1233
			w.sendWhenReady(channel, payload);
E
Erich Gamma 已提交
1234 1235 1236
		});
	}

J
Joao Moreno 已提交
1237
	public getFocusedWindow(): VSCodeWindow {
B
Benjamin Pasero 已提交
1238
		const win = BrowserWindow.getFocusedWindow();
E
Erich Gamma 已提交
1239 1240 1241 1242 1243 1244 1245
		if (win) {
			return this.getWindowById(win.id);
		}

		return null;
	}

J
Joao Moreno 已提交
1246
	public getWindowById(windowId: number): VSCodeWindow {
1247
		const res = WindowsManager.WINDOWS.filter(w => w.id === windowId);
E
Erich Gamma 已提交
1248 1249 1250 1251 1252 1253 1254
		if (res && res.length === 1) {
			return res[0];
		}

		return null;
	}

J
Joao Moreno 已提交
1255
	public getWindows(): VSCodeWindow[] {
E
Erich Gamma 已提交
1256 1257 1258 1259 1260 1261 1262
		return WindowsManager.WINDOWS;
	}

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

J
Joao Moreno 已提交
1263
	private onWindowError(vscodeWindow: VSCodeWindow, error: WindowError): void {
E
Erich Gamma 已提交
1264 1265 1266 1267
		console.error(error === WindowError.CRASHED ? '[VS Code]: render process crashed!' : '[VS Code]: detected unresponsive');

		// Unresponsive
		if (error === WindowError.UNRESPONSIVE) {
1268
			dialog.showMessageBox(vscodeWindow.win, {
B
Benjamin Pasero 已提交
1269
				title: product.nameLong,
E
Erich Gamma 已提交
1270
				type: 'warning',
B
Benjamin Pasero 已提交
1271
				buttons: [nls.localize('reopen', "Reopen"), nls.localize('wait', "Keep Waiting"), nls.localize('close', "Close")],
1272
				message: nls.localize('appStalled', "The window is no longer responding"),
B
Benjamin Pasero 已提交
1273
				detail: nls.localize('appStalledDetail', "You can reopen or close the window or keep waiting."),
E
Erich Gamma 已提交
1274
				noLink: true
1275
			}, result => {
E
Erich Gamma 已提交
1276
				if (result === 0) {
1277 1278
					vscodeWindow.reload();
				} else if (result === 2) {
1279
					this.onBeforeWindowClose(vscodeWindow); // 'close' event will not be fired on destroy(), so run it manually
1280
					vscodeWindow.win.destroy(); // make sure to destroy the window as it is unresponsive
E
Erich Gamma 已提交
1281 1282 1283 1284 1285 1286
				}
			});
		}

		// Crashed
		else {
1287
			dialog.showMessageBox(vscodeWindow.win, {
B
Benjamin Pasero 已提交
1288
				title: product.nameLong,
E
Erich Gamma 已提交
1289
				type: 'warning',
B
Benjamin Pasero 已提交
1290
				buttons: [nls.localize('reopen', "Reopen"), nls.localize('close', "Close")],
1291
				message: nls.localize('appCrashed', "The window has crashed"),
B
Benjamin Pasero 已提交
1292
				detail: nls.localize('appCrashedDetail', "We are sorry for the inconvenience! You can reopen the window to continue where you left off."),
E
Erich Gamma 已提交
1293
				noLink: true
1294
			}, result => {
1295 1296 1297
				if (result === 0) {
					vscodeWindow.reload();
				} else if (result === 1) {
1298
					this.onBeforeWindowClose(vscodeWindow); // 'close' event will not be fired on destroy(), so run it manually
1299 1300
					vscodeWindow.win.destroy(); // make sure to destroy the window as it has crashed
				}
E
Erich Gamma 已提交
1301 1302 1303 1304
			});
		}
	}

J
Joao Moreno 已提交
1305 1306
	private onBeforeWindowClose(win: VSCodeWindow): void {
		if (win.readyState !== ReadyState.READY) {
E
Erich Gamma 已提交
1307 1308 1309 1310
			return; // only persist windows that are fully loaded
		}

		// On Window close, update our stored state of this window
B
Benjamin Pasero 已提交
1311
		const state: IWindowState = { workspacePath: win.openedWorkspacePath, uiState: win.serializeWindowState() };
E
Erich Gamma 已提交
1312 1313 1314 1315 1316 1317
		if (win.isPluginDevelopmentHost) {
			this.windowsState.lastPluginDevelopmentHostWindow = state;
		} else {
			this.windowsState.lastActiveWindow = state;

			this.windowsState.openedFolders.forEach(o => {
B
Benjamin Pasero 已提交
1318
				if (this.isPathEqual(o.workspacePath, win.openedWorkspacePath)) {
E
Erich Gamma 已提交
1319 1320 1321 1322 1323 1324
					o.uiState = state.uiState;
				}
			});
		}
	}

J
Joao Moreno 已提交
1325
	private onWindowClosed(win: VSCodeWindow): void {
E
Erich Gamma 已提交
1326 1327 1328 1329 1330

		// Tell window
		win.dispose();

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

		// Emit
J
Joao Moreno 已提交
1335
		this.eventEmitter.emit(EventTypes.CLOSE, win.id);
E
Erich Gamma 已提交
1336
	}
B
Benjamin Pasero 已提交
1337 1338 1339 1340 1341 1342 1343 1344 1345 1346 1347 1348 1349 1350 1351 1352 1353 1354 1355 1356 1357 1358 1359 1360

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