windows.ts 42.4 KB
Newer Older
E
Erich Gamma 已提交
1 2 3 4 5 6 7
/*---------------------------------------------------------------------------------------------
 *  Copyright (c) Microsoft Corporation. All rights reserved.
 *  Licensed under the MIT License. See License.txt in the project root for license information.
 *--------------------------------------------------------------------------------------------*/

'use strict';

J
Joao Moreno 已提交
8
import * as path from 'path';
B
Benjamin Pasero 已提交
9
import * as 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';
D
Daniel Imms 已提交
16
import { IBackupMainService } from 'vs/platform/backup/common/backup';
17
import { trim } from 'vs/base/common/strings';
J
Joao Moreno 已提交
18
import { IEnvironmentService, ParsedArgs } from 'vs/platform/environment/common/environment';
J
Joao Moreno 已提交
19
import { IStorageService } from 'vs/code/electron-main/storage';
B
Benjamin Pasero 已提交
20
import { IPath, VSCodeWindow, IWindowConfiguration, IWindowState as ISingleWindowState, defaultWindowState, ReadyState } from 'vs/code/electron-main/window';
21
import { ipcMain as ipc, app, screen, BrowserWindow, dialog } from 'electron';
22
import { IPathWithLineAndColumn, parseLineAndColumnAware } from 'vs/code/electron-main/paths';
23
import { ILifecycleService, UnloadReason } from 'vs/code/electron-main/lifecycle';
24
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
B
Benjamin Pasero 已提交
25
import { ILogService } from 'vs/code/electron-main/log';
26
import { getPathLabel } from 'vs/base/common/labels';
J
Johannes Rieken 已提交
27
import { createDecorator, IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
J
Joao Moreno 已提交
28
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
29
import { IWindowSettings } from 'vs/platform/windows/common/windows';
30
import CommonEvent, { Emitter } from 'vs/base/common/event';
B
Benjamin Pasero 已提交
31
import product from 'vs/platform/product';
32
import Uri from 'vs/base/common/uri';
E
Erich Gamma 已提交
33 34 35 36 37 38 39

enum WindowError {
	UNRESPONSIVE,
	CRASHED
}

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

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

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

62
export interface IRecentPathsList {
E
Erich Gamma 已提交
63 64 65 66
	folders: string[];
	files: string[];
}

67 68 69 70 71
interface ILogEntry {
	severity: string;
	arguments: any;
}

72 73 74
interface INativeOpenDialogOptions {
	pickFolders?: boolean;
	pickFiles?: boolean;
75 76
	path?: string;
	forceNewWindow?: boolean;
77
	window?: VSCodeWindow;
78 79
}

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

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

J
Joao Moreno 已提交
88
export interface IWindowsMainService {
89
	_serviceBrand: any;
J
Joao Moreno 已提交
90 91

	// events
92 93
	onWindowReady: CommonEvent<VSCodeWindow>;
	onWindowClose: CommonEvent<number>;
B
Benjamin Pasero 已提交
94
	onPathsOpen: CommonEvent<IPath[]>;
95
	onRecentPathsChange: CommonEvent<void>;
J
Joao Moreno 已提交
96 97

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

J
Joao Moreno 已提交
125
export class WindowsManager implements IWindowsMainService {
J
Joao Moreno 已提交
126

127
	_serviceBrand: any;
E
Erich Gamma 已提交
128

129
	private static MAX_TOTAL_RECENT_ENTRIES = 100;
130

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

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

137
	private initialUserEnv: platform.IProcessEnvironment;
E
Erich Gamma 已提交
138 139
	private windowsState: IWindowsState;

140 141 142 143 144 145 146 147 148
	private _onRecentPathsChange = new Emitter<void>();
	onRecentPathsChange: CommonEvent<void> = this._onRecentPathsChange.event;

	private _onWindowReady = new Emitter<VSCodeWindow>();
	onWindowReady: CommonEvent<VSCodeWindow> = this._onWindowReady.event;

	private _onWindowClose = new Emitter<number>();
	onWindowClose: CommonEvent<number> = this._onWindowClose.event;

B
Benjamin Pasero 已提交
149 150
	private _onPathsOpen = new Emitter<IPath[]>();
	onPathsOpen: CommonEvent<IPath> = this._onPathsOpen.event;
151

J
Joao Moreno 已提交
152 153 154
	constructor(
		@IInstantiationService private instantiationService: IInstantiationService,
		@ILogService private logService: ILogService,
J
Joao Moreno 已提交
155
		@IStorageService private storageService: IStorageService,
156
		@IEnvironmentService private environmentService: IEnvironmentService,
B
Benjamin Pasero 已提交
157
		@ILifecycleService private lifecycleService: ILifecycleService,
158
		@IBackupMainService private backupService: IBackupMainService,
159
		@IConfigurationService private configurationService: IConfigurationService,
J
Joao Moreno 已提交
160
		@ITelemetryService private telemetryService: ITelemetryService
161
	) { }
J
Joao Moreno 已提交
162

163
	public ready(initialUserEnv: platform.IProcessEnvironment): void {
E
Erich Gamma 已提交
164 165
		this.registerListeners();

166
		this.initialUserEnv = initialUserEnv;
J
Joao Moreno 已提交
167
		this.windowsState = this.storageService.getItem<IWindowsState>(WindowsManager.windowsStateStorageKey) || { openedFolders: [] };
168 169

		this.updateWindowsJumpList();
E
Erich Gamma 已提交
170 171 172
	}

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

G
Giorgos Retsinas 已提交
176
			// Mac only event: open new window when we get activated
E
Erich Gamma 已提交
177
			if (!hasVisibleWindows) {
G
Giorgos Retsinas 已提交
178
				this.openNewWindow();
E
Erich Gamma 已提交
179 180 181 182 183 184
			}
		});

		let macOpenFiles: string[] = [];
		let runningTimeout: number = null;
		app.on('open-file', (event: Event, path: string) => {
J
Joao Moreno 已提交
185
			this.logService.log('App#open-file: ', path);
E
Erich Gamma 已提交
186 187 188 189 190 191 192 193 194 195 196 197 198
			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(() => {
199
				this.open({ cli: this.environmentService.args, pathsToOpen: macOpenFiles, preferNewWindow: true /* dropping on the dock prefers to open in a new window */ });
E
Erich Gamma 已提交
200 201 202 203 204
				macOpenFiles = [];
				runningTimeout = null;
			}, 100);
		});

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

B
Benjamin Pasero 已提交
208
			const win = this.getWindowById(windowId);
E
Erich Gamma 已提交
209 210 211 212
			if (win) {
				win.setReady();

				// Event
213
				this._onWindowReady.fire(win);
E
Erich Gamma 已提交
214 215 216
			}
		});

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

