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

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

enum WindowError {
	UNRESPONSIVE,
	CRASHED
}

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

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

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

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

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

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

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

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

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

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

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

S
Sandeep Somavarapu 已提交
127 128 129 130 131 132 133 134 135 136 137 138 139 140 141
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 已提交
142
export class WindowsManager implements IWindowsService {
J
Joao Moreno 已提交
143

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

146
	private static MAX_TOTAL_RECENT_ENTRIES = 100;
147

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

540 541 542
	private onBroadcast(event: string, payload: any): void {

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

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

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

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

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

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

				return iPath;
			});

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

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

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

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

608 609 610 611 612 613 614 615 616 617 618 619 620 621 622
		// Restore backups if they exist and it's the first instance
		if (openConfig.restoreBackups) {
			const backupWorkspaces = this.backupService.getBackupWorkspaces();
			if (backupWorkspaces.length > 0) {
				backupWorkspaces.forEach(workspace => {
					iPathsToOpen.push(this.toIPath(workspace));
				});
				// Get rid of duplicates
				iPathsToOpen = arrays.distinct(iPathsToOpen, path => {
					return path.workspacePath;
				});
				this.backupService.clearBackupWorkspaces();
			}
		}

J
Joao Moreno 已提交
623 624
		let filesToOpen: IPath[] = [];
		let filesToDiff: IPath[] = [];
625 626 627
		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);
628 629

		// Diff mode needs special care
630
		const candidates = iPathsToOpen.filter(iPath => !!iPath.filePath && !iPath.createFilePath);
631 632 633 634 635 636 637
		if (openConfig.diffMode) {
			if (candidates.length === 2) {
				filesToDiff = candidates;
			} else {
				emptyToOpen = [Object.create(null)]; // improper use of diffMode, open empty
			}

638
			foldersToOpen = []; // diff is always in empty workspace
B
Benjamin Pasero 已提交
639
			filesToCreate = []; // diff ignores other files that do not exist
640 641 642 643
		} else {
			filesToOpen = candidates;
		}

J
Joao Moreno 已提交
644
		let configuration: IWindowConfiguration;
E
Erich Gamma 已提交
645

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

B
Benjamin Pasero 已提交
649
			// const the user settings override how files are open in a new window or same window unless we are forced
650 651 652 653 654 655
			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!)
656 657 658 659
					const windowConfig = this.configurationService.getConfiguration<IWindowSettings>('window');
					if (windowConfig && !windowConfig.openFilesInNewWindow) {
						openFilesInNewWindow = false; // do not open in new window if user configured this explicitly
					}
660
				}
E
Erich Gamma 已提交
661 662 663
			}

			// Open Files in last instance if any and flag tells us so
B
Benjamin Pasero 已提交
664
			const lastActiveWindow = this.getLastActiveWindow();
