windows.ts 39.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 os from 'os';
B
Benjamin Pasero 已提交
10
import * as fs from 'original-fs';
J
Joao Moreno 已提交
11 12 13
import * as platform from 'vs/base/common/platform';
import * as nls from 'vs/nls';
import * as paths from 'vs/base/common/paths';
14
import * as types from 'vs/base/common/types';
J
Joao Moreno 已提交
15
import * as arrays from 'vs/base/common/arrays';
16
import { assign, mixin } from 'vs/base/common/objects';
J
Joao Moreno 已提交
17
import { EventEmitter } from 'events';
18
import { 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';
J
Johannes Rieken 已提交
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;
E
Erich Gamma 已提交
53 54 55 56
}

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

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

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

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

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

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

J
Joao Moreno 已提交
89
export const IWindowsMainService = createDecorator<IWindowsMainService>('windowsMainService');
J
Joao Moreno 已提交
90

J
Joao Moreno 已提交
91
export interface IWindowsMainService {
92
	_serviceBrand: any;
J
Joao Moreno 已提交
93 94 95

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

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

J
Joao Moreno 已提交
127
export class WindowsManager implements IWindowsMainService, IWindowEventService {
J
Joao Moreno 已提交
128

129
	_serviceBrand: any;
E
Erich Gamma 已提交
130

131
	private static MAX_TOTAL_RECENT_ENTRIES = 100;
132

133
	private static recentPathsListStorageKey = 'openedPathsList';
134
	private static workingDirPickerStorageKey = 'pickerWorkingDir';
E
Erich Gamma 已提交
135 136
	private static windowsStateStorageKey = 'windowsState';

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

J
Joao Moreno 已提交
139
	private eventEmitter = new EventEmitter();
140
	private initialUserEnv: platform.IProcessEnvironment;
E
Erich Gamma 已提交
141 142
	private windowsState: IWindowsState;

S
Sandeep Somavarapu 已提交
143 144 145 146 147 148
	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 已提交
149 150 151
	constructor(
		@IInstantiationService private instantiationService: IInstantiationService,
		@ILogService private logService: ILogService,
J
Joao Moreno 已提交
152
		@IStorageService private storageService: IStorageService,
153
		@IEnvironmentService private environmentService: IEnvironmentService,
J
Joao Moreno 已提交
154
		@ILifecycleService private lifecycleService: ILifecycleService,
B
Benjamin Pasero 已提交
155
		@IUpdateService private updateService: IUpdateService,
D
Daniel Imms 已提交
156
		@IConfigurationService private configurationService: IConfigurationService
157
	) { }
J
Joao Moreno 已提交
158

J
Joao Moreno 已提交
159
	onOpen(clb: (path: IPath) => void): () => void {
J
Joao Moreno 已提交
160 161 162 163 164
		this.eventEmitter.addListener(EventTypes.OPEN, clb);

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

J
Joao Moreno 已提交
165
	onReady(clb: (win: VSCodeWindow) => void): () => void {
J
Joao Moreno 已提交
166 167 168 169 170 171 172 173 174 175 176
		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);
	}

177
	public ready(initialUserEnv: platform.IProcessEnvironment): void {
E
Erich Gamma 已提交
178 179
		this.registerListeners();

180
		this.initialUserEnv = initialUserEnv;
J
Joao Moreno 已提交
181
		this.windowsState = this.storageService.getItem<IWindowsState>(WindowsManager.windowsStateStorageKey) || { openedFolders: [] };
E
Erich Gamma 已提交
182 183 184
	}

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

G
Giorgos Retsinas 已提交
188
			// Mac only event: open new window when we get activated
E
Erich Gamma 已提交
189
			if (!hasVisibleWindows) {
G
Giorgos Retsinas 已提交
190
				this.openNewWindow();
E
Erich Gamma 已提交
191 192 193 194 195 196
			}
		});

		let macOpenFiles: string[] = [];
		let runningTimeout: number = null;
		app.on('open-file', (event: Event, path: string) => {
J
Joao Moreno 已提交
197
			this.logService.log('App#open-file: ', path);
E
Erich Gamma 已提交
198 199 200 201 202 203 204 205 206 207 208 209 210
			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(() => {
211
				this.open({ cli: this.environmentService.args, pathsToOpen: macOpenFiles, preferNewWindow: true /* dropping on the dock prefers to open in a new window */ });
E
Erich Gamma 已提交
212 213 214 215 216 217
				macOpenFiles = [];
				runningTimeout = null;
			}, 100);
		});

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

E
Erich Gamma 已提交
220 221 222
			crashReporter.start(config);
		});

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

B
Benjamin Pasero 已提交
226
			const win = this.getWindowById(windowId);
E
Erich Gamma 已提交
227 228 229 230
			if (win) {
				win.setReady();

				// Event
J
Joao Moreno 已提交
231
				this.eventEmitter.emit(EventTypes.READY, win);
E
Erich Gamma 已提交
232 233 234
			}
		});

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

