windows.ts 43.5 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 } from 'vs/platform/instantiation/common/instantiation';
28
import { IWindowSettings } from 'vs/platform/windows/common/windows';
29
import CommonEvent, { Emitter } from 'vs/base/common/event';
30
import product from 'vs/platform/node/product';
E
Erich Gamma 已提交
31 32 33 34 35 36

enum WindowError {
	UNRESPONSIVE,
	CRASHED
}

37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54
export enum OpenContext {

	// opening when running from the command line
	CLI,

	// macOS only: opening from the dock (also when opening files to a running instance from desktop)
	DOCK,

	// opening from the main application window
	MENU,

	// opening from a file or folder dialog
	DIALOG,

	// any other way of opening
	OTHER
}

E
Erich Gamma 已提交
55
export interface IOpenConfiguration {
56
	context: OpenContext;
B
Benjamin Pasero 已提交
57
	cli: ParsedArgs;
58
	userEnv?: platform.IProcessEnvironment;
E
Erich Gamma 已提交
59
	pathsToOpen?: string[];
60
	preferNewWindow?: boolean;
E
Erich Gamma 已提交
61
	forceNewWindow?: boolean;
62
	forceReuseWindow?: boolean;
E
Erich Gamma 已提交
63
	forceEmpty?: boolean;
J
Joao Moreno 已提交
64
	windowToUse?: VSCodeWindow;
65
	diffMode?: boolean;
B
Benjamin Pasero 已提交
66
	initialStartup?: boolean;
E
Erich Gamma 已提交
67 68 69 70
}

interface IWindowState {
	workspacePath?: string;
J
Joao Moreno 已提交
71
	uiState: ISingleWindowState;
E
Erich Gamma 已提交
72 73 74 75 76 77 78 79
}

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

80
export interface IRecentPathsList {
E
Erich Gamma 已提交
81 82 83 84
	folders: string[];
	files: string[];
}

85 86 87
interface INativeOpenDialogOptions {
	pickFolders?: boolean;
	pickFiles?: boolean;
88 89
	path?: string;
	forceNewWindow?: boolean;
90
	window?: VSCodeWindow;
91 92
}

93 94 95 96 97 98
const ReopenFoldersSetting = {
	ALL: 'all',
	ONE: 'one',
	NONE: 'none'
};

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

J
Joao Moreno 已提交
101
export interface IWindowsMainService {
102
	_serviceBrand: any;
J
Joao Moreno 已提交
103 104

	// events
105 106
	onWindowReady: CommonEvent<VSCodeWindow>;
	onWindowClose: CommonEvent<number>;
B
Benjamin Pasero 已提交
107
	onPathsOpen: CommonEvent<IPath[]>;
108
	onRecentPathsChange: CommonEvent<void>;
J
Joao Moreno 已提交
109 110

	// methods
111
	ready(initialUserEnv: platform.IProcessEnvironment): void;
B
Benjamin Pasero 已提交
112
	reload(win: VSCodeWindow, cli?: ParsedArgs): void;
J
Joao Moreno 已提交
113
	open(openConfig: IOpenConfiguration): VSCodeWindow[];
B
Benjamin Pasero 已提交
114
	openExtensionDevelopmentHostWindow(openConfig: IOpenConfiguration): void;
J
Joao Moreno 已提交
115
	openFileFolderPicker(forceNewWindow?: boolean): void;
116 117
	openFilePicker(forceNewWindow?: boolean, path?: string, window?: VSCodeWindow): void;
	openFolderPicker(forceNewWindow?: boolean, window?: VSCodeWindow): void;
118
	openAccessibilityOptions(): void;
119
	focusLastActive(cli: ParsedArgs, context: OpenContext): VSCodeWindow;
J
Joao Moreno 已提交
120 121
	getLastActiveWindow(): VSCodeWindow;
	findWindow(workspacePath: string, filePath?: string, extensionDevelopmentPath?: string): VSCodeWindow;
122
	openNewWindow(context: OpenContext): void;
J
Joao Moreno 已提交
123 124
	sendToFocused(channel: string, ...args: any[]): void;
	sendToAll(channel: string, payload: any, windowIdsToIgnore?: number[]): void;
J
Joao Moreno 已提交
125 126 127
	getFocusedWindow(): VSCodeWindow;
	getWindowById(windowId: number): VSCodeWindow;
	getWindows(): VSCodeWindow[];
J
Joao Moreno 已提交
128
	getWindowCount(): number;
129
	addToRecentPathsList(paths: { path: string; isFile?: boolean; }[]): void;
J
Joao Moreno 已提交
130
	getRecentPathsList(workspacePath?: string, filesToOpen?: IPath[]): IRecentPathsList;
B
Benjamin Pasero 已提交
131 132
	removeFromRecentPathsList(path: string): void;
	removeFromRecentPathsList(paths: string[]): void;
133
	clearRecentPathsList(): void;
J
Joao Moreno 已提交
134
	toggleMenuBar(windowId: number): void;
135
	quit(): void;
J
Joao Moreno 已提交
136 137
}

