windows.ts 48.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';
20
import { IPath, VSCodeWindow, IWindowConfiguration, IWindowState as ISingleWindowState, defaultWindowState, WindowMode } 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';
C
Christof Marti 已提交
29
import { getLastActiveWindow, findBestWindowOrFolder } from 'vs/code/node/windowsUtils';
30
import CommonEvent, { Emitter } from 'vs/base/common/event';
31
import product from 'vs/platform/node/product';
C
Christof Marti 已提交
32
import { OpenContext } from 'vs/code/common/windows';
33
import { ITelemetryService, ITelemetryData } from 'vs/platform/telemetry/common/telemetry';
E
Erich Gamma 已提交
34 35 36 37 38 39 40

enum WindowError {
	UNRESPONSIVE,
	CRASHED
}

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

54 55 56 57
interface INewWindowState extends ISingleWindowState {
	hasDefaultState?: boolean;
}

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

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

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

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

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

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

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

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

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

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

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

131
	private static MAX_TOTAL_RECENT_ENTRIES = 100;
132

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

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

139
	private initialUserEnv: platform.IProcessEnvironment;
140

E
Erich Gamma 已提交
141
	private windowsState: IWindowsState;
142
	private lastClosedWindowState: IWindowState;
E
Erich Gamma 已提交
143

144 145
	private firstWindowLoading: boolean;

146 147 148 149 150 151 152 153 154
	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;

155 156 157
	private _onWindowReload = new Emitter<number>();
	onWindowReload: CommonEvent<number> = this._onWindowReload.event;

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

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

171
	public ready(initialUserEnv: platform.IProcessEnvironment): void {
E
Erich Gamma 已提交
172 173
		this.registerListeners();

174
		this.initialUserEnv = initialUserEnv;
J
Joao Moreno 已提交
175
		this.windowsState = this.storageService.getItem<IWindowsState>(WindowsManager.windowsStateStorageKey) || { openedFolders: [] };
E
Erich Gamma 已提交
176 177 178
	}

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

G
Giorgos Retsinas 已提交
182
			// Mac only event: open new window when we get activated
E
Erich Gamma 已提交
183
			if (!hasVisibleWindows) {
184
				this.openNewWindow(OpenContext.DOCK);
E
Erich Gamma 已提交
185 186 187 188 189 190
			}
		});

		let macOpenFiles: string[] = [];
		let runningTimeout: number = null;
		app.on('open-file', (event: Event, path: string) => {
J
Joao Moreno 已提交
191
			this.logService.log('App#open-file: ', path);
E
Erich Gamma 已提交
192 193 194 195 196 197 198 199 200 201 202 203 204
			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 已提交
205 206 207 208 209 210
				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 已提交
211 212 213 214 215
				macOpenFiles = [];
				runningTimeout = null;
			}, 100);
		});

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

B
Benjamin Pasero 已提交
219
			const win = this.getWindowById(windowId);
E
Erich Gamma 已提交
220 221 222 223
			if (win) {
				win.setReady();

				// Event
224
				this._onWindowReady.fire(win);
E
Erich Gamma 已提交
225 226 227
			}
		});

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

232 233 234 235
				// Handle specific events on main side
				this.onBroadcast(broadcast.channel, broadcast.payload);

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

251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278
		// Update our windows state before quitting and before closing windows
		this.lifecycleService.onBeforeWindowClose(win => this.onBeforeWindowClose(win));
		this.lifecycleService.onBeforeQuit(() => this.onBeforeQuit());

		// Update jump list when recent paths change
		this.onRecentPathsChange(() => this.updateWindowsJumpList());
	}

	// Note that onBeforeQuit() and onBeforeWindowClose() are fired in different order depending on the OS:
	// - macOS: since the app will not quit when closing the last window, you will always first get
	//          the onBeforeQuit() event followed by N onbeforeWindowClose() events for each window
	// - other: on other OS, closing the last window will quit the app so the order depends on the
	//          user interaction: closing the last window will first trigger onBeforeWindowClose()
	//          and then onBeforeQuit(). Using the quit action however will first issue onBeforeQuit()
	//          and then onBeforeWindowClose().
	private onBeforeQuit(): void {
		const currentWindowsState: IWindowsState = {
			openedFolders: [],
			lastPluginDevelopmentHostWindow: this.windowsState.lastPluginDevelopmentHostWindow,
			lastActiveWindow: this.lastClosedWindowState //will be set on Win/Linux if last window was closed, resulting in an exit
		};

		// 1.) Find a last active window (pick any other first window otherwise)
		if (!currentWindowsState.lastActiveWindow) {
			let activeWindow = this.getLastActiveWindow();
			if (!activeWindow || activeWindow.isExtensionDevelopmentHost) {
				activeWindow = WindowsManager.WINDOWS.filter(w => !w.isExtensionDevelopmentHost)[0];
			}
E
Erich Gamma 已提交
279

280 281
			if (activeWindow) {
				currentWindowsState.lastActiveWindow = { workspacePath: activeWindow.openedWorkspacePath, uiState: activeWindow.serializeWindowState() };
E
Erich Gamma 已提交
282
			}
283 284 285 286 287 288 289
		}

		// 2.) Find extension host window
		const extensionHostWindow = WindowsManager.WINDOWS.filter(w => w.isExtensionDevelopmentHost && !w.isExtensionTestHost)[0];
		if (extensionHostWindow) {
			currentWindowsState.lastPluginDevelopmentHostWindow = { workspacePath: extensionHostWindow.openedWorkspacePath, uiState: extensionHostWindow.serializeWindowState() };
		}
E
Erich Gamma 已提交
290

291 292 293 294 295 296 297
		// 3.) All windows with opened folders for N >= 2 to support reopenFolders: all or for auto update
		//
		// Carefull here: asking a window for its window state after it has been closed returns bogus values (width: 0, height: 0)
		// so if we ever want to persist the UI state of the last closed window (window count === 1), it has
		// to come from the stored lastClosedWindowState on Win/Linux at least
		if (this.getWindowCount() > 1) {
			currentWindowsState.openedFolders = WindowsManager.WINDOWS.filter(w => !!w.openedWorkspacePath && !w.isExtensionDevelopmentHost).map(w => {
E
Erich Gamma 已提交
298 299 300
				return <IWindowState>{
					workspacePath: w.openedWorkspacePath,
					uiState: w.serializeWindowState()
B
Benjamin Pasero 已提交
301
				};
E
Erich Gamma 已提交
302
			});
303
		}
E
Erich Gamma 已提交
304

305 306 307
		// Persist
		this.storageService.setItem(WindowsManager.windowsStateStorageKey, currentWindowsState);
	}