239 240 241 242
				// Handle specific events on main side
				this.onBroadcast(broadcast.channel, broadcast.payload);

				// Send to windows
243
				if (target) {
B
Benjamin Pasero 已提交
244
					const otherWindowsWithTarget = WindowsManager.WINDOWS.filter(w => w.id !== windowId && typeof w.openedWorkspacePath === 'string');
245 246 247 248 249
					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) {
250 251 252 253 254
						targetWindow.send('vscode:broadcast', broadcast);
					}
				} else {
					this.sendToAll('vscode:broadcast', broadcast, [windowId]);
				}
E
Erich Gamma 已提交
255
			}
256 257
		});

B
Benjamin Pasero 已提交
258
		this.updateService.on('update-downloaded', (update: IUpdate) => {
E
Erich Gamma 已提交
259 260 261 262 263 264 265 266 267
			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 已提交
268
		ipc.on('vscode:update-apply', () => {
J
Joao Moreno 已提交
269
			this.logService.log('IPC#vscode:update-apply');
E
Erich Gamma 已提交
270

B
Benjamin Pasero 已提交
271 272
			if (this.updateService.availableUpdate) {
				this.updateService.availableUpdate.quitAndUpdate();
E
Erich Gamma 已提交
273 274 275
			}
		});

B
Benjamin Pasero 已提交
276
		this.updateService.on('update-not-available', (explicit: boolean) => {
E
Erich Gamma 已提交
277 278 279 280 281 282 283
			this.sendToFocused('vscode:telemetry', { eventName: 'update:notAvailable', data: { explicit } });

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

J
Joao Moreno 已提交
284
		this.updateService.on('update-available', (url: string, version: string) => {
J
Joao Moreno 已提交
285
			if (url) {
J
Joao Moreno 已提交
286
				this.sendToFocused('vscode:update-available', url, version);
J
Joao Moreno 已提交
287 288 289
			}
		});

J
Joao Moreno 已提交
290
		this.lifecycleService.onBeforeQuit(() => {
E
Erich Gamma 已提交
291 292 293 294 295 296 297 298

			// 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 已提交
299
			this.windowsState.openedFolders = WindowsManager.WINDOWS.filter(w => w.readyState === ReadyState.READY && !!w.openedWorkspacePath && !w.isPluginDevelopmentHost).map(w => {
E
Erich Gamma 已提交
300 301 302
				return <IWindowState>{
					workspacePath: w.openedWorkspacePath,
					uiState: w.serializeWindowState()
B
Benjamin Pasero 已提交
303
				};
E
Erich Gamma 已提交
304 305 306 307
			});
		});

		app.on('will-quit', () => {
J
Joao Moreno 已提交
308
			this.storageService.setItem(WindowsManager.windowsStateStorageKey, this.windowsState);
E
Erich Gamma 已提交
309
		});
310 311

		let loggedStartupTimes = false;
J
Joao Moreno 已提交
312
		this.onReady(window => {
313 314 315 316 317 318
			if (loggedStartupTimes) {
				return; // only for the first window
			}

			loggedStartupTimes = true;

B
Benjamin Pasero 已提交
319
			this.logStartupTimes(window);
320
		});
E
Erich Gamma 已提交
321 322
	}

B
Benjamin Pasero 已提交
323 324 325 326 327 328 329 330 331 332 333 334 335 336 337
	private logStartupTimes(window: VSCodeWindow): void {
		let totalmem: number;
		let cpus: { count: number; speed: number; model: string; };

		try {
			totalmem = os.totalmem();

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

B
Benjamin Pasero 已提交
338
		window.send('vscode:telemetry', { eventName: 'startupTime', data: { ellapsed: Date.now() - global.vscodeStart, totalmem, cpus } });
B
Benjamin Pasero 已提交
339 340
	}

341 342 343
	private onBroadcast(event: string, payload: any): void {

		// Theme changes
344 345
		if (event === 'vscode:changeColorTheme' && typeof payload === 'string') {
			this.storageService.setItem(VSCodeWindow.colorThemeStorageKey, payload);
346 347 348
		}
	}

B
Benjamin Pasero 已提交
349
	public reload(win: VSCodeWindow, cli?: ParsedArgs): void {
E
Erich Gamma 已提交
350 351

		// Only reload when the window has not vetoed this
352
		this.lifecycleService.unload(win).done(veto => {
E
Erich Gamma 已提交
353 354 355 356 357 358
			if (!veto) {
				win.reload(cli);
			}
		});
	}

J
Joao Moreno 已提交
359 360
	public open(openConfig: IOpenConfiguration): VSCodeWindow[] {
		let iPathsToOpen: IPath[];
B
Benjamin Pasero 已提交
361
		const usedWindows: VSCodeWindow[] = [];
E
Erich Gamma 已提交
362 363 364

		// Find paths from provided paths if any
		if (openConfig.pathsToOpen && openConfig.pathsToOpen.length > 0) {
365
			iPathsToOpen = openConfig.pathsToOpen.map(pathToOpen => {
B
Benjamin Pasero 已提交
366
				const iPath = this.toIPath(pathToOpen, false, openConfig.cli && openConfig.cli.goto);
E
Erich Gamma 已提交
367 368 369

				// Warn if the requested path to open does not exist
				if (!iPath) {
B
Benjamin Pasero 已提交
370
					const options: Electron.ShowMessageBoxOptions = {
B
Benjamin Pasero 已提交
371
						title: product.nameLong,
E
Erich Gamma 已提交
372 373 374 375 376 377 378
						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 已提交
379
					const activeWindow = BrowserWindow.getFocusedWindow();
E
Erich Gamma 已提交
380
					if (activeWindow) {
381
						dialog.showMessageBox(activeWindow, options);
E
Erich Gamma 已提交
382
					} else {
383
						dialog.showMessageBox(options);
E
Erich Gamma 已提交
384 385 386 387 388 389 390 391 392 393
					}
				}

				return iPath;
			});

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

			if (iPathsToOpen.length === 0) {
394
				return null; // indicate to outside that open failed
E
Erich Gamma 已提交
395 396 397 398 399 400 401 402 403 404
			}
		}

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

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

J
Joao Moreno 已提交
409 410
		let filesToOpen: IPath[] = [];
		let filesToDiff: IPath[] = [];
411 412 413
		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);
414 415

		// Diff mode needs special care
416
		const candidates = iPathsToOpen.filter(iPath => !!iPath.filePath && !iPath.createFilePath);
417 418 419 420 421 422 423
		if (openConfig.diffMode) {
			if (candidates.length === 2) {
				filesToDiff = candidates;
			} else {
				emptyToOpen = [Object.create(null)]; // improper use of diffMode, open empty
			}

424
			foldersToOpen = []; // diff is always in empty workspace
B
Benjamin Pasero 已提交
425
			filesToCreate = []; // diff ignores other files that do not exist
426 427 428 429
		} else {
			filesToOpen = candidates;
		}

J
Joao Moreno 已提交
430
		let configuration: IWindowConfiguration;
E
Erich Gamma 已提交
431

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

B
Benjamin Pasero 已提交
435
			// const the user settings override how files are open in a new window or same window unless we are forced
436 437 438 439 440 441
			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!)
442 443 444 445
					const windowConfig = this.configurationService.getConfiguration<IWindowSettings>('window');
					if (windowConfig && !windowConfig.openFilesInNewWindow) {
						openFilesInNewWindow = false; // do not open in new window if user configured this explicitly
					}
446
				}
E
Erich Gamma 已提交
447 448 449
			}

			// Open Files in last instance if any and flag tells us so
B
Benjamin Pasero 已提交
450
			const lastActiveWindow = this.getLastActiveWindow();
E
Erich Gamma 已提交
451
			if (!openFilesInNewWindow && lastActiveWindow) {
B
Benjamin Pasero 已提交
452
				lastActiveWindow.focus();
453
				lastActiveWindow.ready().then(readyWindow => {
454
					readyWindow.send('vscode:openFiles', { filesToOpen, filesToCreate, filesToDiff });
E
Erich Gamma 已提交
455
				});
456 457

				usedWindows.push(lastActiveWindow);
E
Erich Gamma 已提交
458 459 460 461
			}

			// Otherwise open instance with files
			else {
462
				configuration = this.toConfiguration(this.getWindowUserEnv(openConfig), openConfig.cli, null, filesToOpen, filesToCreate, filesToDiff);
B
Benjamin Pasero 已提交
463
				const browserWindow = this.openInBrowserWindow(configuration, true /* new window */);
464
				usedWindows.push(browserWindow);
E
Erich Gamma 已提交
465 466 467 468 469 470

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

		// Handle folders to open
471
		let openInNewWindow = openConfig.preferNewWindow || openConfig.forceNewWindow;
E
Erich Gamma 已提交
472 473 474
		if (foldersToOpen.length > 0) {

			// Check for existing instances
475
			const windowsOnWorkspacePath = arrays.coalesce(foldersToOpen.map(iPath => this.findWindow(iPath.workspacePath)));
E
Erich Gamma 已提交
476
			if (windowsOnWorkspacePath.length > 0) {
B
Benjamin Pasero 已提交
477
				const browserWindow = windowsOnWorkspacePath[0];
478
				browserWindow.focus(); // just focus one of them
479
				browserWindow.ready().then(readyWindow => {
480
					readyWindow.send('vscode:openFiles', { filesToOpen, filesToCreate, filesToDiff });
E
Erich Gamma 已提交
481 482
				});

483 484
				usedWindows.push(browserWindow);

E
Erich Gamma 已提交
485 486 487
				// Reset these because we handled them
				filesToOpen = [];
				filesToCreate = [];
488
				filesToDiff = [];
E
Erich Gamma 已提交
489

490
				openInNewWindow = true; // any other folders to open must open in new window then
E
Erich Gamma 已提交
491 492 493
			}

			// Open remaining ones
494 495
			foldersToOpen.forEach(folderToOpen => {
				if (windowsOnWorkspacePath.some(win => this.isPathEqual(win.openedWorkspacePath, folderToOpen.workspacePath))) {
E
Erich Gamma 已提交
496 497 498
					return; // ignore folders that are already open
				}

499
				configuration = this.toConfiguration(this.getWindowUserEnv(openConfig), openConfig.cli, folderToOpen.workspacePath, filesToOpen, filesToCreate, filesToDiff);
B
Benjamin Pasero 已提交
500
				const browserWindow = this.openInBrowserWindow(configuration, openInNewWindow, openInNewWindow ? void 0 : openConfig.windowToUse);
501
				usedWindows.push(browserWindow);
E
Erich Gamma 已提交
502 503 504 505

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

508
				openInNewWindow = true; // any other folders to open must open in new window then
E
Erich Gamma 已提交
509 510 511 512 513 514
			});
		}

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

519
				openInNewWindow = true; // any other folders to open must open in new window then
E
Erich Gamma 已提交
520 521 522
			});
		}