J
Joao Moreno 已提交
138
export class WindowsManager implements IWindowsMainService {
J
Joao Moreno 已提交
139

140
	_serviceBrand: any;
E
Erich Gamma 已提交
141

142
	private static MAX_TOTAL_RECENT_ENTRIES = 100;
143

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

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

150
	private initialUserEnv: platform.IProcessEnvironment;
E
Erich Gamma 已提交
151 152
	private windowsState: IWindowsState;

153 154 155 156 157 158 159 160 161
	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 已提交
162 163
	private _onPathsOpen = new Emitter<IPath[]>();
	onPathsOpen: CommonEvent<IPath> = this._onPathsOpen.event;
164

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

174
	public ready(initialUserEnv: platform.IProcessEnvironment): void {
E
Erich Gamma 已提交
175 176
		this.registerListeners();

177
		this.initialUserEnv = initialUserEnv;
J
Joao Moreno 已提交
178
		this.windowsState = this.storageService.getItem<IWindowsState>(WindowsManager.windowsStateStorageKey) || { openedFolders: [] };
179 180

		this.updateWindowsJumpList();
E
Erich Gamma 已提交
181 182 183
	}

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

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

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

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

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

				// Event
229
				this._onWindowReady.fire(win);
E
Erich Gamma 已提交
230 231 232
			}
		});

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

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

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

J
Joao Moreno 已提交
256
		this.lifecycleService.onBeforeQuit(() => {
E
Erich Gamma 已提交
257 258 259 260 261 262 263 264

			// 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
265
			this.windowsState.openedFolders = WindowsManager.WINDOWS.filter(w => w.readyState === ReadyState.READY && !!w.openedWorkspacePath && !w.isExtensionDevelopmentHost).map(w => {
E
Erich Gamma 已提交
266 267 268
				return <IWindowState>{
					workspacePath: w.openedWorkspacePath,
					uiState: w.serializeWindowState()
B
Benjamin Pasero 已提交
269
				};
E
Erich Gamma 已提交
270 271 272 273
			});
		});

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

277 278
		// Update jump list when recent paths change
		this.onRecentPathsChange(() => this.updateWindowsJumpList());
E
Erich Gamma 已提交
279 280
	}

281 282 283
	private onBroadcast(event: string, payload: any): void {

		// Theme changes
284 285
		if (event === 'vscode:changeColorTheme' && typeof payload === 'string') {
			this.storageService.setItem(VSCodeWindow.colorThemeStorageKey, payload);
286 287 288
		}
	}

B
Benjamin Pasero 已提交
289
	public reload(win: VSCodeWindow, cli?: ParsedArgs): void {
E
Erich Gamma 已提交
290 291

		// Only reload when the window has not vetoed this
292
		this.lifecycleService.unload(win, UnloadReason.RELOAD).done(veto => {
E
Erich Gamma 已提交
293 294 295 296 297 298
			if (!veto) {
				win.reload(cli);
			}
		});
	}

J
Joao Moreno 已提交
299
	public open(openConfig: IOpenConfiguration): VSCodeWindow[] {
300 301
		const windowConfig = this.configurationService.getConfiguration<IWindowSettings>('window');

J
Joao Moreno 已提交
302
		let iPathsToOpen: IPath[];
B
Benjamin Pasero 已提交
303
		const usedWindows: VSCodeWindow[] = [];
E
Erich Gamma 已提交
304 305 306

		// Find paths from provided paths if any
		if (openConfig.pathsToOpen && openConfig.pathsToOpen.length > 0) {
307
			iPathsToOpen = openConfig.pathsToOpen.map(pathToOpen => {
B
Benjamin Pasero 已提交
308
				const iPath = this.toIPath(pathToOpen, false, openConfig.cli && openConfig.cli.goto);
E
Erich Gamma 已提交
309 310 311

				// Warn if the requested path to open does not exist
				if (!iPath) {
B
Benjamin Pasero 已提交
312
					const options: Electron.ShowMessageBoxOptions = {
B
Benjamin Pasero 已提交
313
						title: product.nameLong,
E
Erich Gamma 已提交
314 315 316 317 318 319 320
						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 已提交
321
					const activeWindow = BrowserWindow.getFocusedWindow();
E
Erich Gamma 已提交
322
					if (activeWindow) {
323
						dialog.showMessageBox(activeWindow, options);
E
Erich Gamma 已提交
324
					} else {
325
						dialog.showMessageBox(options);
E
Erich Gamma 已提交
326 327 328 329 330 331 332 333 334 335
					}
				}

				return iPath;
			});

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

			if (iPathsToOpen.length === 0) {
336
				return null; // indicate to outside that open failed
E
Erich Gamma 已提交
337 338 339 340 341 342 343 344 345 346
			}
		}

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

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

351
		let foldersToOpen = arrays.distinct(iPathsToOpen.filter(iPath => iPath.workspacePath && !iPath.filePath).map(iPath => iPath.workspacePath), folder => platform.isLinux ? folder : folder.toLowerCase()); // prevent duplicates
352
		let foldersToRestore = (openConfig.initialStartup && !openConfig.cli.extensionDevelopmentPath) ? this.backupService.getWorkspaceBackupPaths() : [];
353 354 355
		let filesToOpen: IPath[] = [];
		let filesToDiff: IPath[] = [];
		let emptyToOpen = iPathsToOpen.filter(iPath => !iPath.workspacePath && !iPath.filePath);
356
		let emptyToRestore = (openConfig.initialStartup && !openConfig.cli.extensionDevelopmentPath) ? this.backupService.getEmptyWorkspaceBackupPaths() : [];
357 358 359 360 361 362 363 364 365 366 367
		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
			}

368 369 370
			foldersToOpen = []; 	// diff is always in empty workspace
			foldersToRestore = [];	// diff is always in empty workspace
			filesToCreate = []; 	// diff ignores other files that do not exist
371 372 373 374
		} else {
			filesToOpen = candidates;
		}