308

309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334
	// See note on #onBeforeQuit() for details how these events are flowing
	private onBeforeWindowClose(win: VSCodeWindow): void {
		if (this.lifecycleService.isQuitRequested()) {
			return; // during quit, many windows close in parallel so let it be handled in the before-quit handler
		}

		// On Window close, update our stored UI state of this window
		const state: IWindowState = { workspacePath: win.openedWorkspacePath, uiState: win.serializeWindowState() };
		if (win.isExtensionDevelopmentHost && !win.isExtensionTestHost) {
			this.windowsState.lastPluginDevelopmentHostWindow = state; // do not let test run window state overwrite our extension development state
		}

		// Any non extension host window with same workspace
		else if (!win.isExtensionDevelopmentHost && !!win.openedWorkspacePath) {
			this.windowsState.openedFolders.forEach(o => {
				if (this.isPathEqual(o.workspacePath, win.openedWorkspacePath)) {
					o.uiState = state.uiState;
				}
			});
		}

		// On Windows and Linux closing the last window will trigger quit. Since we are storing all UI state
		// before quitting, we need to remember the UI state of this window to be able to persist it.
		if (!platform.isMacintosh && this.getWindowCount() === 1) {
			this.lastClosedWindowState = state;
		}
E
Erich Gamma 已提交
335 336
	}

337
	private onBroadcast(event: string, payload: any): void {
338 339

		// Theme changes
340 341
		if (event === 'vscode:changeColorTheme' && typeof payload === 'string') {
			this.storageService.setItem(VSCodeWindow.themeStorageKey, payload);
342
		}
343
	}
B
Benjamin Pasero 已提交
344
	public reload(win: VSCodeWindow, cli?: ParsedArgs): void {
E
Erich Gamma 已提交
345 346

		// Only reload when the window has not vetoed this
347
		this.lifecycleService.unload(win, UnloadReason.RELOAD).done(veto => {
E
Erich Gamma 已提交
348 349
			if (!veto) {
				win.reload(cli);
350 351 352

				// Emit
				this._onWindowReload.fire(win.id);
E
Erich Gamma 已提交
353 354 355 356
			}
		});
	}

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

J
Joao Moreno 已提交
360
		let iPathsToOpen: IPath[];
B
Benjamin Pasero 已提交
361
		const usedWindows: VSCodeWindow[] = [];
E
Erich Gamma 已提交
362 363 364

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

				// Warn if the requested path to open does not exist
				if (!iPath) {
B
Benjamin Pasero 已提交
370
					const options: Electron.ShowMessageBoxOptions = {
B
Benjamin Pasero 已提交
371
						title: product.nameLong,
E
Erich Gamma 已提交
372 373 374 375 376 377 378
						type: 'info',
						buttons: [nls.localize('ok', "OK")],
						message: nls.localize('pathNotExistTitle', "Path does not exist"),
						detail: nls.localize('pathNotExistDetail', "The path '{0}' does not seem to exist anymore on disk.", pathToOpen),
						noLink: true
					};

B
Benjamin Pasero 已提交
379
					const activeWindow = BrowserWindow.getFocusedWindow();
E
Erich Gamma 已提交
380
					if (activeWindow) {
381
						dialog.showMessageBox(activeWindow, options);
E
Erich Gamma 已提交
382
					} else {
383
						dialog.showMessageBox(options);
E
Erich Gamma 已提交
384 385 386 387 388 389 390 391 392 393
					}
				}

				return iPath;
			});

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

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

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

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

409
		let foldersToOpen = arrays.distinct(iPathsToOpen.filter(iPath => iPath.workspacePath && !iPath.filePath).map(iPath => iPath.workspacePath), folder => platform.isLinux ? folder : folder.toLowerCase()); // prevent duplicates
410
		let foldersToRestore = (openConfig.initialStartup && !openConfig.cli.extensionDevelopmentPath) ? this.backupService.getWorkspaceBackupPaths() : [];
411 412 413
		let filesToOpen: IPath[] = [];
		let filesToDiff: IPath[] = [];
		let emptyToOpen = iPathsToOpen.filter(iPath => !iPath.workspacePath && !iPath.filePath);
414
		let emptyToRestore = (openConfig.initialStartup && !openConfig.cli.extensionDevelopmentPath) ? this.backupService.getEmptyWorkspaceBackupPaths() : [];
415 416 417 418 419 420 421 422 423 424 425
		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
			}

426 427 428
			foldersToOpen = []; 	// diff is always in empty workspace
			foldersToRestore = [];	// diff is always in empty workspace
			filesToCreate = []; 	// diff ignores other files that do not exist
429 430 431 432
		} else {
			filesToOpen = candidates;
		}

433
		// let the user settings override how folders are open in a new window or same window unless we are forced
434
		let openFolderInNewWindow = (openConfig.preferNewWindow || openConfig.forceNewWindow) && !openConfig.forceReuseWindow;
435 436
		if (!openConfig.forceNewWindow && !openConfig.forceReuseWindow && windowConfig && (windowConfig.openFoldersInNewWindow === 'on' || windowConfig.openFoldersInNewWindow === 'off')) {
			openFolderInNewWindow = (windowConfig.openFoldersInNewWindow === 'on');
437
		}
438

B
Benjamin Pasero 已提交
439
		// Handle files to open/diff or to create when we dont open a folder and we do not restore any folder/untitled from hot-exit