221 222 223 224
				// Handle specific events on main side
				this.onBroadcast(broadcast.channel, broadcast.payload);

				// Send to windows
225
				if (target) {
B
Benjamin Pasero 已提交
226
					const otherWindowsWithTarget = WindowsManager.WINDOWS.filter(w => w.id !== windowId && typeof w.openedWorkspacePath === 'string');
227 228 229 230 231
					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) {
232 233 234 235 236
						targetWindow.send('vscode:broadcast', broadcast);
					}
				} else {
					this.sendToAll('vscode:broadcast', broadcast, [windowId]);
				}
E
Erich Gamma 已提交
237
			}
238 239
		});

J
Joao Moreno 已提交
240
		this.lifecycleService.onBeforeQuit(() => {
E
Erich Gamma 已提交
241 242 243 244 245 246 247 248

			// 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 已提交
249
			this.windowsState.openedFolders = WindowsManager.WINDOWS.filter(w => w.readyState === ReadyState.READY && !!w.openedWorkspacePath && !w.isPluginDevelopmentHost).map(w => {
E
Erich Gamma 已提交
250 251 252
				return <IWindowState>{
					workspacePath: w.openedWorkspacePath,
					uiState: w.serializeWindowState()
B
Benjamin Pasero 已提交
253
				};
E
Erich Gamma 已提交
254 255 256 257
			});
		});

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

261 262
		// Update jump list when recent paths change
		this.onRecentPathsChange(() => this.updateWindowsJumpList());
E
Erich Gamma 已提交
263 264
	}

265 266 267
	private onBroadcast(event: string, payload: any): void {

		// Theme changes
268 269
		if (event === 'vscode:changeColorTheme' && typeof payload === 'string') {
			this.storageService.setItem(VSCodeWindow.colorThemeStorageKey, payload);
270 271 272
		}
	}

B
Benjamin Pasero 已提交
273
	public reload(win: VSCodeWindow, cli?: ParsedArgs): void {
E
Erich Gamma 已提交
274 275

		// Only reload when the window has not vetoed this
276
		this.lifecycleService.unload(win, UnloadReason.RELOAD).done(veto => {
E
Erich Gamma 已提交
277 278 279 280 281 282
			if (!veto) {
				win.reload(cli);
			}
		});
	}