375
		// let the user settings override how folders are open in a new window or same window unless we are forced
376
		let openFolderInNewWindow = (openConfig.preferNewWindow || openConfig.forceNewWindow) && !openConfig.forceReuseWindow;
377 378
		if (!openConfig.forceNewWindow && !openConfig.forceReuseWindow && windowConfig && (windowConfig.openFoldersInNewWindow === 'on' || windowConfig.openFoldersInNewWindow === 'off')) {
			openFolderInNewWindow = (windowConfig.openFoldersInNewWindow === 'on');
379
		}
380

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

384
			// let the user settings override how files are open in a new window or same window unless we are forced (not for extension development though)
385
			let openFilesInNewWindow: boolean;
386 387
			if (openConfig.forceNewWindow || openConfig.forceReuseWindow) {
				openFilesInNewWindow = openConfig.forceNewWindow && !openConfig.forceReuseWindow;
388
			} else {
B
Benjamin Pasero 已提交
389
				if (openConfig.context === OpenContext.DOCK) {
390 391 392
					openFilesInNewWindow = true; // only on macOS do we allow to open files in a new window if this is triggered via DOCK context
				}

393
				if (!openConfig.cli.extensionDevelopmentPath && windowConfig && (windowConfig.openFilesInNewWindow === 'on' || windowConfig.openFilesInNewWindow === 'off' || <any>windowConfig.openFilesInNewWindow === false /* TODO@Ben migration */)) {
394
					openFilesInNewWindow = (windowConfig.openFilesInNewWindow === 'on');
395
				}
E
Erich Gamma 已提交
396 397 398
			}

			// Open Files in last instance if any and flag tells us so
B
Benjamin Pasero 已提交
399
			const lastActiveWindow = this.getLastActiveWindow();
E
Erich Gamma 已提交
400
			if (!openFilesInNewWindow && lastActiveWindow) {
B
Benjamin Pasero 已提交
401
				lastActiveWindow.focus();
402
				const files = { filesToOpen, filesToCreate, filesToDiff }; // copy to object because they get reset shortly after
403
				lastActiveWindow.ready().then(readyWindow => {
404
					readyWindow.send('vscode:openFiles', files);
E
Erich Gamma 已提交
405
				});
406 407

				usedWindows.push(lastActiveWindow);
E
Erich Gamma 已提交
408 409 410 411
			}

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

B
Benjamin Pasero 已提交
416
				openFolderInNewWindow = true; // any other folders to open must open in new window then
E
Erich Gamma 已提交
417
			}
418 419 420 421 422

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

425 426 427
		// 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 已提交
428 429

			// Check for existing instances
430
			const windowsOnWorkspacePath = arrays.coalesce(allFoldersToOpen.map(folderToOpen => this.findWindow(folderToOpen)));
E
Erich Gamma 已提交
431
			if (windowsOnWorkspacePath.length > 0) {
B
Benjamin Pasero 已提交
432
				const browserWindow = windowsOnWorkspacePath[0];
433
				browserWindow.focus(); // just focus one of them
434
				const files = { filesToOpen, filesToCreate, filesToDiff }; // copy to object because they get reset shortly after
435
				browserWindow.ready().then(readyWindow => {
436
					readyWindow.send('vscode:openFiles', files);
E
Erich Gamma 已提交
437 438
				});

439 440
				usedWindows.push(browserWindow);

E
Erich Gamma 已提交
441 442 443
				// Reset these because we handled them
				filesToOpen = [];
				filesToCreate = [];
444
				filesToDiff = [];
E
Erich Gamma 已提交
445

B
Benjamin Pasero 已提交
446
				openFolderInNewWindow = true; // any other folders to open must open in new window then
E
Erich Gamma 已提交
447 448 449
			}

			// Open remaining ones