B
wip  
Benjamin Pasero 已提交
440
		if (!foldersToOpen.length && !foldersToRestore.length && !emptyToRestore.length && (filesToOpen.length > 0 || filesToCreate.length > 0 || filesToDiff.length > 0)) {
E
Erich Gamma 已提交
441

442
			// 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)
443
			let openFilesInNewWindow: boolean;
444 445
			if (openConfig.forceNewWindow || openConfig.forceReuseWindow) {
				openFilesInNewWindow = openConfig.forceNewWindow && !openConfig.forceReuseWindow;
446
			} else {
B
Benjamin Pasero 已提交
447
				if (openConfig.context === OpenContext.DOCK) {
448 449 450
					openFilesInNewWindow = true; // only on macOS do we allow to open files in a new window if this is triggered via DOCK context
				}

B
Benjamin Pasero 已提交
451
				if (!openConfig.cli.extensionDevelopmentPath && windowConfig && (windowConfig.openFilesInNewWindow === 'on' || windowConfig.openFilesInNewWindow === 'off')) {
452
					openFilesInNewWindow = (windowConfig.openFilesInNewWindow === 'on');
453
				}
E
Erich Gamma 已提交
454 455 456
			}

			// Open Files in last instance if any and flag tells us so
457 458 459 460 461 462 463 464 465 466 467
			const fileToCheck = filesToOpen[0] || filesToCreate[0] || filesToDiff[0];
			const windowOrFolder = findBestWindowOrFolder({
				windows: WindowsManager.WINDOWS,
				newWindow: openFilesInNewWindow,
				reuseWindow: openConfig.forceReuseWindow,
				context: openConfig.context,
				filePath: fileToCheck && fileToCheck.filePath,
				userHome: this.environmentService.userHome
			});
			if (windowOrFolder instanceof VSCodeWindow) {
				windowOrFolder.focus();
468
				const files = { filesToOpen, filesToCreate, filesToDiff }; // copy to object because they get reset shortly after
469
				windowOrFolder.ready().then(readyWindow => {
470
					readyWindow.send('vscode:openFiles', files);
E
Erich Gamma 已提交
471
				});
472

473
				usedWindows.push(windowOrFolder);
E
Erich Gamma 已提交
474 475 476 477
			}

			// Otherwise open instance with files
			else {
478
				const configuration = this.toConfiguration(openConfig, windowOrFolder, filesToOpen, filesToCreate, filesToDiff);
B
Benjamin Pasero 已提交
479
				const browserWindow = this.openInBrowserWindow(configuration, true /* new window */);
480
				usedWindows.push(browserWindow);
E
Erich Gamma 已提交
481

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

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

491 492 493
		// 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 已提交
494 495

			// Check for existing instances
496
			const windowsOnWorkspacePath = arrays.coalesce(allFoldersToOpen.map(folderToOpen => this.findWindow(folderToOpen)));
E
Erich Gamma 已提交
497
			if (windowsOnWorkspacePath.length > 0) {
B
Benjamin Pasero 已提交
498
				const browserWindow = windowsOnWorkspacePath[0];
499
				browserWindow.focus(); // just focus one of them
500
				const files = { filesToOpen, filesToCreate, filesToDiff }; // copy to object because they get reset shortly after
501
				browserWindow.ready().then(readyWindow => {
502
					readyWindow.send('vscode:openFiles', files);
E
Erich Gamma 已提交
503 504
				});

505 506
				usedWindows.push(browserWindow);

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

B
Benjamin Pasero 已提交
512
				openFolderInNewWindow = true; // any other folders to open must open in new window then
E
Erich Gamma 已提交
513 514 515
			}

			// Open remaining ones
516 517
			allFoldersToOpen.forEach(folderToOpen => {
				if (windowsOnWorkspacePath.some(win => this.isPathEqual(win.openedWorkspacePath, folderToOpen))) {
E
Erich Gamma 已提交
518 519 520
					return; // ignore folders that are already open
				}

521
				const configuration = this.toConfiguration(openConfig, folderToOpen, filesToOpen, filesToCreate, filesToDiff);
B
Benjamin Pasero 已提交
522
				const browserWindow = this.openInBrowserWindow(configuration, openFolderInNewWindow, openFolderInNewWindow ? void 0 : openConfig.windowToUse);
523
				usedWindows.push(browserWindow);
E
Erich Gamma 已提交
524 525 526 527

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

B
Benjamin Pasero 已提交
530
				openFolderInNewWindow = true; // any other folders to open must open in new window then
E
Erich Gamma 已提交
531 532 533 534
			});
		}

		// Handle empty
535
		if (emptyToRestore.length > 0) {
536
			emptyToRestore.forEach(emptyWorkspaceBackupFolder => {
B
wip  
Benjamin Pasero 已提交
537
				const configuration = this.toConfiguration(openConfig, void 0, filesToOpen, filesToCreate, filesToDiff);
538
				const browserWindow = this.openInBrowserWindow(configuration, true /* new window */, null, emptyWorkspaceBackupFolder);
539 540
				usedWindows.push(browserWindow);

B
wip  
Benjamin Pasero 已提交
541 542 543 544 545
				// Reset these because we handled them
				filesToOpen = [];
				filesToCreate = [];
				filesToDiff = [];

B
Benjamin Pasero 已提交
546
				openFolderInNewWindow = true; // any other folders to open must open in new window then
547 548
			});
		}
B
Benjamin Pasero 已提交
549

550 551
		// Only open empty if no empty workspaces were restored
		else if (emptyToOpen.length > 0) {
E
Erich Gamma 已提交
552
			emptyToOpen.forEach(() => {
553
				const configuration = this.toConfiguration(openConfig);
B
Benjamin Pasero 已提交
554
				const browserWindow = this.openInBrowserWindow(configuration, openFolderInNewWindow, openFolderInNewWindow ? void 0 : openConfig.windowToUse);
555
				usedWindows.push(browserWindow);
E
Erich Gamma 已提交
556

B
Benjamin Pasero 已提交
557
				openFolderInNewWindow = true; // any other folders to open must open in new window then
E
Erich Gamma 已提交
558 559 560
			});
		}

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

566 567 568
			iPathsToOpen.forEach(iPath => {
				if (iPath.filePath || iPath.workspacePath) {
					app.addRecentDocument(iPath.filePath || iPath.workspacePath);
569
					recentPaths.push({ path: iPath.filePath || iPath.workspacePath, isFile: !!iPath.filePath });
570 571
				}
			});
E
Erich Gamma 已提交
572

573 574 575 576
			if (recentPaths.length) {
				this.addToRecentPathsList(recentPaths);
			}
		}