J
Joao Moreno 已提交
283 284
	public open(openConfig: IOpenConfiguration): VSCodeWindow[] {
		let iPathsToOpen: IPath[];
B
Benjamin Pasero 已提交
285
		const usedWindows: VSCodeWindow[] = [];
E
Erich Gamma 已提交
286 287 288

		// Find paths from provided paths if any
		if (openConfig.pathsToOpen && openConfig.pathsToOpen.length > 0) {
289
			iPathsToOpen = openConfig.pathsToOpen.map(pathToOpen => {
B
Benjamin Pasero 已提交
290
				const iPath = this.toIPath(pathToOpen, false, openConfig.cli && openConfig.cli.goto);
E
Erich Gamma 已提交
291 292 293

				// Warn if the requested path to open does not exist
				if (!iPath) {
B
Benjamin Pasero 已提交
294
					const options: Electron.ShowMessageBoxOptions = {
B
Benjamin Pasero 已提交
295
						title: product.nameLong,
E
Erich Gamma 已提交
296 297 298 299 300 301 302
						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 已提交
303
					const activeWindow = BrowserWindow.getFocusedWindow();
E
Erich Gamma 已提交
304
					if (activeWindow) {
305
						dialog.showMessageBox(activeWindow, options);
E
Erich Gamma 已提交
306
					} else {
307
						dialog.showMessageBox(options);
E
Erich Gamma 已提交
308 309 310 311 312 313 314 315 316 317
					}
				}

				return iPath;
			});

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

			if (iPathsToOpen.length === 0) {
318
				return null; // indicate to outside that open failed
E
Erich Gamma 已提交
319 320 321 322 323 324 325 326 327 328
			}
		}

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

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

333 334
		let foldersToOpen = arrays.distinct(iPathsToOpen.filter(iPath => iPath.workspacePath && !iPath.filePath).map(iPath => iPath.workspacePath), folder => platform.isLinux ? folder : folder.toLowerCase()); // prevent duplicates
		let foldersToRestore = (openConfig.initialStartup && !openConfig.cli.extensionDevelopmentPath) ? this.backupService.getWorkspaceBackupPaths() : [];
335 336 337
		let filesToOpen: IPath[] = [];
		let filesToDiff: IPath[] = [];
		let emptyToOpen = iPathsToOpen.filter(iPath => !iPath.workspacePath && !iPath.filePath);
338
		let emptyToRestore = (openConfig.initialStartup && !openConfig.cli.extensionDevelopmentPath) ? this.backupService.getEmptyWorkspaceBackupWindowIds() : [];
339 340 341 342 343 344 345 346 347 348 349
		let filesToCreate = iPathsToOpen.filter(iPath => !!iPath.filePath && iPath.createFilePath);

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

350 351 352
			foldersToOpen = []; 	// diff is always in empty workspace
			foldersToRestore = [];	// diff is always in empty workspace
			filesToCreate = []; 	// diff ignores other files that do not exist
353 354 355 356
		} else {
			filesToOpen = candidates;
		}

357 358
		let openInNewWindow = openConfig.preferNewWindow || openConfig.forceNewWindow;

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

B
Benjamin Pasero 已提交
362
			// const the user settings override how files are open in a new window or same window unless we are forced
363 364 365 366 367
			let openFilesInNewWindow: boolean;
			if (openConfig.forceNewWindow) {
				openFilesInNewWindow = true;
			} else {
				openFilesInNewWindow = openConfig.preferNewWindow;
368
				if (openFilesInNewWindow && !openConfig.cli.extensionDevelopmentPath) { // can be overriden via settings (not for PDE though!)
369 370 371 372
					const windowConfig = this.configurationService.getConfiguration<IWindowSettings>('window');
					if (windowConfig && !windowConfig.openFilesInNewWindow) {
						openFilesInNewWindow = false; // do not open in new window if user configured this explicitly
					}
373
				}
E
Erich Gamma 已提交
374 375 376
			}

			// Open Files in last instance if any and flag tells us so
B
Benjamin Pasero 已提交
377
			const lastActiveWindow = this.getLastActiveWindow();
E
Erich Gamma 已提交
378
			if (!openFilesInNewWindow && lastActiveWindow) {
B
Benjamin Pasero 已提交
379
				lastActiveWindow.focus();
380
				lastActiveWindow.ready().then(readyWindow => {
381
					readyWindow.send('vscode:openFiles', { filesToOpen, filesToCreate, filesToDiff });
E
Erich Gamma 已提交
382
				});
383 384

				usedWindows.push(lastActiveWindow);
E
Erich Gamma 已提交
385 386 387 388
			}

			// Otherwise open instance with files
			else {
389
				const configuration = this.toConfiguration(openConfig, null, null, filesToOpen, filesToCreate, filesToDiff);
B
Benjamin Pasero 已提交
390
				const browserWindow = this.openInBrowserWindow(configuration, true /* new window */);
391
				usedWindows.push(browserWindow);
E
Erich Gamma 已提交
392

393
				openInNewWindow = true; // any other folders to open must open in new window then
E
Erich Gamma 已提交
394
			}
395 396 397 398 399

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

402 403 404
		// Handle folders to open (instructed and to restore)
		let allFoldersToOpen = arrays.distinct([...foldersToOpen, ...foldersToRestore], folder => platform.isLinux ? folder : folder.toLowerCase()); // prevent duplicates
		if (allFoldersToOpen.length > 0) {
E
Erich Gamma 已提交
405 406

			// Check for existing instances
407
			const windowsOnWorkspacePath = arrays.coalesce(allFoldersToOpen.map(folderToOpen => this.findWindow(folderToOpen)));
E
Erich Gamma 已提交
408
			if (windowsOnWorkspacePath.length > 0) {
B
Benjamin Pasero 已提交
409
				const browserWindow = windowsOnWorkspacePath[0];
410
				browserWindow.focus(); // just focus one of them
411
				browserWindow.ready().then(readyWindow => {
412
					readyWindow.send('vscode:openFiles', { filesToOpen, filesToCreate, filesToDiff });
E
Erich Gamma 已提交
413 414
				});

415 416
				usedWindows.push(browserWindow);

E
Erich Gamma 已提交
417 418 419
				// Reset these because we handled them
				filesToOpen = [];
				filesToCreate = [];
420
				filesToDiff = [];
E
Erich Gamma 已提交
421

422
				openInNewWindow = true; // any other folders to open must open in new window then
E
Erich Gamma 已提交
423 424 425
			}

			// Open remaining ones
426 427
			allFoldersToOpen.forEach(folderToOpen => {
				if (windowsOnWorkspacePath.some(win => this.isPathEqual(win.openedWorkspacePath, folderToOpen))) {
E
Erich Gamma 已提交
428 429 430
					return; // ignore folders that are already open
				}

431
				const configuration = this.toConfiguration(openConfig, null, folderToOpen, filesToOpen, filesToCreate, filesToDiff);
B
Benjamin Pasero 已提交
432
				const browserWindow = this.openInBrowserWindow(configuration, openInNewWindow, openInNewWindow ? void 0 : openConfig.windowToUse);
433
				usedWindows.push(browserWindow);
E
Erich Gamma 已提交
434 435 436 437

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

440
				openInNewWindow = true; // any other folders to open must open in new window then
E
Erich Gamma 已提交
441 442 443 444
			});
		}

		// Handle empty
445 446 447 448 449 450 451 452 453 454 455 456 457 458
		if (emptyToRestore.length > 0) {
			// TODO: There's an extra empty workspace opening when restoring an empty workspace (sometimes)
			emptyToRestore.forEach(vscodeWindowId => {
				const configuration = this.toConfiguration(openConfig);
				configuration.vscodeWindowId = vscodeWindowId;
				const browserWindow = this.openInBrowserWindow(configuration, openInNewWindow, openInNewWindow ? void 0 : openConfig.windowToUse);
				usedWindows.push(browserWindow);

				openInNewWindow = true; // any other folders to open must open in new window then
			});
		}
		// TODO: Is this handling correct?
		// Only open empty if no empty workspaces were restored
		else if (emptyToOpen.length > 0) {
E
Erich Gamma 已提交
459
			emptyToOpen.forEach(() => {
460
				const configuration = this.toConfiguration(openConfig);
B
Benjamin Pasero 已提交
461
				const browserWindow = this.openInBrowserWindow(configuration, openInNewWindow, openInNewWindow ? void 0 : openConfig.windowToUse);
462
				usedWindows.push(browserWindow);
E
Erich Gamma 已提交
463

464
				openInNewWindow = true; // any other folders to open must open in new window then
E
Erich Gamma 已提交
465 466 467
			});
		}

468
		// Remember in recent document list (unless this opens for extension development)
469
		// Also do not add paths when files are opened for diffing, only if opened individually
470
		if (!usedWindows.some(w => w.isPluginDevelopmentHost) && !openConfig.cli.diff) {
471 472
			const recentPaths: { path: string; isFile?: boolean; }[] = [];

473 474 475
			iPathsToOpen.forEach(iPath => {
				if (iPath.filePath || iPath.workspacePath) {
					app.addRecentDocument(iPath.filePath || iPath.workspacePath);
476
					recentPaths.push({ path: iPath.filePath || iPath.workspacePath, isFile: !!iPath.filePath });
477 478
				}
			});
E
Erich Gamma 已提交
479

480 481 482 483
			if (recentPaths.length) {
				this.addToRecentPathsList(recentPaths);
			}
		}
E
Erich Gamma 已提交
484

485
		// Register new paths for backup
486
		if (!openConfig.cli.extensionDevelopmentPath) {
487 488
			this.backupService.pushWorkspaceBackupPathsSync(iPathsToOpen.filter(p => p.workspacePath).map(p => Uri.file(p.workspacePath)));
		}
489

490
		// Emit events
B
Benjamin Pasero 已提交
491
		this._onPathsOpen.fire(iPathsToOpen);
492

493
		return arrays.distinct(usedWindows);
E
Erich Gamma 已提交
494 495
	}