450 451
			allFoldersToOpen.forEach(folderToOpen => {
				if (windowsOnWorkspacePath.some(win => this.isPathEqual(win.openedWorkspacePath, folderToOpen))) {
E
Erich Gamma 已提交
452 453 454
					return; // ignore folders that are already open
				}

455
				const configuration = this.toConfiguration(openConfig, folderToOpen, filesToOpen, filesToCreate, filesToDiff);
B
Benjamin Pasero 已提交
456
				const browserWindow = this.openInBrowserWindow(configuration, openFolderInNewWindow, openFolderInNewWindow ? void 0 : openConfig.windowToUse);
457
				usedWindows.push(browserWindow);
E
Erich Gamma 已提交
458 459 460 461

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

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

		// Handle empty
469
		if (emptyToRestore.length > 0) {
470
			emptyToRestore.forEach(emptyWorkspaceBackupFolder => {
B
Benjamin Pasero 已提交
471
				const configuration = this.toConfiguration(openConfig);
472
				const browserWindow = this.openInBrowserWindow(configuration, true /* new window */, null, emptyWorkspaceBackupFolder);
473 474
				usedWindows.push(browserWindow);

B
Benjamin Pasero 已提交
475
				openFolderInNewWindow = true; // any other folders to open must open in new window then
476 477
			});
		}
B
Benjamin Pasero 已提交
478

479 480
		// Only open empty if no empty workspaces were restored
		else if (emptyToOpen.length > 0) {
E
Erich Gamma 已提交
481
			emptyToOpen.forEach(() => {
482
				const configuration = this.toConfiguration(openConfig);
B
Benjamin Pasero 已提交
483
				const browserWindow = this.openInBrowserWindow(configuration, openFolderInNewWindow, openFolderInNewWindow ? void 0 : openConfig.windowToUse);
484
				usedWindows.push(browserWindow);
E
Erich Gamma 已提交
485

B
Benjamin Pasero 已提交
486
				openFolderInNewWindow = true; // any other folders to open must open in new window then
E
Erich Gamma 已提交
487 488 489
			});
		}

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

495 496 497
			iPathsToOpen.forEach(iPath => {
				if (iPath.filePath || iPath.workspacePath) {
					app.addRecentDocument(iPath.filePath || iPath.workspacePath);
498
					recentPaths.push({ path: iPath.filePath || iPath.workspacePath, isFile: !!iPath.filePath });
499 500
				}
			});
E
Erich Gamma 已提交
501

502 503 504 505
			if (recentPaths.length) {
				this.addToRecentPathsList(recentPaths);
			}
		}
E
Erich Gamma 已提交
506

507
		// Emit events
B
Benjamin Pasero 已提交
508
		this._onPathsOpen.fire(iPathsToOpen);
509

510
		return arrays.distinct(usedWindows);
E
Erich Gamma 已提交
511 512
	}

513 514
	public addToRecentPathsList(paths: { path: string; isFile?: boolean; }[]): void {
		if (!paths || !paths.length) {
515 516 517 518
			return;
		}

		const mru = this.getRecentPathsList();
519 520
		paths.forEach(p => {
			const {path, isFile} = p;
521

522 523 524 525 526 527 528 529 530 531 532 533
			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);
		});
534 535

		this.storageService.setItem(WindowsManager.recentPathsListStorageKey, mru);
536
		this._onRecentPathsChange.fire();
537 538
	}

539 540 541 542 543 544 545 546 547 548
	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];
		}

549
		const mru = this.getRecentPathsList();
550
		let update = false;
551

552 553 554 555 556 557
		paths.forEach(path => {
			let index = mru.files.indexOf(path);
			if (index >= 0) {
				mru.files.splice(index, 1);
				update = true;
			}
558

559 560 561 562 563 564
			index = mru.folders.indexOf(path);
			if (index >= 0) {
				mru.folders.splice(index, 1);
				update = true;
			}
		});
565

566 567
		if (update) {
			this.storageService.setItem(WindowsManager.recentPathsListStorageKey, mru);
568
			this._onRecentPathsChange.fire();
569
		}
570 571 572 573 574
	}

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

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

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

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