E
Erich Gamma 已提交
577

578
		// Emit events
B
Benjamin Pasero 已提交
579
		this._onPathsOpen.fire(iPathsToOpen);
580

581
		return arrays.distinct(usedWindows);
E
Erich Gamma 已提交
582 583
	}

584 585
	public addToRecentPathsList(paths: { path: string; isFile?: boolean; }[]): void {
		if (!paths || !paths.length) {
586 587 588 589
			return;
		}

		const mru = this.getRecentPathsList();
590
		paths.forEach(p => {
M
Martin Aeschlimann 已提交
591
			const { path, isFile } = p;
592

593 594 595 596 597 598 599 600 601 602 603 604
			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);
		});
605 606

		this.storageService.setItem(WindowsManager.recentPathsListStorageKey, mru);
607
		this._onRecentPathsChange.fire();
608 609
	}

610 611 612 613 614 615 616 617 618 619
	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];
		}

620
		const mru = this.getRecentPathsList();
621
		let update = false;
622

623 624 625 626 627 628
		paths.forEach(path => {
			let index = mru.files.indexOf(path);
			if (index >= 0) {
				mru.files.splice(index, 1);
				update = true;
			}
629

630 631 632 633 634 635
			index = mru.folders.indexOf(path);
			if (index >= 0) {
				mru.folders.splice(index, 1);
				update = true;
			}
		});
636

637 638
		if (update) {
			this.storageService.setItem(WindowsManager.recentPathsListStorageKey, mru);
639
			this._onRecentPathsChange.fire();
640
		}
641 642 643 644 645
	}

	public clearRecentPathsList(): void {
		this.storageService.setItem(WindowsManager.recentPathsListStorageKey, { folders: [], files: [] });
		app.clearRecentDocuments();
646 647 648

		// Event
		this._onRecentPathsChange.fire();
649 650 651 652 653 654 655 656 657 658 659 660 661 662 663 664 665 666 667 668 669 670 671 672 673 674 675 676 677 678 679 680 681
	}

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

682
	private getWindowUserEnv(openConfig: IOpenConfiguration): platform.IProcessEnvironment {
683 684 685
		return assign({}, this.initialUserEnv, openConfig.userEnv || {});
	}

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

B
Benjamin Pasero 已提交
688
		// Reload an existing extension development host window on the same path
E
Erich Gamma 已提交
689
		// We currently do not allow more than one extension development window
B
Benjamin Pasero 已提交
690
		// on the same extension path.
691
		let res = WindowsManager.WINDOWS.filter(w => w.config && this.isPathEqual(w.config.extensionDevelopmentPath, openConfig.cli.extensionDevelopmentPath));
E
Erich Gamma 已提交
692 693
		if (res && res.length === 1) {
			this.reload(res[0], openConfig.cli);
B
Benjamin Pasero 已提交
694
			res[0].focus(); // make sure it gets focus and is restored
E
Erich Gamma 已提交
695 696 697 698

			return;
		}

699
		// Fill in previously opened workspace unless an explicit path is provided and we are not unit testing
B
Benjamin Pasero 已提交
700
		if (openConfig.cli._.length === 0 && !openConfig.cli.extensionTestsPath) {
B
Benjamin Pasero 已提交
701
			const workspaceToOpen = this.windowsState.lastPluginDevelopmentHostWindow && this.windowsState.lastPluginDevelopmentHostWindow.workspacePath;
E
Erich Gamma 已提交
702
			if (workspaceToOpen) {
B
Benjamin Pasero 已提交
703
				openConfig.cli._ = [workspaceToOpen];
E
Erich Gamma 已提交
704 705 706 707
			}
		}

		// Make sure we are not asked to open a path that is already opened
B
Benjamin Pasero 已提交
708 709
		if (openConfig.cli._.length > 0) {
			res = WindowsManager.WINDOWS.filter(w => w.openedWorkspacePath && openConfig.cli._.indexOf(w.openedWorkspacePath) >= 0);
E
Erich Gamma 已提交
710
			if (res.length) {
B
Benjamin Pasero 已提交
711
				openConfig.cli._ = [];
E
Erich Gamma 已提交
712 713 714 715
			}
		}

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

719
	private toConfiguration(config: IOpenConfiguration, workspacePath?: string, filesToOpen?: IPath[], filesToCreate?: IPath[], filesToDiff?: IPath[]): IWindowConfiguration {
720
		const configuration: IWindowConfiguration = mixin({}, config.cli); // inherit all properties from CLI
721
		configuration.appRoot = this.environmentService.appRoot;
B
Benjamin Pasero 已提交
722
		configuration.execPath = process.execPath;
723 724
		configuration.userEnv = this.getWindowUserEnv(config);
		configuration.isInitialStartup = config.initialStartup;
E
Erich Gamma 已提交
725 726 727
		configuration.workspacePath = workspacePath;
		configuration.filesToOpen = filesToOpen;
		configuration.filesToCreate = filesToCreate;
728
		configuration.filesToDiff = filesToDiff;
J
Johannes Rieken 已提交
729
		configuration.nodeCachedDataDir = this.environmentService.isBuilt && this.environmentService.nodeCachedDataDir;
E
Erich Gamma 已提交
730 731 732 733

		return configuration;
	}