496 497
	public addToRecentPathsList(paths: { path: string; isFile?: boolean; }[]): void {
		if (!paths || !paths.length) {
498 499 500 501
			return;
		}

		const mru = this.getRecentPathsList();
502 503
		paths.forEach(p => {
			const {path, isFile} = p;
504

505 506 507 508 509 510 511 512 513 514 515 516
			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);
		});
517 518

		this.storageService.setItem(WindowsManager.recentPathsListStorageKey, mru);
519
		this._onRecentPathsChange.fire();
520 521
	}

522 523 524 525 526 527 528 529 530 531
	public removeFromRecentPathsList(path: string): void;
	public removeFromRecentPathsList(paths: string[]): void;
	public removeFromRecentPathsList(arg1: any): void {
		let paths: string[];
		if (Array.isArray(arg1)) {
			paths = arg1;
		} else {
			paths = [arg1];
		}

532
		const mru = this.getRecentPathsList();
533
		let update = false;
534

535 536 537 538 539 540
		paths.forEach(path => {
			let index = mru.files.indexOf(path);
			if (index >= 0) {
				mru.files.splice(index, 1);
				update = true;
			}
541

542 543 544 545 546 547
			index = mru.folders.indexOf(path);
			if (index >= 0) {
				mru.folders.splice(index, 1);
				update = true;
			}
		});
548

549 550
		if (update) {
			this.storageService.setItem(WindowsManager.recentPathsListStorageKey, mru);
551
			this._onRecentPathsChange.fire();
552
		}
553 554 555 556 557
	}

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

		// Event
		this._onRecentPathsChange.fire();
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
	}

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

594
	private getWindowUserEnv(openConfig: IOpenConfiguration): platform.IProcessEnvironment {
595 596 597
		return assign({}, this.initialUserEnv, openConfig.userEnv || {});
	}

E
Erich Gamma 已提交
598 599 600 601 602
	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.
603
		let res = WindowsManager.WINDOWS.filter(w => w.config && this.isPathEqual(w.config.extensionDevelopmentPath, openConfig.cli.extensionDevelopmentPath));
E
Erich Gamma 已提交
604 605
		if (res && res.length === 1) {
			this.reload(res[0], openConfig.cli);
B
Benjamin Pasero 已提交
606
			res[0].focus(); // make sure it gets focus and is restored
E
Erich Gamma 已提交
607 608 609 610

			return;
		}

611
		// Fill in previously opened workspace unless an explicit path is provided and we are not unit testing
B
Benjamin Pasero 已提交
612
		if (openConfig.cli._.length === 0 && !openConfig.cli.extensionTestsPath) {
B
Benjamin Pasero 已提交
613
			const workspaceToOpen = this.windowsState.lastPluginDevelopmentHostWindow && this.windowsState.lastPluginDevelopmentHostWindow.workspacePath;
E
Erich Gamma 已提交
614
			if (workspaceToOpen) {
B
Benjamin Pasero 已提交
615
				openConfig.cli._ = [workspaceToOpen];
E
Erich Gamma 已提交
616 617 618 619
			}
		}

		// Make sure we are not asked to open a path that is already opened
B
Benjamin Pasero 已提交
620 621
		if (openConfig.cli._.length > 0) {
			res = WindowsManager.WINDOWS.filter(w => w.openedWorkspacePath && openConfig.cli._.indexOf(w.openedWorkspacePath) >= 0);
E
Erich Gamma 已提交
622
			if (res.length) {
B
Benjamin Pasero 已提交
623
				openConfig.cli._ = [];
E
Erich Gamma 已提交
624 625 626 627
			}
		}

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