B
Benjamin Pasero 已提交
615
	public openExtensionDevelopmentHostWindow(openConfig: IOpenConfiguration): void {
E
Erich Gamma 已提交
616

B
Benjamin Pasero 已提交
617
		// Reload an existing extension development host window on the same path
E
Erich Gamma 已提交
618
		// We currently do not allow more than one extension development window
B
Benjamin Pasero 已提交
619
		// on the same extension path.
620
		let res = WindowsManager.WINDOWS.filter(w => w.config && this.isPathEqual(w.config.extensionDevelopmentPath, openConfig.cli.extensionDevelopmentPath));
E
Erich Gamma 已提交
621 622
		if (res && res.length === 1) {
			this.reload(res[0], openConfig.cli);
B
Benjamin Pasero 已提交
623
			res[0].focus(); // make sure it gets focus and is restored
E
Erich Gamma 已提交
624 625 626 627

			return;
		}

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

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

		// Open it
645
		this.open({ context: openConfig.context, cli: openConfig.cli, forceNewWindow: true, forceEmpty: openConfig.cli._.length === 0 });
E
Erich Gamma 已提交
646 647
	}

648
	private toConfiguration(config: IOpenConfiguration, workspacePath?: string, filesToOpen?: IPath[], filesToCreate?: IPath[], filesToDiff?: IPath[]): IWindowConfiguration {
649
		const configuration: IWindowConfiguration = mixin({}, config.cli); // inherit all properties from CLI
650
		configuration.appRoot = this.environmentService.appRoot;
B
Benjamin Pasero 已提交
651
		configuration.execPath = process.execPath;
652 653
		configuration.userEnv = this.getWindowUserEnv(config);
		configuration.isInitialStartup = config.initialStartup;
E
Erich Gamma 已提交
654 655 656
		configuration.workspacePath = workspacePath;
		configuration.filesToOpen = filesToOpen;
		configuration.filesToCreate = filesToCreate;
657
		configuration.filesToDiff = filesToDiff;
J
Johannes Rieken 已提交
658
		configuration.nodeCachedDataDir = this.environmentService.isBuilt && this.environmentService.nodeCachedDataDir;
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
					} :
					{ workspacePath: candidate };
			}
		} catch (error) {
687 688
			this.removeFromRecentPathsList(candidate); // since file does not seem to exist anymore, remove from recent

E
Erich Gamma 已提交
689 690 691 692 693 694 695 696
			if (ignoreFileNotFound) {
				return { filePath: candidate, createFilePath: true }; // assume this is a file that does not yet exist
			}
		}

		return null;
	}

B
Benjamin Pasero 已提交
697
	private cliToPaths(cli: ParsedArgs, ignoreFileNotFound?: boolean): IPath[] {
E
Erich Gamma 已提交
698 699 700

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

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

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

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

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

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

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

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

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

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

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

J
Johannes Rieken 已提交
760
			vscodeWindow = new VSCodeWindow({
761
				state: this.getNewWindowState(configuration),
762
				extensionDevelopmentPath: configuration.extensionDevelopmentPath,
763
				isExtensionTestHost: !!configuration.extensionTestsPath,
B
Benjamin Pasero 已提交
764
				allowFullscreen: this.lifecycleService.wasUpdated || (windowConfig && windowConfig.restoreFullscreen),
765
				titleBarStyle: windowConfig ? windowConfig.titleBarStyle : void 0
J
Johannes Rieken 已提交
766 767 768 769
			},
				this.logService,
				this.environmentService,
				this.configurationService,
770
				this.storageService
J
Johannes Rieken 已提交
771
			);
772

E
Erich Gamma 已提交
773 774 775
			WindowsManager.WINDOWS.push(vscodeWindow);

			// Window Events
776 777
			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));
778 779
			vscodeWindow.win.webContents.on('crashed', () => this.onWindowError(vscodeWindow, WindowError.CRASHED));
			vscodeWindow.win.on('unresponsive', () => this.onWindowError(vscodeWindow, WindowError.UNRESPONSIVE));
E
Erich Gamma 已提交
780 781 782 783
			vscodeWindow.win.on('close', () => this.onBeforeWindowClose(vscodeWindow));
			vscodeWindow.win.on('closed', () => this.onWindowClosed(vscodeWindow));

			// Lifecycle
J
Joao Moreno 已提交
784
			this.lifecycleService.registerWindow(vscodeWindow);
E
Erich Gamma 已提交
785 786 787 788 789 790
		}

		// Existing window
		else {

			// Some configuration things get inherited if the window is being reused and we are
B
Benjamin Pasero 已提交
791
			// in extension development host mode. These options are all development related.
B
Benjamin Pasero 已提交
792
			const currentWindowConfig = vscodeWindow.config;
A
Alex Dima 已提交
793 794
			if (!configuration.extensionDevelopmentPath && currentWindowConfig && !!currentWindowConfig.extensionDevelopmentPath) {
				configuration.extensionDevelopmentPath = currentWindowConfig.extensionDevelopmentPath;
B
Benjamin Pasero 已提交
795
				configuration.verbose = currentWindowConfig.verbose;
796
				configuration.debugBrkPluginHost = currentWindowConfig.debugBrkPluginHost;
797
				configuration.debugPluginHost = currentWindowConfig.debugPluginHost;
798
				configuration['extensions-dir'] = currentWindowConfig['extensions-dir'];
E
Erich Gamma 已提交
799 800 801 802
			}
		}

		// Only load when the window has not vetoed this