523
		// Remember in recent document list (unless this opens for extension development)
524
		// Also do not add paths when files are opened for diffing, only if opened individually
525
		if (!usedWindows.some(w => w.isPluginDevelopmentHost) && !openConfig.cli.diff) {
526 527 528 529 530 531 532
			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 已提交
533 534

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

537
		return arrays.distinct(usedWindows);
E
Erich Gamma 已提交
538 539
	}

540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612
	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 };
	}

613
	private getWindowUserEnv(openConfig: IOpenConfiguration): platform.IProcessEnvironment {
614 615 616
		return assign({}, this.initialUserEnv, openConfig.userEnv || {});
	}

E
Erich Gamma 已提交
617 618 619 620 621
	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.
622
		let res = WindowsManager.WINDOWS.filter(w => w.config && this.isPathEqual(w.config.extensionDevelopmentPath, openConfig.cli.extensionDevelopmentPath));
E
Erich Gamma 已提交
623 624
		if (res && res.length === 1) {
			this.reload(res[0], openConfig.cli);
B
Benjamin Pasero 已提交
625
			res[0].focus(); // make sure it gets focus and is restored
E
Erich Gamma 已提交
626 627 628 629

			return;
		}

630
		// Fill in previously opened workspace unless an explicit path is provided and we are not unit testing