631
	private toConfiguration(config: IOpenConfiguration, vscodeWindowId?: string, workspacePath?: string, filesToOpen?: IPath[], filesToCreate?: IPath[], filesToDiff?: IPath[]): IWindowConfiguration {
632
		const configuration: IWindowConfiguration = mixin({}, config.cli); // inherit all properties from CLI
633
		configuration.appRoot = this.environmentService.appRoot;
B
Benjamin Pasero 已提交
634
		configuration.execPath = process.execPath;
635 636
		configuration.userEnv = this.getWindowUserEnv(config);
		configuration.isInitialStartup = config.initialStartup;
637
		configuration.vscodeWindowId = vscodeWindowId;
E
Erich Gamma 已提交
638 639 640
		configuration.workspacePath = workspacePath;
		configuration.filesToOpen = filesToOpen;
		configuration.filesToCreate = filesToCreate;
641
		configuration.filesToDiff = filesToDiff;
E
Erich Gamma 已提交
642 643 644 645

		return configuration;
	}

J
Joao Moreno 已提交
646
	private toIPath(anyPath: string, ignoreFileNotFound?: boolean, gotoLineMode?: boolean): IPath {
E
Erich Gamma 已提交
647 648 649 650
		if (!anyPath) {
			return null;
		}

651
		let parsedPath: IPathWithLineAndColumn;
E
Erich Gamma 已提交
652
		if (gotoLineMode) {
J
Joao Moreno 已提交
653
			parsedPath = parseLineAndColumnAware(anyPath);
E
Erich Gamma 已提交
654 655 656
			anyPath = parsedPath.path;
		}

B
Benjamin Pasero 已提交
657
		const candidate = path.normalize(anyPath);
E
Erich Gamma 已提交
658
		try {
B
Benjamin Pasero 已提交
659
			const candidateStat = fs.statSync(candidate);
E
Erich Gamma 已提交
660 661 662 663 664
			if (candidateStat) {
				return candidateStat.isFile() ?
					{
						filePath: candidate,
						lineNumber: gotoLineMode ? parsedPath.line : void 0,
665
						columnNumber: gotoLineMode ? parsedPath.column : void 0
E
Erich Gamma 已提交
666 667 668 669
					} :
					{ workspacePath: candidate };
			}
		} catch (error) {
670 671
			this.removeFromRecentPathsList(candidate); // since file does not seem to exist anymore, remove from recent

E
Erich Gamma 已提交
672 673 674 675 676 677 678 679
			if (ignoreFileNotFound) {
				return { filePath: candidate, createFilePath: true }; // assume this is a file that does not yet exist
			}
		}

		return null;
	}

B
Benjamin Pasero 已提交
680
	private cliToPaths(cli: ParsedArgs, ignoreFileNotFound?: boolean): IPath[] {
E
Erich Gamma 已提交
681 682 683

		// Check for pass in candidate or last opened path
		let candidates: string[] = [];
B
Benjamin Pasero 已提交
684 685
		if (cli._.length > 0) {
			candidates = cli._;
E
Erich Gamma 已提交
686 687 688 689
		}

		// No path argument, check settings for what to do now
		else {
690 691 692 693
			let reopenFolders: string;
			if (this.lifecycleService.wasUpdated) {
				reopenFolders = ReopenFoldersSetting.ALL; // always reopen all folders when an update was applied
			} else {
694 695
				const windowConfig = this.configurationService.getConfiguration<IWindowSettings>('window');
				reopenFolders = (windowConfig && windowConfig.reopenFolders) || ReopenFoldersSetting.ONE;
696 697
			}

B
Benjamin Pasero 已提交
698
			const lastActiveFolder = this.windowsState.lastActiveWindow && this.windowsState.lastActiveWindow.workspacePath;
E
Erich Gamma 已提交
699 700

			// Restore all
701
			if (reopenFolders === ReopenFoldersSetting.ALL) {
B
Benjamin Pasero 已提交
702
				const lastOpenedFolders = this.windowsState.openedFolders.map(o => o.workspacePath);
E
Erich Gamma 已提交
703 704 705 706 707 708 709 710 711 712 713

				// 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
714
			else if (lastActiveFolder && (reopenFolders === ReopenFoldersSetting.ONE || reopenFolders !== ReopenFoldersSetting.NONE)) {
E
Erich Gamma 已提交
715 716 717 718
				candidates.push(lastActiveFolder);
			}
		}

719
		const iPaths = candidates.map(candidate => this.toIPath(candidate, ignoreFileNotFound, cli.goto)).filter(path => !!path);
E
Erich Gamma 已提交
720 721 722 723 724 725 726 727
		if (iPaths.length > 0) {
			return iPaths;
		}

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

J
Joao Moreno 已提交
728 729
	private openInBrowserWindow(configuration: IWindowConfiguration, forceNewWindow?: boolean, windowToUse?: VSCodeWindow): VSCodeWindow {
		let vscodeWindow: VSCodeWindow;
E
Erich Gamma 已提交
730 731 732 733 734

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

			if (vscodeWindow) {
B
Benjamin Pasero 已提交
735
				vscodeWindow.focus();
E
Erich Gamma 已提交
736 737 738 739 740
			}
		}

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

J
Joao Moreno 已提交
743
			vscodeWindow = this.instantiationService.createInstance(VSCodeWindow, {
744
				state: this.getNewWindowState(configuration),
745
				extensionDevelopmentPath: configuration.extensionDevelopmentPath,
B
Benjamin Pasero 已提交
746
				allowFullscreen: this.lifecycleService.wasUpdated || (windowConfig && windowConfig.restoreFullscreen),
747 748
				titleBarStyle: windowConfig ? windowConfig.titleBarStyle : void 0,
				vscodeWindowId: configuration.vscodeWindowId
749 750
			});

751 752 753 754 755 756
			configuration.vscodeWindowId = vscodeWindow.vscodeWindowId;
			if (!configuration.extensionDevelopmentPath) {
				// TODO: Cover closing a folder/existing window case
				this.backupService.pushEmptyWorkspaceBackupWindowIdSync(configuration.vscodeWindowId);
			}

E
Erich Gamma 已提交
757 758 759
			WindowsManager.WINDOWS.push(vscodeWindow);

			// Window Events
760 761
			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));