J
Joao Moreno 已提交
734
	private toIPath(anyPath: string, ignoreFileNotFound?: boolean, gotoLineMode?: boolean): IPath {
E
Erich Gamma 已提交
735 736 737 738
		if (!anyPath) {
			return null;
		}

739
		let parsedPath: IPathWithLineAndColumn;
E
Erich Gamma 已提交
740
		if (gotoLineMode) {
J
Joao Moreno 已提交
741
			parsedPath = parseLineAndColumnAware(anyPath);
E
Erich Gamma 已提交
742 743 744
			anyPath = parsedPath.path;
		}

B
Benjamin Pasero 已提交
745
		const candidate = path.normalize(anyPath);
E
Erich Gamma 已提交
746
		try {
B
Benjamin Pasero 已提交
747
			const candidateStat = fs.statSync(candidate);
E
Erich Gamma 已提交
748 749 750 751 752
			if (candidateStat) {
				return candidateStat.isFile() ?
					{
						filePath: candidate,
						lineNumber: gotoLineMode ? parsedPath.line : void 0,
753
						columnNumber: gotoLineMode ? parsedPath.column : void 0
E
Erich Gamma 已提交
754 755 756 757
					} :
					{ workspacePath: candidate };
			}
		} catch (error) {
758 759
			this.removeFromRecentPathsList(candidate); // since file does not seem to exist anymore, remove from recent

E
Erich Gamma 已提交
760 761 762 763 764 765 766 767
			if (ignoreFileNotFound) {
				return { filePath: candidate, createFilePath: true }; // assume this is a file that does not yet exist
			}
		}

		return null;
	}

B
Benjamin Pasero 已提交
768
	private cliToPaths(cli: ParsedArgs, ignoreFileNotFound?: boolean): IPath[] {
E
Erich Gamma 已提交
769 770 771

		// Check for pass in candidate or last opened path
		let candidates: string[] = [];
B
Benjamin Pasero 已提交
772 773
		if (cli._.length > 0) {
			candidates = cli._;
E
Erich Gamma 已提交
774 775 776 777
		}

		// No path argument, check settings for what to do now
		else {
778 779 780 781
			let reopenFolders: string;
			if (this.lifecycleService.wasUpdated) {
				reopenFolders = ReopenFoldersSetting.ALL; // always reopen all folders when an update was applied
			} else {
782 783
				const windowConfig = this.configurationService.getConfiguration<IWindowSettings>('window');
				reopenFolders = (windowConfig && windowConfig.reopenFolders) || ReopenFoldersSetting.ONE;
784 785
			}

B
Benjamin Pasero 已提交
786
			const lastActiveFolder = this.windowsState.lastActiveWindow && this.windowsState.lastActiveWindow.workspacePath;
E
Erich Gamma 已提交
787 788

			// Restore all
789
			if (reopenFolders === ReopenFoldersSetting.ALL) {
B
Benjamin Pasero 已提交
790
				const lastOpenedFolders = this.windowsState.openedFolders.map(o => o.workspacePath);
E
Erich Gamma 已提交
791 792 793 794 795 796 797 798 799 800 801

				// 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
802
			else if (lastActiveFolder && (reopenFolders === ReopenFoldersSetting.ONE || reopenFolders !== ReopenFoldersSetting.NONE)) {
E
Erich Gamma 已提交
803 804 805 806
				candidates.push(lastActiveFolder);
			}
		}

807
		const iPaths = candidates.map(candidate => this.toIPath(candidate, ignoreFileNotFound, cli.goto)).filter(path => !!path);
E
Erich Gamma 已提交
808 809 810 811 812 813 814 815
		if (iPaths.length > 0) {
			return iPaths;
		}

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

816
	private openInBrowserWindow(configuration: IWindowConfiguration, forceNewWindow?: boolean, windowToUse?: VSCodeWindow, emptyWorkspaceBackupFolder?: string): VSCodeWindow {
J
Joao Moreno 已提交
817
		let vscodeWindow: VSCodeWindow;
E
Erich Gamma 已提交
818 819 820 821 822

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

			if (vscodeWindow) {
B
Benjamin Pasero 已提交
823
				vscodeWindow.focus();
E
Erich Gamma 已提交
824 825 826 827 828
			}
		}

		// New window
		if (!vscodeWindow) {
829
			const windowConfig = this.configurationService.getConfiguration<IWindowSettings>('window');
830 831 832 833 834 835 836 837 838 839 840 841 842 843 844 845
			const state = this.getNewWindowState(configuration);

			// Window state is not from a previous session: only allow fullscreen if we inherit it or user wants fullscreen
			let allowFullscreen: boolean;
			if (state.hasDefaultState) {
				allowFullscreen = (windowConfig && windowConfig.newWindowDimensions && ['fullscreen', 'inherit'].indexOf(windowConfig.newWindowDimensions) >= 0);
			}

			// Window state is from a previous session: only allow fullscreen when we got updated or user wants to restore
			else {
				allowFullscreen = this.lifecycleService.wasUpdated || (windowConfig && windowConfig.restoreFullscreen);
			}

			if (state.mode === WindowMode.Fullscreen && !allowFullscreen) {
				state.mode = WindowMode.Normal;
			}
846

J
Johannes Rieken 已提交
847
			vscodeWindow = new VSCodeWindow({
848
				state,
849
				extensionDevelopmentPath: configuration.extensionDevelopmentPath,
850
				isExtensionTestHost: !!configuration.extensionTestsPath,
851
				titleBarStyle: windowConfig ? windowConfig.titleBarStyle : void 0
J
Johannes Rieken 已提交
852 853 854
			},
				this.logService,
				this.environmentService,
855 856
				this.configurationService,
				this.storageService
J
Johannes Rieken 已提交
857
			);
858

E
Erich Gamma 已提交
859 860 861
			WindowsManager.WINDOWS.push(vscodeWindow);

			// Window Events
862 863
			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));
864
			vscodeWindow.win.webContents.on('crashed', () => this.onWindowError(vscodeWindow, WindowError.CRASHED));
865
			vscodeWindow.win.webContents.on('did-start-loading', () => this.onWindowStartLoading(vscodeWindow));
866
			vscodeWindow.win.on('unresponsive', () => this.onWindowError(vscodeWindow, WindowError.UNRESPONSIVE));
E
Erich Gamma 已提交
867 868 869
			vscodeWindow.win.on('closed', () => this.onWindowClosed(vscodeWindow));

			// Lifecycle
J
Joao Moreno 已提交
870
			this.lifecycleService.registerWindow(vscodeWindow);
E
Erich Gamma 已提交
871 872 873 874 875 876
		}

		// Existing window
		else {

			// Some configuration things get inherited if the window is being reused and we are
B
Benjamin Pasero 已提交
877
			// in extension development host mode. These options are all development related.
B
Benjamin Pasero 已提交
878
			const currentWindowConfig = vscodeWindow.config;
A
Alex Dima 已提交
879 880
			if (!configuration.extensionDevelopmentPath && currentWindowConfig && !!currentWindowConfig.extensionDevelopmentPath) {
				configuration.extensionDevelopmentPath = currentWindowConfig.extensionDevelopmentPath;
B
Benjamin Pasero 已提交
881
				configuration.verbose = currentWindowConfig.verbose;
882
				configuration.debugBrkPluginHost = currentWindowConfig.debugBrkPluginHost;
883
				configuration.debugPluginHost = currentWindowConfig.debugPluginHost;
884
				configuration['extensions-dir'] = currentWindowConfig['extensions-dir'];
E
Erich Gamma 已提交
885 886 887 888
			}
		}

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