B
Benjamin Pasero 已提交
631
		if (openConfig.cli._.length === 0 && !openConfig.cli.extensionTestsPath) {
B
Benjamin Pasero 已提交
632
			const workspaceToOpen = this.windowsState.lastPluginDevelopmentHostWindow && this.windowsState.lastPluginDevelopmentHostWindow.workspacePath;
E
Erich Gamma 已提交
633
			if (workspaceToOpen) {
B
Benjamin Pasero 已提交
634
				openConfig.cli._ = [workspaceToOpen];
E
Erich Gamma 已提交
635 636 637 638
			}
		}

		// Make sure we are not asked to open a path that is already opened
B
Benjamin Pasero 已提交
639 640
		if (openConfig.cli._.length > 0) {
			res = WindowsManager.WINDOWS.filter(w => w.openedWorkspacePath && openConfig.cli._.indexOf(w.openedWorkspacePath) >= 0);
E
Erich Gamma 已提交
641
			if (res.length) {
B
Benjamin Pasero 已提交
642
				openConfig.cli._ = [];
E
Erich Gamma 已提交
643 644 645 646
			}
		}

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

B
Benjamin Pasero 已提交
650
	private toConfiguration(userEnv: platform.IProcessEnvironment, cli: ParsedArgs, workspacePath?: string, filesToOpen?: IPath[], filesToCreate?: IPath[], filesToDiff?: IPath[]): IWindowConfiguration {
B
Benjamin Pasero 已提交
651
		const configuration: IWindowConfiguration = mixin({}, cli); // inherit all properties from CLI
652
		configuration.appRoot = this.environmentService.appRoot;
B
Benjamin Pasero 已提交
653 654
		configuration.execPath = process.execPath;
		configuration.userEnv = userEnv;
E
Erich Gamma 已提交
655 656 657
		configuration.workspacePath = workspacePath;
		configuration.filesToOpen = filesToOpen;
		configuration.filesToCreate = filesToCreate;
658
		configuration.filesToDiff = filesToDiff;
E
Erich Gamma 已提交
659 660 661 662

		return configuration;
	}