762 763
			vscodeWindow.win.webContents.on('crashed', () => this.onWindowError(vscodeWindow, WindowError.CRASHED));
			vscodeWindow.win.on('unresponsive', () => this.onWindowError(vscodeWindow, WindowError.UNRESPONSIVE));
E
Erich Gamma 已提交
764 765 766 767
			vscodeWindow.win.on('close', () => this.onBeforeWindowClose(vscodeWindow));
			vscodeWindow.win.on('closed', () => this.onWindowClosed(vscodeWindow));

			// Lifecycle
J
Joao Moreno 已提交
768
			this.lifecycleService.registerWindow(vscodeWindow);
E
Erich Gamma 已提交
769 770 771 772 773 774 775
		}

		// 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 已提交
776
			const currentWindowConfig = vscodeWindow.config;
A
Alex Dima 已提交
777 778
			if (!configuration.extensionDevelopmentPath && currentWindowConfig && !!currentWindowConfig.extensionDevelopmentPath) {
				configuration.extensionDevelopmentPath = currentWindowConfig.extensionDevelopmentPath;
B
Benjamin Pasero 已提交
779
				configuration.verbose = currentWindowConfig.verbose;
780
				configuration.debugBrkPluginHost = currentWindowConfig.debugBrkPluginHost;
781
				configuration.debugPluginHost = currentWindowConfig.debugPluginHost;
782
				configuration['extensions-dir'] = currentWindowConfig['extensions-dir'];
E
Erich Gamma 已提交
783 784 785 786
			}
		}

		// Only load when the window has not vetoed this