B
Benjamin Pasero 已提交
892 893 894 895 896
				// Register window for backups
				if (!configuration.extensionDevelopmentPath) {
					this.backupService.registerWindowForBackupsSync(vscodeWindow.id, !configuration.workspacePath, emptyWorkspaceBackupFolder, configuration.workspacePath);
				}

E
Erich Gamma 已提交
897 898 899 900
				// Load it
				vscodeWindow.load(configuration);
			}
		});
901 902

		return vscodeWindow;
E
Erich Gamma 已提交
903 904
	}

905
	private getNewWindowState(configuration: IWindowConfiguration): INewWindowState {
E
Erich Gamma 已提交
906

B
Benjamin Pasero 已提交
907
		// extension development host Window - load from stored settings if any
A
Alex Dima 已提交
908
		if (!!configuration.extensionDevelopmentPath && this.windowsState.lastPluginDevelopmentHostWindow) {
E
Erich Gamma 已提交
909 910 911 912 913
			return this.windowsState.lastPluginDevelopmentHostWindow.uiState;
		}

		// Known Folder - load from stored settings if any
		if (configuration.workspacePath) {
B
Benjamin Pasero 已提交
914
			const stateForWorkspace = this.windowsState.openedFolders.filter(o => this.isPathEqual(o.workspacePath, configuration.workspacePath)).map(o => o.uiState);
E
Erich Gamma 已提交
915 916 917 918 919 920
			if (stateForWorkspace.length) {
				return stateForWorkspace[0];
			}
		}

		// First Window
B
Benjamin Pasero 已提交
921
		const lastActive = this.getLastActiveWindow();
E
Erich Gamma 已提交
922 923 924 925 926 927 928 929 930
		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
931
		let displayToUse: Electron.Display;
B
Benjamin Pasero 已提交
932
		const displays = screen.getAllDisplays();
E
Erich Gamma 已提交
933 934 935 936 937 938 939 940 941 942 943

		// 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 已提交
944
				const cursorPoint = screen.getCursorScreenPoint();
E
Erich Gamma 已提交
945 946 947 948 949 950 951 952
				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());
			}

953
			// fallback to primary display or first display
E
Erich Gamma 已提交
954
			if (!displayToUse) {
955
				displayToUse = screen.getPrimaryDisplay() || displays[0];
E
Erich Gamma 已提交
956 957 958
			}
		}

959
		let state = defaultWindowState() as INewWindowState;
960 961
		state.x = displayToUse.bounds.x + (displayToUse.bounds.width / 2) - (state.width / 2);
		state.y = displayToUse.bounds.y + (displayToUse.bounds.height / 2) - (state.height / 2);
E
Erich Gamma 已提交
962

963 964 965 966 967 968 969 970 971 972 973
		// Check for newWindowDimensions setting and adjust accordingly
		const windowConfig = this.configurationService.getConfiguration<IWindowSettings>('window');
		let ensureNoOverlap = true;
		if (windowConfig && windowConfig.newWindowDimensions) {
			if (windowConfig.newWindowDimensions === 'maximized') {
				state.mode = WindowMode.Maximized;
				ensureNoOverlap = false;
			} else if (windowConfig.newWindowDimensions === 'fullscreen') {
				state.mode = WindowMode.Fullscreen;
				ensureNoOverlap = false;
			} else if (windowConfig.newWindowDimensions === 'inherit' && lastActive) {
B
Benjamin Pasero 已提交
974 975 976 977 978 979 980
				const lastActiveState = lastActive.serializeWindowState();
				if (lastActiveState.mode === WindowMode.Fullscreen) {
					state.mode = WindowMode.Fullscreen; // only take mode (fixes https://github.com/Microsoft/vscode/issues/19331)
				} else {
					state = lastActiveState;
				}

981 982 983 984 985 986 987 988
				ensureNoOverlap = false;
			}
		}

		if (ensureNoOverlap) {
			state = this.ensureNoOverlap(state);
		}

989 990
		state.hasDefaultState = true; // flag as default state

991
		return state;
E
Erich Gamma 已提交
992 993
	}

J
Joao Moreno 已提交
994
	private ensureNoOverlap(state: ISingleWindowState): ISingleWindowState {
E
Erich Gamma 已提交
995 996 997 998
		if (WindowsManager.WINDOWS.length === 0) {
			return state;
		}

999 1000
		const existingWindowBounds = WindowsManager.WINDOWS.map(win => win.getBounds());
		while (existingWindowBounds.some(b => b.x === state.x || b.y === state.y)) {
E
Erich Gamma 已提交
1001 1002 1003 1004 1005 1006 1007
			state.x += 30;
			state.y += 30;
		}

		return state;
	}