803
		this.lifecycleService.unload(vscodeWindow, UnloadReason.LOAD).done(veto => {
E
Erich Gamma 已提交
804 805
			if (!veto) {

B
Benjamin Pasero 已提交
806 807 808 809 810
				// Register window for backups
				if (!configuration.extensionDevelopmentPath) {
					this.backupService.registerWindowForBackupsSync(vscodeWindow.id, !configuration.workspacePath, emptyWorkspaceBackupFolder, configuration.workspacePath);
				}

E
Erich Gamma 已提交
811 812 813 814
				// Load it
				vscodeWindow.load(configuration);
			}
		});
815 816

		return vscodeWindow;
E
Erich Gamma 已提交
817 818
	}

J
Joao Moreno 已提交
819
	private getNewWindowState(configuration: IWindowConfiguration): ISingleWindowState {
E
Erich Gamma 已提交
820

B
Benjamin Pasero 已提交
821
		// extension development host Window - load from stored settings if any
A
Alex Dima 已提交
822
		if (!!configuration.extensionDevelopmentPath && this.windowsState.lastPluginDevelopmentHostWindow) {
E
Erich Gamma 已提交
823 824 825 826 827
			return this.windowsState.lastPluginDevelopmentHostWindow.uiState;
		}

		// Known Folder - load from stored settings if any
		if (configuration.workspacePath) {
B
Benjamin Pasero 已提交
828
			const stateForWorkspace = this.windowsState.openedFolders.filter(o => this.isPathEqual(o.workspacePath, configuration.workspacePath)).map(o => o.uiState);
E
Erich Gamma 已提交
829 830 831 832 833 834
			if (stateForWorkspace.length) {
				return stateForWorkspace[0];
			}
		}

		// First Window
B
Benjamin Pasero 已提交
835
		const lastActive = this.getLastActiveWindow();
E
Erich Gamma 已提交
836 837 838 839 840 841 842 843 844
		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
845
		let displayToUse: Electron.Display;
B
Benjamin Pasero 已提交
846
		const displays = screen.getAllDisplays();
E
Erich Gamma 已提交
847 848 849 850 851 852 853 854 855 856 857

		// 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 已提交
858
				const cursorPoint = screen.getCursorScreenPoint();
E
Erich Gamma 已提交
859 860 861 862 863 864 865 866 867 868 869 870 871 872
				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 已提交
873
		const defaultState = defaultWindowState();
E
Erich Gamma 已提交
874 875 876 877 878 879
		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 已提交
880
	private ensureNoOverlap(state: ISingleWindowState): ISingleWindowState {
E
Erich Gamma 已提交
881 882 883 884
		if (WindowsManager.WINDOWS.length === 0) {
			return state;
		}

885 886
		const existingWindowBounds = WindowsManager.WINDOWS.map(win => win.getBounds());
		while (existingWindowBounds.some(b => b.x === state.x || b.y === state.y)) {
E
Erich Gamma 已提交
887 888 889 890 891 892 893
			state.x += 30;
			state.y += 30;
		}

		return state;
	}

894
	public openFileFolderPicker(forceNewWindow?: boolean): void {
895
		this.doPickAndOpen({ pickFolders: true, pickFiles: true, forceNewWindow });
896 897
	}

898 899
	public openFilePicker(forceNewWindow?: boolean, path?: string, window?: VSCodeWindow): void {
		this.doPickAndOpen({ pickFiles: true, forceNewWindow, path, window });
900 901
	}

902 903
	public openFolderPicker(forceNewWindow?: boolean, window?: VSCodeWindow): void {
		this.doPickAndOpen({ pickFolders: true, forceNewWindow, window });
E
Erich Gamma 已提交
904 905
	}

906 907 908 909 910 911 912 913 914 915 916 917 918 919 920 921
	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');
	}

922
	private doPickAndOpen(options: INativeOpenDialogOptions): void {
923
		this.getFileOrFolderPaths(options, (paths: string[]) => {
E
Erich Gamma 已提交
924
			if (paths && paths.length) {
925
				this.open({ context: OpenContext.DIALOG, cli: this.environmentService.args, pathsToOpen: paths, forceNewWindow: options.forceNewWindow });
E
Erich Gamma 已提交
926 927 928 929
			}
		});
	}

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

B
Benjamin Pasero 已提交
934
		let pickerProperties: ('openFile' | 'openDirectory' | 'multiSelections' | 'createDirectory')[];