787
		this.lifecycleService.unload(vscodeWindow, UnloadReason.LOAD).done(veto => {
E
Erich Gamma 已提交
788 789 790 791 792 793
			if (!veto) {

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

		return vscodeWindow;
E
Erich Gamma 已提交
796 797
	}

J
Joao Moreno 已提交
798
	private getNewWindowState(configuration: IWindowConfiguration): ISingleWindowState {
E
Erich Gamma 已提交
799 800

		// plugin development host Window - load from stored settings if any
A
Alex Dima 已提交
801
		if (!!configuration.extensionDevelopmentPath && this.windowsState.lastPluginDevelopmentHostWindow) {
E
Erich Gamma 已提交
802 803 804 805 806
			return this.windowsState.lastPluginDevelopmentHostWindow.uiState;
		}

		// Known Folder - load from stored settings if any
		if (configuration.workspacePath) {
B
Benjamin Pasero 已提交
807
			const stateForWorkspace = this.windowsState.openedFolders.filter(o => this.isPathEqual(o.workspacePath, configuration.workspacePath)).map(o => o.uiState);
E
Erich Gamma 已提交
808 809 810 811 812 813
			if (stateForWorkspace.length) {
				return stateForWorkspace[0];
			}
		}

		// First Window
B
Benjamin Pasero 已提交
814
		const lastActive = this.getLastActiveWindow();
E
Erich Gamma 已提交
815 816 817 818 819 820 821 822 823
		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
824
		let displayToUse: Electron.Display;
B
Benjamin Pasero 已提交
825
		const displays = screen.getAllDisplays();
E
Erich Gamma 已提交
826 827 828 829 830 831 832 833 834 835 836

		// 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 已提交
837
				const cursorPoint = screen.getCursorScreenPoint();
E
Erich Gamma 已提交
838 839 840 841 842 843 844 845 846 847 848 849 850 851
				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 已提交
852
		const defaultState = defaultWindowState();
E
Erich Gamma 已提交
853 854 855 856 857 858
		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 已提交
859
	private ensureNoOverlap(state: ISingleWindowState): ISingleWindowState {
E
Erich Gamma 已提交
860 861 862 863
		if (WindowsManager.WINDOWS.length === 0) {
			return state;
		}

864 865
		const existingWindowBounds = WindowsManager.WINDOWS.map(win => win.getBounds());
		while (existingWindowBounds.some(b => b.x === state.x || b.y === state.y)) {
E
Erich Gamma 已提交
866 867 868 869 870 871 872
			state.x += 30;
			state.y += 30;
		}

		return state;
	}

873
	public openFileFolderPicker(forceNewWindow?: boolean): void {
874
		this.doPickAndOpen({ pickFolders: true, pickFiles: true, forceNewWindow });
875 876
	}

877 878
	public openFilePicker(forceNewWindow?: boolean, path?: string, window?: VSCodeWindow): void {
		this.doPickAndOpen({ pickFiles: true, forceNewWindow, path, window });
879 880
	}

881 882
	public openFolderPicker(forceNewWindow?: boolean, window?: VSCodeWindow): void {
		this.doPickAndOpen({ pickFolders: true, forceNewWindow, window });
E
Erich Gamma 已提交
883 884
	}

885 886 887 888 889 890 891 892 893 894 895 896 897 898 899 900
	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');
	}

901
	private doPickAndOpen(options: INativeOpenDialogOptions): void {
902
		this.getFileOrFolderPaths(options, (paths: string[]) => {
E
Erich Gamma 已提交
903
			if (paths && paths.length) {
904
				this.open({ cli: this.environmentService.args, pathsToOpen: paths, forceNewWindow: options.forceNewWindow });
E
Erich Gamma 已提交
905 906 907 908
			}
		});
	}

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

B
Benjamin Pasero 已提交
913
		let pickerProperties: ('openFile' | 'openDirectory' | 'multiSelections' | 'createDirectory')[];
914
		if (options.pickFiles && options.pickFolders) {
E
Erich Gamma 已提交
915 916
			pickerProperties = ['multiSelections', 'openDirectory', 'openFile', 'createDirectory'];
		} else {
917
			pickerProperties = ['multiSelections', options.pickFolders ? 'openDirectory' : 'openFile', 'createDirectory'];
E
Erich Gamma 已提交
918 919
		}

920
		dialog.showOpenDialog(focussedWindow && focussedWindow.win, {
E
Erich Gamma 已提交
921 922
			defaultPath: workingDir,
			properties: pickerProperties
923
		}, paths => {
E
Erich Gamma 已提交
924 925 926
			if (paths && paths.length > 0) {

				// Remember path in storage for next time
J
Joao Moreno 已提交
927
				this.storageService.setItem(WindowsManager.workingDirPickerStorageKey, path.dirname(paths[0]));
E
Erich Gamma 已提交
928 929 930 931 932 933 934 935 936

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

B
Benjamin Pasero 已提交
937
	public focusLastActive(cli: ParsedArgs): VSCodeWindow {
B
Benjamin Pasero 已提交
938
		const lastActive = this.getLastActiveWindow();
E
Erich Gamma 已提交
939
		if (lastActive) {
B
Benjamin Pasero 已提交
940
			lastActive.focus();
941 942

			return lastActive;
E
Erich Gamma 已提交
943 944 945
		}

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

		return res && res[0];
E
Erich Gamma 已提交
950 951
	}

J
Joao Moreno 已提交
952
	public getLastActiveWindow(): VSCodeWindow {
E
Erich Gamma 已提交
953
		if (WindowsManager.WINDOWS.length) {
954 955
			const lastFocussedDate = Math.max.apply(Math, WindowsManager.WINDOWS.map(w => w.lastFocusTime));
			const res = WindowsManager.WINDOWS.filter(w => w.lastFocusTime === lastFocussedDate);
E
Erich Gamma 已提交
956 957 958 959 960 961 962 963
			if (res && res.length) {
				return res[0];
			}
		}

		return null;
	}

J
Joao Moreno 已提交
964
	public findWindow(workspacePath: string, filePath?: string, extensionDevelopmentPath?: string): VSCodeWindow {
E
Erich Gamma 已提交
965 966 967
		if (WindowsManager.WINDOWS.length) {

			// Sort the last active window to the front of the array of windows to test
B
Benjamin Pasero 已提交
968 969
			const windowsToTest = WindowsManager.WINDOWS.slice(0);
			const lastActiveWindow = this.getLastActiveWindow();
E
Erich Gamma 已提交
970 971 972 973 974 975
			if (lastActiveWindow) {
				windowsToTest.splice(windowsToTest.indexOf(lastActiveWindow), 1);
				windowsToTest.unshift(lastActiveWindow);
			}

			// Find it
976
			const res = windowsToTest.filter(w => {
E
Erich Gamma 已提交
977 978

				// match on workspace
979
				if (typeof w.openedWorkspacePath === 'string' && (this.isPathEqual(w.openedWorkspacePath, workspacePath))) {
E
Erich Gamma 已提交
980 981 982 983
					return true;
				}

				// match on file
B
Benjamin Pasero 已提交
984
				if (typeof w.openedFilePath === 'string' && this.isPathEqual(w.openedFilePath, filePath)) {
E
Erich Gamma 已提交
985 986 987 988 989 990 991 992
					return true;
				}

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

993 994 995 996 997
				// match on extension development path
				if (typeof extensionDevelopmentPath === 'string' && w.extensionDevelopmentPath === extensionDevelopmentPath) {
					return true;
				}

E
Erich Gamma 已提交
998 999 1000 1001 1002 1003 1004 1005 1006 1007 1008 1009
				return false;
			});

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

		return null;
	}

	public openNewWindow(): void {
1010
		this.open({ cli: this.environmentService.args, forceNewWindow: true, forceEmpty: true });
E
Erich Gamma 已提交
1011 1012 1013 1014 1015 1016
	}

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

		if (focusedWindow) {
1017
			focusedWindow.sendWhenReady(channel, ...args);
E
Erich Gamma 已提交
1018 1019 1020 1021
		}
	}

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

1027
			w.sendWhenReady(channel, payload);
E
Erich Gamma 已提交
1028 1029 1030
		});
	}

J
Joao Moreno 已提交
1031
	public getFocusedWindow(): VSCodeWindow {
B
Benjamin Pasero 已提交
1032
		const win = BrowserWindow.getFocusedWindow();
E
Erich Gamma 已提交
1033 1034 1035 1036 1037 1038 1039
		if (win) {
			return this.getWindowById(win.id);
		}

		return null;
	}

J
Joao Moreno 已提交
1040
	public getWindowById(windowId: number): VSCodeWindow {
1041
		const res = WindowsManager.WINDOWS.filter(w => w.id === windowId);
E
Erich Gamma 已提交
1042 1043 1044 1045 1046 1047 1048
		if (res && res.length === 1) {
			return res[0];
		}

		return null;
	}

J
Joao Moreno 已提交
1049
	public getWindows(): VSCodeWindow[] {
E
Erich Gamma 已提交
1050 1051 1052 1053 1054 1055 1056
		return WindowsManager.WINDOWS;
	}

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

J
Joao Moreno 已提交
1057
	private onWindowError(vscodeWindow: VSCodeWindow, error: WindowError): void {
E
Erich Gamma 已提交
1058 1059 1060 1061
		console.error(error === WindowError.CRASHED ? '[VS Code]: render process crashed!' : '[VS Code]: detected unresponsive');

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

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

J
Joao Moreno 已提交
1099 1100
	private onBeforeWindowClose(win: VSCodeWindow): void {
		if (win.readyState !== ReadyState.READY) {
E
Erich Gamma 已提交
1101 1102 1103 1104
			return; // only persist windows that are fully loaded
		}

		// On Window close, update our stored state of this window
B
Benjamin Pasero 已提交
1105
		const state: IWindowState = { workspacePath: win.openedWorkspacePath, uiState: win.serializeWindowState() };
E
Erich Gamma 已提交
1106 1107 1108 1109 1110 1111
		if (win.isPluginDevelopmentHost) {
			this.windowsState.lastPluginDevelopmentHostWindow = state;
		} else {
			this.windowsState.lastActiveWindow = state;

			this.windowsState.openedFolders.forEach(o => {
B
Benjamin Pasero 已提交
1112
				if (this.isPathEqual(o.workspacePath, win.openedWorkspacePath)) {
E
Erich Gamma 已提交
1113 1114 1115 1116 1117 1118
					o.uiState = state.uiState;
				}
			});
		}
	}

J
Joao Moreno 已提交
1119
	private onWindowClosed(win: VSCodeWindow): void {
E
Erich Gamma 已提交
1120 1121 1122 1123 1124

		// Tell window
		win.dispose();

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

		// Emit
1129
		this._onWindowClose.fire(win.id);
E
Erich Gamma 已提交
1130
	}
B
Benjamin Pasero 已提交
1131 1132 1133 1134 1135 1136 1137 1138 1139 1140 1141 1142 1143 1144 1145 1146 1147 1148 1149 1150 1151 1152 1153 1154

	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 已提交
1155

1156
	public toggleMenuBar(windowId: number): void {
J
Joao Moreno 已提交
1157 1158 1159 1160 1161 1162 1163 1164 1165 1166 1167 1168 1169 1170 1171 1172
		// 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."));
			}
		}
	}
1173

B
Benjamin Pasero 已提交
1174
	private updateWindowsJumpList(): void {
1175 1176 1177 1178 1179 1180 1181 1182 1183 1184 1185 1186 1187 1188 1189 1190 1191 1192 1193 1194 1195 1196 1197 1198 1199 1200 1201 1202 1203 1204 1205 1206 1207 1208 1209 1210 1211 1212 1213 1214 1215 1216 1217 1218 1219 1220 1221 1222 1223 1224 1225 1226 1227 1228 1229 1230 1231 1232 1233 1234
		if (!platform.isWindows) {
			return; // only on windows
		}

		const jumpList: Electron.JumpListCategory[] = [];

		// Tasks
		jumpList.push({
			type: 'tasks',
			items: [
				{
					type: 'task',
					title: nls.localize('newWindow', "New Window"),
					description: nls.localize('newWindowDesc', "Opens a new window"),
					program: process.execPath,
					args: '-n', // force new window
					iconPath: process.execPath,
					iconIndex: 0
				}
			]
		});

		// Recent Folders
		if (this.getRecentPathsList().folders.length > 0) {

			// The user might have meanwhile removed items from the jump list and we have to respect that
			// so we need to update our list of recent paths with the choice of the user to not add them again
			// Also: Windows will not show our custom category at all if there is any entry which was removed
			// by the user! See https://github.com/Microsoft/vscode/issues/15052
			this.removeFromRecentPathsList(app.getJumpListSettings().removedItems.map(r => trim(r.args, '"')));

			// Add entries
			jumpList.push({
				type: 'custom',
				name: nls.localize('recentFolders', "Recent Folders"),
				items: this.getRecentPathsList().folders.slice(0, 7 /* limit number of entries here */).map(folder => {
					return <Electron.JumpListItem>{
						type: 'task',
						title: path.basename(folder) || folder, // use the base name to show shorter entries in the list
						description: nls.localize('folderDesc', "{0} {1}", path.basename(folder), getPathLabel(path.dirname(folder))),
						program: process.execPath,
						args: `"${folder}"`, // open folder (use quotes to support paths with whitespaces)
						iconPath: 'explorer.exe', // simulate folder icon
						iconIndex: 0
					};
				}).filter(i => !!i)
			});
		}

		// Recent
		jumpList.push({
			type: 'recent' // this enables to show files in the "recent" category
		});

		try {
			app.setJumpList(jumpList);
		} catch (error) {
			this.logService.log('#setJumpList', error); // since setJumpList is relatively new API, make sure to guard for errors
		}
	}
1235 1236 1237 1238 1239 1240 1241 1242 1243 1244 1245 1246 1247 1248 1249 1250 1251

	public quit(): void {

		// If the user selected to exit from an extension development host window, do not quit, but just
		// close the window unless this is the last window that is opened.
		const vscodeWindow = this.getFocusedWindow();
		if (vscodeWindow && vscodeWindow.isPluginDevelopmentHost && this.getWindowCount() > 1) {
			vscodeWindow.win.close();
		}

		// Otherwise: normal quit
		else {
			setTimeout(() => {
				app.quit();
			}, 10 /* delay to unwind callback stack (IPC) */);
		}
	}
1252
}