1008 1009
	public openFileFolderPicker(forceNewWindow?: boolean, data?: ITelemetryData): void {
		this.doPickAndOpen({ pickFolders: true, pickFiles: true, forceNewWindow }, 'openFileFolder', data);
1010 1011
	}

1012 1013
	public openFilePicker(forceNewWindow?: boolean, path?: string, window?: VSCodeWindow, data?: ITelemetryData): void {
		this.doPickAndOpen({ pickFiles: true, forceNewWindow, path, window }, 'openFile', data);
1014 1015
	}

1016 1017
	public openFolderPicker(forceNewWindow?: boolean, window?: VSCodeWindow, data?: ITelemetryData): void {
		this.doPickAndOpen({ pickFolders: true, forceNewWindow, window }, 'openFolder', data);
E
Erich Gamma 已提交
1018 1019
	}

1020 1021 1022 1023 1024 1025 1026 1027 1028 1029 1030 1031 1032 1033 1034 1035
	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');
	}

1036
	private doPickAndOpen(options: INativeOpenDialogOptions, eventName: string, data?: ITelemetryData): void {
1037
		this.getFileOrFolderPaths(options, (paths: string[]) => {
1038 1039
			const nOfPaths = paths ? paths.length : 0;
			if (nOfPaths) {
1040
				this.open({ context: OpenContext.DIALOG, cli: this.environmentService.args, pathsToOpen: paths, forceNewWindow: options.forceNewWindow });
E
Erich Gamma 已提交
1041
			}
1042 1043 1044 1045 1046
			this.telemetryService.publicLog(eventName, {
				...data,
				outcome: nOfPaths ? 'success' : 'canceled',
				nOfPaths
			});
E
Erich Gamma 已提交
1047 1048 1049
		});
	}

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

B
Benjamin Pasero 已提交
1054
		let pickerProperties: ('openFile' | 'openDirectory' | 'multiSelections' | 'createDirectory')[];
1055
		if (options.pickFiles && options.pickFolders) {
E
Erich Gamma 已提交
1056 1057
			pickerProperties = ['multiSelections', 'openDirectory', 'openFile', 'createDirectory'];
		} else {
1058
			pickerProperties = ['multiSelections', options.pickFolders ? 'openDirectory' : 'openFile', 'createDirectory'];
E
Erich Gamma 已提交
1059 1060
		}

1061
		dialog.showOpenDialog(focussedWindow && focussedWindow.win, {
E
Erich Gamma 已提交
1062 1063
			defaultPath: workingDir,
			properties: pickerProperties
1064
		}, paths => {
E
Erich Gamma 已提交
1065 1066 1067
			if (paths && paths.length > 0) {

				// Remember path in storage for next time
J
Joao Moreno 已提交
1068
				this.storageService.setItem(WindowsManager.workingDirPickerStorageKey, path.dirname(paths[0]));
E
Erich Gamma 已提交
1069 1070 1071 1072 1073 1074 1075 1076 1077

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

1078
	public focusLastActive(cli: ParsedArgs, context: OpenContext): VSCodeWindow {
B
Benjamin Pasero 已提交
1079
		const lastActive = this.getLastActiveWindow();
E
Erich Gamma 已提交
1080
		if (lastActive) {
B
Benjamin Pasero 已提交
1081
			lastActive.focus();
1082 1083

			return lastActive;
E
Erich Gamma 已提交
1084 1085
		}

B
Benjamin Pasero 已提交
1086 1087
		// No window - open new empty one
		const res = this.open({ context, cli, forceEmpty: true });
1088 1089

		return res && res[0];
E
Erich Gamma 已提交
1090 1091
	}

J
Joao Moreno 已提交
1092
	public getLastActiveWindow(): VSCodeWindow {
1093
		return getLastActiveWindow(WindowsManager.WINDOWS);
E
Erich Gamma 已提交
1094 1095
	}

J
Joao Moreno 已提交
1096
	public findWindow(workspacePath: string, filePath?: string, extensionDevelopmentPath?: string): VSCodeWindow {
E
Erich Gamma 已提交
1097 1098 1099
		if (WindowsManager.WINDOWS.length) {

			// Sort the last active window to the front of the array of windows to test
B
Benjamin Pasero 已提交
1100 1101
			const windowsToTest = WindowsManager.WINDOWS.slice(0);
			const lastActiveWindow = this.getLastActiveWindow();
E
Erich Gamma 已提交
1102 1103 1104 1105 1106 1107
			if (lastActiveWindow) {
				windowsToTest.splice(windowsToTest.indexOf(lastActiveWindow), 1);
				windowsToTest.unshift(lastActiveWindow);
			}

			// Find it
1108
			const res = windowsToTest.filter(w => {
E
Erich Gamma 已提交
1109 1110

				// match on workspace
1111
				if (typeof w.openedWorkspacePath === 'string' && (this.isPathEqual(w.openedWorkspacePath, workspacePath))) {
E
Erich Gamma 已提交
1112 1113 1114 1115
					return true;
				}

				// match on file
B
Benjamin Pasero 已提交
1116
				if (typeof w.openedFilePath === 'string' && this.isPathEqual(w.openedFilePath, filePath)) {
E
Erich Gamma 已提交
1117 1118 1119 1120 1121 1122 1123 1124
					return true;
				}

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

1125 1126 1127 1128 1129
				// match on extension development path
				if (typeof extensionDevelopmentPath === 'string' && w.extensionDevelopmentPath === extensionDevelopmentPath) {
					return true;
				}

E
Erich Gamma 已提交
1130 1131 1132 1133 1134 1135 1136 1137 1138 1139 1140
				return false;
			});

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

		return null;
	}

1141 1142
	public openNewWindow(context: OpenContext): void {
		this.open({ context, cli: this.environmentService.args, forceNewWindow: true, forceEmpty: true });
E
Erich Gamma 已提交
1143 1144 1145 1146 1147 1148
	}

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

		if (focusedWindow) {
1149
			focusedWindow.sendWhenReady(channel, ...args);
E
Erich Gamma 已提交
1150 1151 1152 1153
		}
	}

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

1159
			w.sendWhenReady(channel, payload);
E
Erich Gamma 已提交
1160 1161 1162
		});
	}