E
Erich Gamma 已提交
665
			if (!openFilesInNewWindow && lastActiveWindow) {
B
Benjamin Pasero 已提交
666
				lastActiveWindow.focus();
667
				lastActiveWindow.ready().then(readyWindow => {
668
					readyWindow.send('vscode:openFiles', { filesToOpen, filesToCreate, filesToDiff });
E
Erich Gamma 已提交
669
				});
670 671

				usedWindows.push(lastActiveWindow);
E
Erich Gamma 已提交
672 673 674 675
			}

			// Otherwise open instance with files
			else {
676
				configuration = this.toConfiguration(this.getWindowUserEnv(openConfig), openConfig.cli, null, filesToOpen, filesToCreate, filesToDiff);
B
Benjamin Pasero 已提交
677
				const browserWindow = this.openInBrowserWindow(configuration, true /* new window */);
678
				usedWindows.push(browserWindow);
E
Erich Gamma 已提交
679 680 681 682 683 684

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

		// Handle folders to open
685
		let openInNewWindow = openConfig.preferNewWindow || openConfig.forceNewWindow;
E
Erich Gamma 已提交
686 687 688
		if (foldersToOpen.length > 0) {

			// Check for existing instances
689
			const windowsOnWorkspacePath = arrays.coalesce(foldersToOpen.map(iPath => this.findWindow(iPath.workspacePath)));
E
Erich Gamma 已提交
690
			if (windowsOnWorkspacePath.length > 0) {
B
Benjamin Pasero 已提交
691
				const browserWindow = windowsOnWorkspacePath[0];
692
				browserWindow.focus(); // just focus one of them
693
				browserWindow.ready().then(readyWindow => {
694
					readyWindow.send('vscode:openFiles', { filesToOpen, filesToCreate, filesToDiff });
E
Erich Gamma 已提交
695 696
				});

697 698
				usedWindows.push(browserWindow);

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

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

			// Open remaining ones
708 709
			foldersToOpen.forEach(folderToOpen => {
				if (windowsOnWorkspacePath.some(win => this.isPathEqual(win.openedWorkspacePath, folderToOpen.workspacePath))) {
E
Erich Gamma 已提交
710 711 712
					return; // ignore folders that are already open
				}

713
				configuration = this.toConfiguration(this.getWindowUserEnv(openConfig), openConfig.cli, folderToOpen.workspacePath, filesToOpen, filesToCreate, filesToDiff);
B
Benjamin Pasero 已提交
714
				const browserWindow = this.openInBrowserWindow(configuration, openInNewWindow, openInNewWindow ? void 0 : openConfig.windowToUse);
715
				usedWindows.push(browserWindow);
E
Erich Gamma 已提交
716 717 718 719

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

722
				openInNewWindow = true; // any other folders to open must open in new window then
E
Erich Gamma 已提交
723 724 725 726 727 728
			});
		}

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

733
				openInNewWindow = true; // any other folders to open must open in new window then
E
Erich Gamma 已提交
734 735 736
			});
		}

737
		// Remember in recent document list (unless this opens for extension development)
738
		// Also do not add paths when files are opened for diffing, only if opened individually
739
		if (!usedWindows.some(w => w.isPluginDevelopmentHost) && !openConfig.cli.diff) {
740 741 742 743 744 745 746
			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 已提交
747 748

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

751 752 753 754 755
		// Add to backups
		this.backupService.pushBackupWorkspaces(iPathsToOpen.map((path) => {
			return path.workspacePath;
		}));

756
		return arrays.distinct(usedWindows);
E
Erich Gamma 已提交
757 758
	}

759 760 761 762 763 764 765 766 767 768 769 770 771 772 773 774 775 776 777 778 779 780 781 782 783 784 785 786 787 788 789 790 791 792 793 794 795 796 797 798 799 800 801 802 803 804 805 806 807 808 809 810 811 812 813 814 815 816 817 818 819 820 821 822 823 824 825 826 827 828 829 830 831
	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 };
	}

832
	private getWindowUserEnv(openConfig: IOpenConfiguration): platform.IProcessEnvironment {
833 834 835
		return assign({}, this.initialUserEnv, openConfig.userEnv || {});
	}

E
Erich Gamma 已提交
836 837 838 839 840
	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.
841
		let res = WindowsManager.WINDOWS.filter(w => w.config && this.isPathEqual(w.config.extensionDevelopmentPath, openConfig.cli.extensionDevelopmentPath));
E
Erich Gamma 已提交
842 843
		if (res && res.length === 1) {
			this.reload(res[0], openConfig.cli);
B
Benjamin Pasero 已提交
844
			res[0].focus(); // make sure it gets focus and is restored
E
Erich Gamma 已提交
845 846 847 848

			return;
		}

849
		// Fill in previously opened workspace unless an explicit path is provided and we are not unit testing
B
Benjamin Pasero 已提交
850
		if (openConfig.cli._.length === 0 && !openConfig.cli.extensionTestsPath) {
B
Benjamin Pasero 已提交
851
			const workspaceToOpen = this.windowsState.lastPluginDevelopmentHostWindow && this.windowsState.lastPluginDevelopmentHostWindow.workspacePath;
E
Erich Gamma 已提交
852
			if (workspaceToOpen) {
B
Benjamin Pasero 已提交
853
				openConfig.cli._ = [workspaceToOpen];
E
Erich Gamma 已提交
854 855 856 857
			}
		}

		// Make sure we are not asked to open a path that is already opened
B
Benjamin Pasero 已提交
858 859
		if (openConfig.cli._.length > 0) {
			res = WindowsManager.WINDOWS.filter(w => w.openedWorkspacePath && openConfig.cli._.indexOf(w.openedWorkspacePath) >= 0);
E
Erich Gamma 已提交
860
			if (res.length) {
B
Benjamin Pasero 已提交
861
				openConfig.cli._ = [];
E
Erich Gamma 已提交
862 863 864 865
			}
		}

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