935
		if (options.pickFiles && options.pickFolders) {
E
Erich Gamma 已提交
936 937
			pickerProperties = ['multiSelections', 'openDirectory', 'openFile', 'createDirectory'];
		} else {
938
			pickerProperties = ['multiSelections', options.pickFolders ? 'openDirectory' : 'openFile', 'createDirectory'];
E
Erich Gamma 已提交
939 940
		}

941
		dialog.showOpenDialog(focussedWindow && focussedWindow.win, {
E
Erich Gamma 已提交
942 943
			defaultPath: workingDir,
			properties: pickerProperties
944
		}, paths => {
E
Erich Gamma 已提交
945 946 947
			if (paths && paths.length > 0) {

				// Remember path in storage for next time
J
Joao Moreno 已提交
948
				this.storageService.setItem(WindowsManager.workingDirPickerStorageKey, path.dirname(paths[0]));
E
Erich Gamma 已提交
949 950 951 952 953 954 955 956 957

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

958
	public focusLastActive(cli: ParsedArgs, context: OpenContext): VSCodeWindow {
B
Benjamin Pasero 已提交
959
		const lastActive = this.getLastActiveWindow();
E
Erich Gamma 已提交
960
		if (lastActive) {
B
Benjamin Pasero 已提交
961
			lastActive.focus();
962 963

			return lastActive;
E
Erich Gamma 已提交
964 965 966
		}

		// No window - open new one
967
		this.windowsState.openedFolders = []; // make sure we do not open too much
968
		const res = this.open({ context, cli });
969 970

		return res && res[0];
E
Erich Gamma 已提交
971 972
	}

J
Joao Moreno 已提交
973
	public getLastActiveWindow(): VSCodeWindow {
E
Erich Gamma 已提交
974
		if (WindowsManager.WINDOWS.length) {
975 976
			const lastFocussedDate = Math.max.apply(Math, WindowsManager.WINDOWS.map(w => w.lastFocusTime));
			const res = WindowsManager.WINDOWS.filter(w => w.lastFocusTime === lastFocussedDate);
E
Erich Gamma 已提交
977 978 979 980 981 982 983 984
			if (res && res.length) {
				return res[0];
			}
		}

		return null;
	}

J
Joao Moreno 已提交
985
	public findWindow(workspacePath: string, filePath?: string, extensionDevelopmentPath?: string): VSCodeWindow {
E
Erich Gamma 已提交
986 987 988
		if (WindowsManager.WINDOWS.length) {

			// Sort the last active window to the front of the array of windows to test
B
Benjamin Pasero 已提交
989 990
			const windowsToTest = WindowsManager.WINDOWS.slice(0);
			const lastActiveWindow = this.getLastActiveWindow();
E
Erich Gamma 已提交
991 992 993 994 995 996
			if (lastActiveWindow) {
				windowsToTest.splice(windowsToTest.indexOf(lastActiveWindow), 1);
				windowsToTest.unshift(lastActiveWindow);
			}

			// Find it
997
			const res = windowsToTest.filter(w => {
E
Erich Gamma 已提交
998 999

				// match on workspace
1000
				if (typeof w.openedWorkspacePath === 'string' && (this.isPathEqual(w.openedWorkspacePath, workspacePath))) {
E
Erich Gamma 已提交
1001 1002 1003 1004
					return true;
				}

				// match on file
B
Benjamin Pasero 已提交
1005
				if (typeof w.openedFilePath === 'string' && this.isPathEqual(w.openedFilePath, filePath)) {
E
Erich Gamma 已提交
1006 1007 1008 1009 1010 1011 1012 1013
					return true;
				}

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

1014 1015 1016 1017 1018
				// match on extension development path
				if (typeof extensionDevelopmentPath === 'string' && w.extensionDevelopmentPath === extensionDevelopmentPath) {
					return true;
				}

E
Erich Gamma 已提交
1019 1020 1021 1022 1023 1024 1025 1026 1027 1028 1029
				return false;
			});

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

		return null;
	}

1030 1031
	public openNewWindow(context: OpenContext): void {
		this.open({ context, cli: this.environmentService.args, forceNewWindow: true, forceEmpty: true });
E
Erich Gamma 已提交
1032 1033 1034 1035 1036 1037
	}

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

		if (focusedWindow) {
1038
			focusedWindow.sendWhenReady(channel, ...args);
E
Erich Gamma 已提交
1039 1040 1041 1042
		}
	}

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

1048
			w.sendWhenReady(channel, payload);
E
Erich Gamma 已提交
1049 1050 1051
		});
	}

J
Joao Moreno 已提交
1052
	public getFocusedWindow(): VSCodeWindow {
B
Benjamin Pasero 已提交
1053
		const win = BrowserWindow.getFocusedWindow();
E
Erich Gamma 已提交
1054 1055 1056 1057 1058 1059 1060
		if (win) {
			return this.getWindowById(win.id);
		}

		return null;
	}