J
Joao Moreno 已提交
1163
	public getFocusedWindow(): VSCodeWindow {
B
Benjamin Pasero 已提交
1164
		const win = BrowserWindow.getFocusedWindow();
E
Erich Gamma 已提交
1165 1166 1167 1168 1169 1170 1171
		if (win) {
			return this.getWindowById(win.id);
		}

		return null;
	}

J
Joao Moreno 已提交
1172
	public getWindowById(windowId: number): VSCodeWindow {
1173
		const res = WindowsManager.WINDOWS.filter(w => w.id === windowId);
E
Erich Gamma 已提交
1174 1175 1176 1177 1178 1179 1180
		if (res && res.length === 1) {
			return res[0];
		}

		return null;
	}

J
Joao Moreno 已提交
1181
	public getWindows(): VSCodeWindow[] {
E
Erich Gamma 已提交
1182 1183 1184 1185 1186 1187 1188
		return WindowsManager.WINDOWS;
	}

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

J
Joao Moreno 已提交
1189
	private onWindowError(vscodeWindow: VSCodeWindow, error: WindowError): void {
E
Erich Gamma 已提交
1190 1191 1192 1193
		console.error(error === WindowError.CRASHED ? '[VS Code]: render process crashed!' : '[VS Code]: detected unresponsive');

		// Unresponsive
		if (error === WindowError.UNRESPONSIVE) {
1194
			dialog.showMessageBox(vscodeWindow.win, {
B
Benjamin Pasero 已提交
1195
				title: product.nameLong,
E
Erich Gamma 已提交
1196
				type: 'warning',
B
Benjamin Pasero 已提交
1197
				buttons: [nls.localize('reopen', "Reopen"), nls.localize('wait', "Keep Waiting"), nls.localize('close', "Close")],
1198
				message: nls.localize('appStalled', "The window is no longer responding"),
B
Benjamin Pasero 已提交
1199
				detail: nls.localize('appStalledDetail', "You can reopen or close the window or keep waiting."),
E
Erich Gamma 已提交
1200
				noLink: true
1201
			}, result => {
E
Erich Gamma 已提交
1202
				if (result === 0) {
1203 1204
					vscodeWindow.reload();
				} else if (result === 2) {
1205
					this.onBeforeWindowClose(vscodeWindow); // 'close' event will not be fired on destroy(), so run it manually
1206
					vscodeWindow.win.destroy(); // make sure to destroy the window as it is unresponsive
E
Erich Gamma 已提交
1207 1208 1209 1210 1211 1212
				}
			});
		}

		// Crashed
		else {
1213
			dialog.showMessageBox(vscodeWindow.win, {
B
Benjamin Pasero 已提交
1214
				title: product.nameLong,
E
Erich Gamma 已提交
1215
				type: 'warning',
B
Benjamin Pasero 已提交
1216
				buttons: [nls.localize('reopen', "Reopen"), nls.localize('close', "Close")],
1217
				message: nls.localize('appCrashed', "The window has crashed"),
B
Benjamin Pasero 已提交
1218
				detail: nls.localize('appCrashedDetail', "We are sorry for the inconvenience! You can reopen the window to continue where you left off."),
E
Erich Gamma 已提交
1219
				noLink: true
1220
			}, result => {
1221 1222 1223
				if (result === 0) {
					vscodeWindow.reload();
				} else if (result === 1) {
1224
					this.onBeforeWindowClose(vscodeWindow); // 'close' event will not be fired on destroy(), so run it manually
1225 1226
					vscodeWindow.win.destroy(); // make sure to destroy the window as it has crashed
				}
E
Erich Gamma 已提交
1227 1228 1229 1230
			});
		}
	}

J
Joao Moreno 已提交
1231
	private onWindowClosed(win: VSCodeWindow): void {
E
Erich Gamma 已提交
1232 1233 1234 1235 1236

		// Tell window
		win.dispose();

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

		// Emit
1241
		this._onWindowClose.fire(win.id);
E
Erich Gamma 已提交
1242
	}
B
Benjamin Pasero 已提交
1243 1244 1245 1246 1247 1248 1249 1250 1251 1252 1253 1254 1255 1256 1257 1258 1259 1260 1261 1262 1263 1264 1265 1266

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

1268 1269 1270 1271 1272 1273 1274 1275 1276 1277 1278
	private onWindowStartLoading(win: VSCodeWindow): void {
		if (!this.firstWindowLoading) {
			this.firstWindowLoading = true;

			// Apply jump list when our first window is loading. We do this because
			// setJumpList() seems to take quite a bit of time and would block window
			// loading ( = startup performance) significantly otherwise
			setTimeout(() => this.updateWindowsJumpList()); // unwind from onWindowStartLoading event
		}
	}

B
Benjamin Pasero 已提交
1279
	private updateWindowsJumpList(): void {
1280 1281 1282 1283
		if (!platform.isWindows) {
			return; // only on windows
		}

1284 1285 1286 1287
		if (!this.firstWindowLoading) {
			return; // push this out until a window starts loading for perf reasons
		}

1288 1289 1290 1291 1292 1293 1294 1295 1296 1297 1298 1299 1300 1301 1302 1303 1304 1305 1306 1307 1308 1309 1310 1311 1312 1313 1314 1315 1316 1317 1318 1319 1320 1321 1322 1323 1324 1325 1326 1327 1328 1329 1330 1331 1332 1333 1334 1335 1336 1337 1338 1339 1340 1341 1342 1343
		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
		}
	}
1344 1345 1346 1347 1348 1349

	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();
1350
		if (vscodeWindow && vscodeWindow.isExtensionDevelopmentHost && this.getWindowCount() > 1) {
1351 1352 1353 1354 1355 1356 1357 1358 1359 1360
			vscodeWindow.win.close();
		}

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