J
Joao Moreno 已提交
663
	private toIPath(anyPath: string, ignoreFileNotFound?: boolean, gotoLineMode?: boolean): IPath {
E
Erich Gamma 已提交
664 665 666 667
		if (!anyPath) {
			return null;
		}

668
		let parsedPath: IPathWithLineAndColumn;
E
Erich Gamma 已提交
669
		if (gotoLineMode) {
J
Joao Moreno 已提交
670
			parsedPath = parseLineAndColumnAware(anyPath);
E
Erich Gamma 已提交
671 672 673
			anyPath = parsedPath.path;
		}

B
Benjamin Pasero 已提交
674
		const candidate = path.normalize(anyPath);
E
Erich Gamma 已提交
675
		try {
B
Benjamin Pasero 已提交
676
			const candidateStat = fs.statSync(candidate);
E
Erich Gamma 已提交
677 678 679 680 681
			if (candidateStat) {
				return candidateStat.isFile() ?
					{
						filePath: candidate,
						lineNumber: gotoLineMode ? parsedPath.line : void 0,
682
						columnNumber: gotoLineMode ? parsedPath.column : void 0
E
Erich Gamma 已提交
683 684 685 686 687 688 689 690 691 692 693 694
					} :
					{ 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 已提交
695
	private cliToPaths(cli: ParsedArgs, ignoreFileNotFound?: boolean): IPath[] {
E
Erich Gamma 已提交
696 697 698

		// Check for pass in candidate or last opened path
		let candidates: string[] = [];
B
Benjamin Pasero 已提交
699 700
		if (cli._.length > 0) {
			candidates = cli._;
E
Erich Gamma 已提交
701 702 703 704
		}

		// No path argument, check settings for what to do now
		else {
705 706 707 708
			let reopenFolders: string;
			if (this.lifecycleService.wasUpdated) {
				reopenFolders = ReopenFoldersSetting.ALL; // always reopen all folders when an update was applied
			} else {
709 710
				const windowConfig = this.configurationService.getConfiguration<IWindowSettings>('window');
				reopenFolders = (windowConfig && windowConfig.reopenFolders) || ReopenFoldersSetting.ONE;
711 712
			}

B
Benjamin Pasero 已提交
713
			const lastActiveFolder = this.windowsState.lastActiveWindow && this.windowsState.lastActiveWindow.workspacePath;
E
Erich Gamma 已提交
714 715

			// Restore all
716
			if (reopenFolders === ReopenFoldersSetting.ALL) {
B
Benjamin Pasero 已提交
717
				const lastOpenedFolders = this.windowsState.openedFolders.map(o => o.workspacePath);
E
Erich Gamma 已提交
718 719 720 721 722 723 724 725 726 727 728

				// 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
729
			else if (lastActiveFolder && (reopenFolders === ReopenFoldersSetting.ONE || reopenFolders !== ReopenFoldersSetting.NONE)) {
E
Erich Gamma 已提交
730 731 732 733
				candidates.push(lastActiveFolder);
			}
		}

734
		const iPaths = candidates.map(candidate => this.toIPath(candidate, ignoreFileNotFound, cli.goto)).filter(path => !!path);
E
Erich Gamma 已提交
735 736 737 738 739 740 741 742
		if (iPaths.length > 0) {
			return iPaths;
		}

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

J
Joao Moreno 已提交
743 744
	private openInBrowserWindow(configuration: IWindowConfiguration, forceNewWindow?: boolean, windowToUse?: VSCodeWindow): VSCodeWindow {
		let vscodeWindow: VSCodeWindow;
E
Erich Gamma 已提交
745 746 747 748 749

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

			if (vscodeWindow) {
B
Benjamin Pasero 已提交
750
				vscodeWindow.focus();
E
Erich Gamma 已提交
751 752 753 754 755
			}
		}

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

J
Joao Moreno 已提交
758
			vscodeWindow = this.instantiationService.createInstance(VSCodeWindow, {
759
				state: this.getNewWindowState(configuration),
760
				extensionDevelopmentPath: configuration.extensionDevelopmentPath,
761
				allowFullscreen: this.lifecycleService.wasUpdated || (windowConfig && windowConfig.restoreFullscreen)
762 763
			});

E
Erich Gamma 已提交
764 765 766
			WindowsManager.WINDOWS.push(vscodeWindow);

			// Window Events
767 768
			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));
769 770
			vscodeWindow.win.webContents.on('crashed', () => this.onWindowError(vscodeWindow, WindowError.CRASHED));
			vscodeWindow.win.on('unresponsive', () => this.onWindowError(vscodeWindow, WindowError.UNRESPONSIVE));
E
Erich Gamma 已提交
771 772
			vscodeWindow.win.on('close', () => this.onBeforeWindowClose(vscodeWindow));
			vscodeWindow.win.on('closed', () => this.onWindowClosed(vscodeWindow));
S
Sandeep Somavarapu 已提交
773
			vscodeWindow.win.on('focus', () => this._onFocus.fire(vscodeWindow.id));
E
Erich Gamma 已提交
774

S
Sandeep Somavarapu 已提交
775
			this._onNewWindow.fire(vscodeWindow.id);
E
Erich Gamma 已提交
776
			// Lifecycle
J
Joao Moreno 已提交
777
			this.lifecycleService.registerWindow(vscodeWindow);
E
Erich Gamma 已提交
778 779 780 781 782 783 784
		}

		// 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 已提交
785
			const currentWindowConfig = vscodeWindow.config;
A
Alex Dima 已提交
786 787
			if (!configuration.extensionDevelopmentPath && currentWindowConfig && !!currentWindowConfig.extensionDevelopmentPath) {
				configuration.extensionDevelopmentPath = currentWindowConfig.extensionDevelopmentPath;
B
Benjamin Pasero 已提交
788
				configuration.verbose = currentWindowConfig.verbose;
789
				configuration.debugBrkPluginHost = currentWindowConfig.debugBrkPluginHost;
790
				configuration.debugPluginHost = currentWindowConfig.debugPluginHost;
791
				configuration['extensions-dir'] = currentWindowConfig['extensions-dir'];
E
Erich Gamma 已提交
792 793 794 795
			}
		}

		// Only load when the window has not vetoed this
796
		this.lifecycleService.unload(vscodeWindow).done(veto => {
E
Erich Gamma 已提交
797 798 799 800 801 802
			if (!veto) {

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

		return vscodeWindow;
E
Erich Gamma 已提交
805 806
	}

J
Joao Moreno 已提交
807
	private getNewWindowState(configuration: IWindowConfiguration): ISingleWindowState {
E
Erich Gamma 已提交
808 809

		// plugin development host Window - load from stored settings if any
A
Alex Dima 已提交
810
		if (!!configuration.extensionDevelopmentPath && this.windowsState.lastPluginDevelopmentHostWindow) {
E
Erich Gamma 已提交
811 812 813 814 815
			return this.windowsState.lastPluginDevelopmentHostWindow.uiState;
		}

		// Known Folder - load from stored settings if any
		if (configuration.workspacePath) {
B
Benjamin Pasero 已提交
816
			const stateForWorkspace = this.windowsState.openedFolders.filter(o => this.isPathEqual(o.workspacePath, configuration.workspacePath)).map(o => o.uiState);
E
Erich Gamma 已提交
817 818 819 820 821 822
			if (stateForWorkspace.length) {
				return stateForWorkspace[0];
			}
		}

		// First Window
B
Benjamin Pasero 已提交
823
		const lastActive = this.getLastActiveWindow();
E
Erich Gamma 已提交
824 825 826 827 828 829 830 831 832
		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
833
		let displayToUse: Electron.Display;
B
Benjamin Pasero 已提交
834
		const displays = screen.getAllDisplays();
E
Erich Gamma 已提交
835 836 837 838 839 840 841 842 843 844 845

		// 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 已提交
846
				const cursorPoint = screen.getCursorScreenPoint();
E
Erich Gamma 已提交
847 848 849 850 851 852 853 854 855 856 857 858 859 860
				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 已提交
861
		const defaultState = defaultWindowState();
E
Erich Gamma 已提交
862 863 864 865 866 867
		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 已提交
868
	private ensureNoOverlap(state: ISingleWindowState): ISingleWindowState {
E
Erich Gamma 已提交
869 870 871 872
		if (WindowsManager.WINDOWS.length === 0) {
			return state;
		}

873 874
		const existingWindowBounds = WindowsManager.WINDOWS.map(win => win.getBounds());
		while (existingWindowBounds.some(b => b.x === state.x || b.y === state.y)) {
E
Erich Gamma 已提交
875 876 877 878 879 880 881
			state.x += 30;
			state.y += 30;
		}

		return state;
	}

882
	public openFileFolderPicker(forceNewWindow?: boolean): void {
883
		this.doPickAndOpen({ pickFolders: true, pickFiles: true, forceNewWindow });
884 885
	}

886 887
	public openFilePicker(forceNewWindow?: boolean, path?: string): void {
		this.doPickAndOpen({ pickFiles: true, forceNewWindow, path });
888 889 890
	}

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

894 895 896 897 898 899 900 901 902 903 904 905 906 907 908 909
	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');
	}

910
	private doPickAndOpen(options: INativeOpenDialogOptions): void {
911
		this.getFileOrFolderPaths(options, (paths: string[]) => {
E
Erich Gamma 已提交
912
			if (paths && paths.length) {
913
				this.open({ cli: this.environmentService.args, pathsToOpen: paths, forceNewWindow: options.forceNewWindow });
E
Erich Gamma 已提交
914 915 916 917
			}
		});
	}

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

B
Benjamin Pasero 已提交
922
		let pickerProperties: ('openFile' | 'openDirectory' | 'multiSelections' | 'createDirectory')[];
923
		if (options.pickFiles && options.pickFolders) {
E
Erich Gamma 已提交
924 925
			pickerProperties = ['multiSelections', 'openDirectory', 'openFile', 'createDirectory'];
		} else {
926
			pickerProperties = ['multiSelections', options.pickFolders ? 'openDirectory' : 'openFile', 'createDirectory'];
E
Erich Gamma 已提交
927 928
		}

929
		dialog.showOpenDialog(focussedWindow && focussedWindow.win, {
E
Erich Gamma 已提交
930 931
			defaultPath: workingDir,
			properties: pickerProperties
932
		}, paths => {
E
Erich Gamma 已提交
933 934 935
			if (paths && paths.length > 0) {

				// Remember path in storage for next time
J
Joao Moreno 已提交
936
				this.storageService.setItem(WindowsManager.workingDirPickerStorageKey, path.dirname(paths[0]));
E
Erich Gamma 已提交
937 938 939 940 941 942 943 944 945

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

B
Benjamin Pasero 已提交
946
	public focusLastActive(cli: ParsedArgs): VSCodeWindow {
B
Benjamin Pasero 已提交
947
		const lastActive = this.getLastActiveWindow();
E
Erich Gamma 已提交
948
		if (lastActive) {
B
Benjamin Pasero 已提交
949
			lastActive.focus();
950 951

			return lastActive;
E
Erich Gamma 已提交
952 953 954
		}

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

		return res && res[0];
E
Erich Gamma 已提交
959 960
	}

J
Joao Moreno 已提交
961
	public getLastActiveWindow(): VSCodeWindow {
E
Erich Gamma 已提交
962
		if (WindowsManager.WINDOWS.length) {
963 964
			const lastFocussedDate = Math.max.apply(Math, WindowsManager.WINDOWS.map(w => w.lastFocusTime));
			const res = WindowsManager.WINDOWS.filter(w => w.lastFocusTime === lastFocussedDate);
E
Erich Gamma 已提交
965 966 967 968 969 970 971 972
			if (res && res.length) {
				return res[0];
			}
		}

		return null;
	}

J
Joao Moreno 已提交
973
	public findWindow(workspacePath: string, filePath?: string, extensionDevelopmentPath?: string): VSCodeWindow {
E
Erich Gamma 已提交
974 975 976
		if (WindowsManager.WINDOWS.length) {

			// Sort the last active window to the front of the array of windows to test
B
Benjamin Pasero 已提交
977 978
			const windowsToTest = WindowsManager.WINDOWS.slice(0);
			const lastActiveWindow = this.getLastActiveWindow();
E
Erich Gamma 已提交
979 980 981 982 983 984
			if (lastActiveWindow) {
				windowsToTest.splice(windowsToTest.indexOf(lastActiveWindow), 1);
				windowsToTest.unshift(lastActiveWindow);
			}

			// Find it
985
			const res = windowsToTest.filter(w => {
E
Erich Gamma 已提交
986 987

				// match on workspace
988
				if (typeof w.openedWorkspacePath === 'string' && (this.isPathEqual(w.openedWorkspacePath, workspacePath))) {
E
Erich Gamma 已提交
989 990 991 992
					return true;
				}

				// match on file
B
Benjamin Pasero 已提交
993
				if (typeof w.openedFilePath === 'string' && this.isPathEqual(w.openedFilePath, filePath)) {
E
Erich Gamma 已提交
994 995 996 997 998 999 1000 1001
					return true;
				}

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

1002 1003 1004 1005 1006
				// match on extension development path
				if (typeof extensionDevelopmentPath === 'string' && w.extensionDevelopmentPath === extensionDevelopmentPath) {
					return true;
				}

E
Erich Gamma 已提交
1007 1008 1009 1010 1011 1012 1013 1014 1015 1016 1017 1018
				return false;
			});

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

		return null;
	}

	public openNewWindow(): void {
1019
		this.open({ cli: this.environmentService.args, forceNewWindow: true, forceEmpty: true });
E
Erich Gamma 已提交
1020 1021 1022 1023 1024 1025
	}

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

		if (focusedWindow) {
1026
			focusedWindow.sendWhenReady(channel, ...args);
E
Erich Gamma 已提交
1027 1028 1029 1030
		}
	}

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

1036
			w.sendWhenReady(channel, payload);
E
Erich Gamma 已提交
1037 1038 1039
		});
	}

J
Joao Moreno 已提交
1040
	public getFocusedWindow(): VSCodeWindow {
B
Benjamin Pasero 已提交
1041
		const win = BrowserWindow.getFocusedWindow();
E
Erich Gamma 已提交
1042 1043 1044 1045 1046 1047 1048
		if (win) {
			return this.getWindowById(win.id);
		}

		return null;
	}

J
Joao Moreno 已提交
1049
	public getWindowById(windowId: number): VSCodeWindow {
1050
		const res = WindowsManager.WINDOWS.filter(w => w.id === windowId);
E
Erich Gamma 已提交
1051 1052 1053 1054 1055 1056 1057
		if (res && res.length === 1) {
			return res[0];
		}

		return null;
	}

J
Joao Moreno 已提交
1058
	public getWindows(): VSCodeWindow[] {
E
Erich Gamma 已提交
1059 1060 1061 1062 1063 1064 1065
		return WindowsManager.WINDOWS;
	}

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

J
Joao Moreno 已提交
1066
	private onWindowError(vscodeWindow: VSCodeWindow, error: WindowError): void {
E
Erich Gamma 已提交
1067 1068 1069 1070
		console.error(error === WindowError.CRASHED ? '[VS Code]: render process crashed!' : '[VS Code]: detected unresponsive');

		// Unresponsive
		if (error === WindowError.UNRESPONSIVE) {
1071
			dialog.showMessageBox(vscodeWindow.win, {
B
Benjamin Pasero 已提交
1072
				title: product.nameLong,
E
Erich Gamma 已提交
1073
				type: 'warning',
B
Benjamin Pasero 已提交
1074
				buttons: [nls.localize('reopen', "Reopen"), nls.localize('wait', "Keep Waiting"), nls.localize('close', "Close")],
1075
				message: nls.localize('appStalled', "The window is no longer responding"),
B
Benjamin Pasero 已提交
1076
				detail: nls.localize('appStalledDetail', "You can reopen or close the window or keep waiting."),
E
Erich Gamma 已提交
1077
				noLink: true
1078
			}, result => {
E
Erich Gamma 已提交
1079
				if (result === 0) {
1080 1081
					vscodeWindow.reload();
				} else if (result === 2) {
1082
					this.onBeforeWindowClose(vscodeWindow); // 'close' event will not be fired on destroy(), so run it manually
1083
					vscodeWindow.win.destroy(); // make sure to destroy the window as it is unresponsive
E
Erich Gamma 已提交
1084 1085 1086 1087 1088 1089
				}
			});
		}

		// Crashed
		else {
1090
			dialog.showMessageBox(vscodeWindow.win, {
B
Benjamin Pasero 已提交
1091
				title: product.nameLong,
E
Erich Gamma 已提交
1092
				type: 'warning',
B
Benjamin Pasero 已提交
1093
				buttons: [nls.localize('reopen', "Reopen"), nls.localize('close', "Close")],
1094
				message: nls.localize('appCrashed', "The window has crashed"),
B
Benjamin Pasero 已提交
1095
				detail: nls.localize('appCrashedDetail', "We are sorry for the inconvenience! You can reopen the window to continue where you left off."),
E
Erich Gamma 已提交
1096
				noLink: true
1097
			}, result => {
1098 1099 1100
				if (result === 0) {
					vscodeWindow.reload();
				} else if (result === 1) {
1101
					this.onBeforeWindowClose(vscodeWindow); // 'close' event will not be fired on destroy(), so run it manually
1102 1103
					vscodeWindow.win.destroy(); // make sure to destroy the window as it has crashed
				}
E
Erich Gamma 已提交
1104 1105 1106 1107
			});
		}
	}

J
Joao Moreno 已提交
1108 1109
	private onBeforeWindowClose(win: VSCodeWindow): void {
		if (win.readyState !== ReadyState.READY) {
E
Erich Gamma 已提交
1110 1111 1112 1113
			return; // only persist windows that are fully loaded
		}

		// On Window close, update our stored state of this window
B
Benjamin Pasero 已提交
1114
		const state: IWindowState = { workspacePath: win.openedWorkspacePath, uiState: win.serializeWindowState() };
E
Erich Gamma 已提交
1115 1116 1117 1118 1119 1120
		if (win.isPluginDevelopmentHost) {
			this.windowsState.lastPluginDevelopmentHostWindow = state;
		} else {
			this.windowsState.lastActiveWindow = state;

			this.windowsState.openedFolders.forEach(o => {
B
Benjamin Pasero 已提交
1121
				if (this.isPathEqual(o.workspacePath, win.openedWorkspacePath)) {
E
Erich Gamma 已提交
1122 1123 1124 1125 1126 1127
					o.uiState = state.uiState;
				}
			});
		}
	}

J
Joao Moreno 已提交
1128
	private onWindowClosed(win: VSCodeWindow): void {
E
Erich Gamma 已提交
1129 1130 1131 1132 1133

		// Tell window
		win.dispose();

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

		// Emit
J
Joao Moreno 已提交
1138
		this.eventEmitter.emit(EventTypes.CLOSE, win.id);
E
Erich Gamma 已提交
1139
	}
B
Benjamin Pasero 已提交
1140 1141 1142 1143 1144 1145 1146 1147 1148 1149 1150 1151 1152 1153 1154 1155 1156 1157 1158 1159 1160 1161 1162 1163

	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;
	}
J
Joao Moreno 已提交
1164 1165 1166 1167 1168 1169 1170 1171 1172 1173 1174 1175 1176 1177 1178 1179 1180 1181

	toggleMenuBar(windowId: number): void {
		// Update in settings
		const menuBarHidden = this.storageService.getItem(VSCodeWindow.menuBarHiddenKey, false);
		const newMenuBarHidden = !menuBarHidden;
		this.storageService.setItem(VSCodeWindow.menuBarHiddenKey, newMenuBarHidden);

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

		// Inform user if menu bar is now hidden
		if (newMenuBarHidden) {
			const vscodeWindow = this.getWindowById(windowId);
			if (vscodeWindow) {
				vscodeWindow.send('vscode:showInfoMessage', nls.localize('hiddenMenuBar', "You can still access the menu bar by pressing the **Alt** key."));
			}
		}
	}
1182
}