B
Benjamin Pasero 已提交
869
	private toConfiguration(userEnv: platform.IProcessEnvironment, cli: ParsedArgs, workspacePath?: string, filesToOpen?: IPath[], filesToCreate?: IPath[], filesToDiff?: IPath[]): IWindowConfiguration {
B
Benjamin Pasero 已提交
870
		const configuration: IWindowConfiguration = mixin({}, cli); // inherit all properties from CLI
871
		configuration.appRoot = this.environmentService.appRoot;
B
Benjamin Pasero 已提交
872 873
		configuration.execPath = process.execPath;
		configuration.userEnv = userEnv;
E
Erich Gamma 已提交
874 875 876
		configuration.workspacePath = workspacePath;
		configuration.filesToOpen = filesToOpen;
		configuration.filesToCreate = filesToCreate;
877
		configuration.filesToDiff = filesToDiff;
E
Erich Gamma 已提交
878 879 880 881

		return configuration;
	}

J
Joao Moreno 已提交
882
	private toIPath(anyPath: string, ignoreFileNotFound?: boolean, gotoLineMode?: boolean): IPath {
E
Erich Gamma 已提交
883 884 885 886
		if (!anyPath) {
			return null;
		}

887
		let parsedPath: IPathWithLineAndColumn;
E
Erich Gamma 已提交
888
		if (gotoLineMode) {
J
Joao Moreno 已提交
889
			parsedPath = parseLineAndColumnAware(anyPath);
E
Erich Gamma 已提交
890 891 892
			anyPath = parsedPath.path;
		}

B
Benjamin Pasero 已提交
893
		const candidate = path.normalize(anyPath);
E
Erich Gamma 已提交
894
		try {
B
Benjamin Pasero 已提交
895
			const candidateStat = fs.statSync(candidate);
E
Erich Gamma 已提交
896 897 898 899 900
			if (candidateStat) {
				return candidateStat.isFile() ?
					{
						filePath: candidate,
						lineNumber: gotoLineMode ? parsedPath.line : void 0,
901
						columnNumber: gotoLineMode ? parsedPath.column : void 0
E
Erich Gamma 已提交
902 903 904 905 906 907 908 909 910 911 912 913
					} :
					{ 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 已提交
914
	private cliToPaths(cli: ParsedArgs, ignoreFileNotFound?: boolean): IPath[] {
E
Erich Gamma 已提交
915 916 917

		// Check for pass in candidate or last opened path
		let candidates: string[] = [];
B
Benjamin Pasero 已提交
918 919
		if (cli._.length > 0) {
			candidates = cli._;
E
Erich Gamma 已提交
920 921 922 923
		}

		// No path argument, check settings for what to do now
		else {
924 925 926 927
			let reopenFolders: string;
			if (this.lifecycleService.wasUpdated) {
				reopenFolders = ReopenFoldersSetting.ALL; // always reopen all folders when an update was applied
			} else {
928 929
				const windowConfig = this.configurationService.getConfiguration<IWindowSettings>('window');
				reopenFolders = (windowConfig && windowConfig.reopenFolders) || ReopenFoldersSetting.ONE;
930 931
			}

B
Benjamin Pasero 已提交
932
			const lastActiveFolder = this.windowsState.lastActiveWindow && this.windowsState.lastActiveWindow.workspacePath;
E
Erich Gamma 已提交
933 934

			// Restore all
935
			if (reopenFolders === ReopenFoldersSetting.ALL) {
B
Benjamin Pasero 已提交
936
				const lastOpenedFolders = this.windowsState.openedFolders.map(o => o.workspacePath);
E
Erich Gamma 已提交
937 938 939 940 941 942 943 944 945 946 947

				// 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
948
			else if (lastActiveFolder && (reopenFolders === ReopenFoldersSetting.ONE || reopenFolders !== ReopenFoldersSetting.NONE)) {
E
Erich Gamma 已提交
949 950 951 952
				candidates.push(lastActiveFolder);
			}
		}

953
		const iPaths = candidates.map(candidate => this.toIPath(candidate, ignoreFileNotFound, cli.goto)).filter(path => !!path);
E
Erich Gamma 已提交
954 955 956 957 958 959 960 961
		if (iPaths.length > 0) {
			return iPaths;
		}

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

J
Joao Moreno 已提交
962 963
	private openInBrowserWindow(configuration: IWindowConfiguration, forceNewWindow?: boolean, windowToUse?: VSCodeWindow): VSCodeWindow {
		let vscodeWindow: VSCodeWindow;
E
Erich Gamma 已提交
964 965 966 967 968

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

			if (vscodeWindow) {
B
Benjamin Pasero 已提交
969
				vscodeWindow.focus();
E
Erich Gamma 已提交
970 971 972 973 974
			}
		}

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

J
Joao Moreno 已提交
977
			vscodeWindow = this.instantiationService.createInstance(VSCodeWindow, {
978
				state: this.getNewWindowState(configuration),
979
				extensionDevelopmentPath: configuration.extensionDevelopmentPath,
980
				allowFullscreen: this.lifecycleService.wasUpdated || (windowConfig && windowConfig.restoreFullscreen)
981 982
			});

E
Erich Gamma 已提交
983 984 985
			WindowsManager.WINDOWS.push(vscodeWindow);

			// Window Events
986 987
			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));
988 989
			vscodeWindow.win.webContents.on('crashed', () => this.onWindowError(vscodeWindow, WindowError.CRASHED));
			vscodeWindow.win.on('unresponsive', () => this.onWindowError(vscodeWindow, WindowError.UNRESPONSIVE));
E
Erich Gamma 已提交
990 991
			vscodeWindow.win.on('close', () => this.onBeforeWindowClose(vscodeWindow));
			vscodeWindow.win.on('closed', () => this.onWindowClosed(vscodeWindow));
S
Sandeep Somavarapu 已提交
992
			vscodeWindow.win.on('focus', () => this._onFocus.fire(vscodeWindow.id));
E
Erich Gamma 已提交
993

S
Sandeep Somavarapu 已提交
994
			this._onNewWindow.fire(vscodeWindow.id);
E
Erich Gamma 已提交
995
			// Lifecycle
J
Joao Moreno 已提交
996
			this.lifecycleService.registerWindow(vscodeWindow);
E
Erich Gamma 已提交
997 998 999 1000 1001 1002 1003
		}

		// 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 已提交
1004
			const currentWindowConfig = vscodeWindow.config;
A
Alex Dima 已提交
1005 1006
			if (!configuration.extensionDevelopmentPath && currentWindowConfig && !!currentWindowConfig.extensionDevelopmentPath) {
				configuration.extensionDevelopmentPath = currentWindowConfig.extensionDevelopmentPath;
B
Benjamin Pasero 已提交
1007
				configuration.verbose = currentWindowConfig.verbose;
1008
				configuration.debugBrkPluginHost = currentWindowConfig.debugBrkPluginHost;
1009
				configuration.debugPluginHost = currentWindowConfig.debugPluginHost;
B
Benjamin Pasero 已提交
1010
				configuration.extensionHomePath = currentWindowConfig.extensionHomePath;
E
Erich Gamma 已提交
1011 1012 1013 1014
			}
		}

		// Only load when the window has not vetoed this
1015
		this.lifecycleService.unload(vscodeWindow).done(veto => {
E
Erich Gamma 已提交
1016 1017 1018 1019 1020 1021
			if (!veto) {

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

		return vscodeWindow;
E
Erich Gamma 已提交
1024 1025
	}

J
Joao Moreno 已提交
1026
	private getNewWindowState(configuration: IWindowConfiguration): ISingleWindowState {
E
Erich Gamma 已提交
1027 1028

		// plugin development host Window - load from stored settings if any
A
Alex Dima 已提交
1029
		if (!!configuration.extensionDevelopmentPath && this.windowsState.lastPluginDevelopmentHostWindow) {
E
Erich Gamma 已提交
1030 1031 1032 1033 1034
			return this.windowsState.lastPluginDevelopmentHostWindow.uiState;
		}

		// Known Folder - load from stored settings if any
		if (configuration.workspacePath) {
B
Benjamin Pasero 已提交
1035
			const stateForWorkspace = this.windowsState.openedFolders.filter(o => this.isPathEqual(o.workspacePath, configuration.workspacePath)).map(o => o.uiState);
E
Erich Gamma 已提交
1036 1037 1038 1039 1040 1041
			if (stateForWorkspace.length) {
				return stateForWorkspace[0];
			}
		}

		// First Window
B
Benjamin Pasero 已提交
1042
		const lastActive = this.getLastActiveWindow();
E
Erich Gamma 已提交
1043 1044 1045 1046 1047 1048 1049 1050 1051
		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
1052
		let displayToUse: Electron.Display;
B
Benjamin Pasero 已提交
1053
		const displays = screen.getAllDisplays();
E
Erich Gamma 已提交
1054 1055 1056 1057 1058 1059 1060 1061 1062 1063 1064

		// 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 已提交
1065
				const cursorPoint = screen.getCursorScreenPoint();
E
Erich Gamma 已提交
1066 1067 1068 1069 1070 1071 1072 1073 1074 1075 1076 1077 1078 1079
				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 已提交
1080
		const defaultState = defaultWindowState();
E
Erich Gamma 已提交
1081 1082 1083 1084 1085 1086
		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 已提交
1087
	private ensureNoOverlap(state: ISingleWindowState): ISingleWindowState {
E
Erich Gamma 已提交
1088 1089 1090 1091
		if (WindowsManager.WINDOWS.length === 0) {
			return state;
		}

1092 1093
		const existingWindowBounds = WindowsManager.WINDOWS.map(win => win.getBounds());
		while (existingWindowBounds.some(b => b.x === state.x || b.y === state.y)) {
E
Erich Gamma 已提交
1094 1095 1096 1097 1098 1099 1100
			state.x += 30;
			state.y += 30;
		}

		return state;
	}

1101
	public openFileFolderPicker(forceNewWindow?: boolean): void {
1102
		this.doPickAndOpen({ pickFolders: true, pickFiles: true, forceNewWindow });
1103 1104
	}

1105 1106
	public openFilePicker(forceNewWindow?: boolean, path?: string): void {
		this.doPickAndOpen({ pickFiles: true, forceNewWindow, path });
1107 1108 1109
	}

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

1113 1114 1115 1116 1117 1118 1119 1120 1121 1122 1123 1124 1125 1126 1127 1128
	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');
	}

1129
	private doPickAndOpen(options: INativeOpenDialogOptions): void {
1130
		this.getFileOrFolderPaths(options, (paths: string[]) => {
E
Erich Gamma 已提交
1131
			if (paths && paths.length) {
1132
				this.open({ cli: this.environmentService.args, pathsToOpen: paths, forceNewWindow: options.forceNewWindow });
E
Erich Gamma 已提交
1133 1134 1135 1136
			}
		});
	}

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

B
Benjamin Pasero 已提交
1141
		let pickerProperties: ('openFile' | 'openDirectory' | 'multiSelections' | 'createDirectory')[];
1142
		if (options.pickFiles && options.pickFolders) {
E
Erich Gamma 已提交
1143 1144
			pickerProperties = ['multiSelections', 'openDirectory', 'openFile', 'createDirectory'];
		} else {
1145
			pickerProperties = ['multiSelections', options.pickFolders ? 'openDirectory' : 'openFile', 'createDirectory'];
E
Erich Gamma 已提交
1146 1147
		}

1148
		dialog.showOpenDialog(focussedWindow && focussedWindow.win, {
E
Erich Gamma 已提交
1149 1150
			defaultPath: workingDir,
			properties: pickerProperties
1151
		}, paths => {
E
Erich Gamma 已提交
1152 1153 1154
			if (paths && paths.length > 0) {

				// Remember path in storage for next time
J
Joao Moreno 已提交
1155
				this.storageService.setItem(WindowsManager.workingDirPickerStorageKey, path.dirname(paths[0]));
E
Erich Gamma 已提交
1156 1157 1158 1159 1160 1161 1162 1163 1164

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

B
Benjamin Pasero 已提交
1165
	public focusLastActive(cli: ParsedArgs): VSCodeWindow {
B
Benjamin Pasero 已提交
1166
		const lastActive = this.getLastActiveWindow();
E
Erich Gamma 已提交
1167
		if (lastActive) {
B
Benjamin Pasero 已提交
1168
			lastActive.focus();
1169 1170

			return lastActive;
E
Erich Gamma 已提交
1171 1172 1173
		}

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

		return res && res[0];
E
Erich Gamma 已提交
1178 1179
	}

J
Joao Moreno 已提交
1180
	public getLastActiveWindow(): VSCodeWindow {
E
Erich Gamma 已提交
1181
		if (WindowsManager.WINDOWS.length) {
1182 1183
			const lastFocussedDate = Math.max.apply(Math, WindowsManager.WINDOWS.map(w => w.lastFocusTime));
			const res = WindowsManager.WINDOWS.filter(w => w.lastFocusTime === lastFocussedDate);
E
Erich Gamma 已提交
1184 1185 1186 1187 1188 1189 1190 1191
			if (res && res.length) {
				return res[0];
			}
		}

		return null;
	}

J
Joao Moreno 已提交
1192
	public findWindow(workspacePath: string, filePath?: string, extensionDevelopmentPath?: string): VSCodeWindow {
E
Erich Gamma 已提交
1193 1194 1195
		if (WindowsManager.WINDOWS.length) {

			// Sort the last active window to the front of the array of windows to test
B
Benjamin Pasero 已提交
1196 1197
			const windowsToTest = WindowsManager.WINDOWS.slice(0);
			const lastActiveWindow = this.getLastActiveWindow();
E
Erich Gamma 已提交
1198 1199 1200 1201 1202 1203
			if (lastActiveWindow) {
				windowsToTest.splice(windowsToTest.indexOf(lastActiveWindow), 1);
				windowsToTest.unshift(lastActiveWindow);
			}

			// Find it
1204
			const res = windowsToTest.filter(w => {
E
Erich Gamma 已提交
1205 1206

				// match on workspace
1207
				if (typeof w.openedWorkspacePath === 'string' && (this.isPathEqual(w.openedWorkspacePath, workspacePath))) {
E
Erich Gamma 已提交
1208 1209 1210 1211
					return true;
				}

				// match on file
B
Benjamin Pasero 已提交
1212
				if (typeof w.openedFilePath === 'string' && this.isPathEqual(w.openedFilePath, filePath)) {
E
Erich Gamma 已提交
1213 1214 1215 1216 1217 1218 1219 1220
					return true;
				}

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

1221 1222 1223 1224 1225
				// match on extension development path
				if (typeof extensionDevelopmentPath === 'string' && w.extensionDevelopmentPath === extensionDevelopmentPath) {
					return true;
				}

E
Erich Gamma 已提交
1226 1227 1228 1229 1230 1231 1232 1233 1234 1235 1236 1237
				return false;
			});

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

		return null;
	}

	public openNewWindow(): void {
1238
		this.open({ cli: this.environmentService.args, forceNewWindow: true, forceEmpty: true });
E
Erich Gamma 已提交
1239 1240 1241 1242 1243 1244
	}

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

		if (focusedWindow) {
1245
			focusedWindow.sendWhenReady(channel, ...args);
E
Erich Gamma 已提交
1246 1247 1248 1249
		}
	}

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

1255
			w.sendWhenReady(channel, payload);
E
Erich Gamma 已提交
1256 1257 1258
		});
	}

J
Joao Moreno 已提交
1259
	public getFocusedWindow(): VSCodeWindow {
B
Benjamin Pasero 已提交
1260
		const win = BrowserWindow.getFocusedWindow();
E
Erich Gamma 已提交
1261 1262 1263 1264 1265 1266 1267
		if (win) {
			return this.getWindowById(win.id);
		}

		return null;
	}

J
Joao Moreno 已提交
1268
	public getWindowById(windowId: number): VSCodeWindow {
1269
		const res = WindowsManager.WINDOWS.filter(w => w.id === windowId);
E
Erich Gamma 已提交
1270 1271 1272 1273 1274 1275 1276
		if (res && res.length === 1) {
			return res[0];
		}

		return null;
	}

J
Joao Moreno 已提交
1277
	public getWindows(): VSCodeWindow[] {
E
Erich Gamma 已提交
1278 1279 1280 1281 1282 1283 1284
		return WindowsManager.WINDOWS;
	}

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

J
Joao Moreno 已提交
1285
	private onWindowError(vscodeWindow: VSCodeWindow, error: WindowError): void {
E
Erich Gamma 已提交
1286 1287 1288 1289
		console.error(error === WindowError.CRASHED ? '[VS Code]: render process crashed!' : '[VS Code]: detected unresponsive');

		// Unresponsive
		if (error === WindowError.UNRESPONSIVE) {
1290
			dialog.showMessageBox(vscodeWindow.win, {
B
Benjamin Pasero 已提交
1291
				title: product.nameLong,
E
Erich Gamma 已提交
1292
				type: 'warning',
B
Benjamin Pasero 已提交
1293
				buttons: [nls.localize('reopen', "Reopen"), nls.localize('wait', "Keep Waiting"), nls.localize('close', "Close")],
1294
				message: nls.localize('appStalled', "The window is no longer responding"),
B
Benjamin Pasero 已提交
1295
				detail: nls.localize('appStalledDetail', "You can reopen or close the window or keep waiting."),
E
Erich Gamma 已提交
1296
				noLink: true
1297
			}, result => {
E
Erich Gamma 已提交
1298
				if (result === 0) {
1299 1300
					vscodeWindow.reload();
				} else if (result === 2) {
1301
					this.onBeforeWindowClose(vscodeWindow); // 'close' event will not be fired on destroy(), so run it manually
1302
					vscodeWindow.win.destroy(); // make sure to destroy the window as it is unresponsive
E
Erich Gamma 已提交
1303 1304 1305 1306 1307 1308
				}
			});
		}

		// Crashed
		else {
1309
			dialog.showMessageBox(vscodeWindow.win, {
B
Benjamin Pasero 已提交
1310
				title: product.nameLong,
E
Erich Gamma 已提交
1311
				type: 'warning',
B
Benjamin Pasero 已提交
1312
				buttons: [nls.localize('reopen', "Reopen"), nls.localize('close', "Close")],
1313
				message: nls.localize('appCrashed', "The window has crashed"),
B
Benjamin Pasero 已提交
1314
				detail: nls.localize('appCrashedDetail', "We are sorry for the inconvenience! You can reopen the window to continue where you left off."),
E
Erich Gamma 已提交
1315
				noLink: true
1316
			}, result => {
1317 1318 1319
				if (result === 0) {
					vscodeWindow.reload();
				} else if (result === 1) {
1320
					this.onBeforeWindowClose(vscodeWindow); // 'close' event will not be fired on destroy(), so run it manually
1321 1322
					vscodeWindow.win.destroy(); // make sure to destroy the window as it has crashed
				}
E
Erich Gamma 已提交
1323 1324 1325 1326
			});
		}
	}

J
Joao Moreno 已提交
1327 1328
	private onBeforeWindowClose(win: VSCodeWindow): void {
		if (win.readyState !== ReadyState.READY) {
E
Erich Gamma 已提交
1329 1330 1331 1332
			return; // only persist windows that are fully loaded
		}

		// On Window close, update our stored state of this window
B
Benjamin Pasero 已提交
1333
		const state: IWindowState = { workspacePath: win.openedWorkspacePath, uiState: win.serializeWindowState() };
E
Erich Gamma 已提交
1334 1335 1336 1337 1338 1339
		if (win.isPluginDevelopmentHost) {
			this.windowsState.lastPluginDevelopmentHostWindow = state;
		} else {
			this.windowsState.lastActiveWindow = state;

			this.windowsState.openedFolders.forEach(o => {
B
Benjamin Pasero 已提交
1340
				if (this.isPathEqual(o.workspacePath, win.openedWorkspacePath)) {
E
Erich Gamma 已提交
1341 1342 1343 1344 1345 1346
					o.uiState = state.uiState;
				}
			});
		}
	}

J
Joao Moreno 已提交
1347
	private onWindowClosed(win: VSCodeWindow): void {
E
Erich Gamma 已提交
1348 1349 1350 1351 1352

		// Tell window
		win.dispose();

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

		// Emit
J
Joao Moreno 已提交
1357
		this.eventEmitter.emit(EventTypes.CLOSE, win.id);
E
Erich Gamma 已提交
1358
	}
B
Benjamin Pasero 已提交
1359 1360 1361 1362 1363 1364 1365 1366 1367 1368 1369 1370 1371 1372 1373 1374 1375 1376 1377 1378 1379 1380 1381 1382

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