J
Joao Moreno 已提交
1061
	public getWindowById(windowId: number): VSCodeWindow {
1062
		const res = WindowsManager.WINDOWS.filter(w => w.id === windowId);
E
Erich Gamma 已提交
1063 1064 1065 1066 1067 1068 1069
		if (res && res.length === 1) {
			return res[0];
		}

		return null;
	}

J
Joao Moreno 已提交
1070
	public getWindows(): VSCodeWindow[] {
E
Erich Gamma 已提交
1071 1072 1073 1074 1075 1076 1077
		return WindowsManager.WINDOWS;
	}

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

J
Joao Moreno 已提交
1078
	private onWindowError(vscodeWindow: VSCodeWindow, error: WindowError): void {
E
Erich Gamma 已提交
1079 1080 1081 1082
		console.error(error === WindowError.CRASHED ? '[VS Code]: render process crashed!' : '[VS Code]: detected unresponsive');

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

		// Crashed
		else {
1102
			dialog.showMessageBox(vscodeWindow.win, {
B
Benjamin Pasero 已提交
1103
				title: product.nameLong,
E
Erich Gamma 已提交
1104
				type: 'warning',
B
Benjamin Pasero 已提交
1105
				buttons: [nls.localize('reopen', "Reopen"), nls.localize('close', "Close")],
1106
				message: nls.localize('appCrashed', "The window has crashed"),
B
Benjamin Pasero 已提交
1107
				detail: nls.localize('appCrashedDetail', "We are sorry for the inconvenience! You can reopen the window to continue where you left off."),
E
Erich Gamma 已提交
1108
				noLink: true
1109
			}, result => {
1110 1111 1112
				if (result === 0) {
					vscodeWindow.reload();
				} else if (result === 1) {
1113
					this.onBeforeWindowClose(vscodeWindow); // 'close' event will not be fired on destroy(), so run it manually
1114 1115
					vscodeWindow.win.destroy(); // make sure to destroy the window as it has crashed
				}
E
Erich Gamma 已提交
1116 1117 1118 1119
			});
		}
	}

J
Joao Moreno 已提交
1120 1121
	private onBeforeWindowClose(win: VSCodeWindow): void {
		if (win.readyState !== ReadyState.READY) {
E
Erich Gamma 已提交
1122 1123 1124 1125
			return; // only persist windows that are fully loaded
		}

		// On Window close, update our stored state of this window
B
Benjamin Pasero 已提交
1126
		const state: IWindowState = { workspacePath: win.openedWorkspacePath, uiState: win.serializeWindowState() };
1127 1128 1129 1130
		if (win.isExtensionDevelopmentHost) {
			if (!win.isExtensionTestHost) {
				this.windowsState.lastPluginDevelopmentHostWindow = state; // do not let test run window state overwrite our extension development state
			}
E
Erich Gamma 已提交
1131 1132 1133 1134
		} else {
			this.windowsState.lastActiveWindow = state;

			this.windowsState.openedFolders.forEach(o => {
B
Benjamin Pasero 已提交
1135
				if (this.isPathEqual(o.workspacePath, win.openedWorkspacePath)) {
E
Erich Gamma 已提交
1136 1137 1138 1139 1140 1141
					o.uiState = state.uiState;
				}
			});
		}
	}

J
Joao Moreno 已提交
1142
	private onWindowClosed(win: VSCodeWindow): void {
E
Erich Gamma 已提交
1143 1144 1145 1146 1147

		// Tell window
		win.dispose();

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

		// Emit
1152
		this._onWindowClose.fire(win.id);
E
Erich Gamma 已提交
1153
	}
B
Benjamin Pasero 已提交
1154 1155 1156 1157 1158 1159 1160 1161 1162 1163 1164 1165 1166 1167 1168 1169 1170 1171 1172 1173 1174 1175 1176 1177

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

1179
	public toggleMenuBar(windowId: number): void {
J
Joao Moreno 已提交
1180 1181 1182 1183 1184 1185 1186 1187 1188 1189 1190 1191 1192 1193 1194 1195
		// 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."));
			}
		}
	}
1196

B
Benjamin Pasero 已提交
1197
	private updateWindowsJumpList(): void {
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 1235 1236 1237 1238 1239 1240 1241 1242 1243 1244 1245 1246 1247 1248 1249 1250 1251 1252 1253 1254 1255 1256 1257
		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
		}
	}
1258 1259 1260 1261 1262 1263

	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();
1264
		if (vscodeWindow && vscodeWindow.isExtensionDevelopmentHost && this.getWindowCount() > 1) {
1265 1266 1267 1268 1269 1270 1271 1272 1273 1274
			vscodeWindow.win.close();
		}

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