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

6
import { basename, normalize, join, dirname, extname } from 'path';
7
import * as fs from 'fs';
B
Benjamin Pasero 已提交
8
import { localize } from 'vs/nls';
J
Joao Moreno 已提交
9
import * as arrays from 'vs/base/common/arrays';
10
import { assign, mixin, equals } from 'vs/base/common/objects';
11
import { IBackupMainService, IEmptyWindowBackupInfo } from 'vs/platform/backup/common/backup';
J
Joao Moreno 已提交
12
import { IEnvironmentService, ParsedArgs } from 'vs/platform/environment/common/environment';
B
Benjamin Pasero 已提交
13
import { IStateService } from 'vs/platform/state/common/state';
14
import { CodeWindow, defaultWindowState } from 'vs/code/electron-main/window';
M
Martin Aeschlimann 已提交
15
import { hasArgs, asArray } from 'vs/platform/environment/node/argv';
B
Benjamin Pasero 已提交
16
import { ipcMain as ipc, screen, BrowserWindow, dialog, systemPreferences } from 'electron';
17
import { parseLineAndColumnAware } from 'vs/code/node/paths';
B
Benjamin Pasero 已提交
18
import { ILifecycleService, UnloadReason, IWindowUnloadEvent, LifecycleService } from 'vs/platform/lifecycle/electron-main/lifecycleMain';
19
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
20
import { ILogService } from 'vs/platform/log/common/log';
21
import { IWindowSettings, OpenContext, IPath, IWindowConfiguration, INativeOpenDialogOptions, IPathsToWaitFor, IEnterWorkspaceResult, IMessageBoxResult, INewWindowOptions, IURIToOpen, URIType, OpenDialogOptions } from 'vs/platform/windows/common/windows';
22
import { getLastActiveWindow, findBestWindowOrFolderForFile, findWindowOnWorkspace, findWindowOnExtensionDevelopmentPath, findWindowOnWorkspaceOrFolderUri } from 'vs/code/node/windowsFinder';
M
Matt Bierner 已提交
23
import { Event as CommonEvent, Emitter } from 'vs/base/common/event';
24
import product from 'vs/platform/node/product';
B
Benjamin Pasero 已提交
25
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
26
import { IWindowsMainService, IOpenConfiguration, IWindowsCountChangedEvent, ICodeWindow, IWindowState as ISingleWindowState, WindowMode } from 'vs/platform/windows/electron-main/windows';
B
Benjamin Pasero 已提交
27
import { IHistoryMainService } from 'vs/platform/history/common/history';
B
Benjamin Pasero 已提交
28
import { IProcessEnvironment, isLinux, isMacintosh, isWindows } from 'vs/base/common/platform';
29
import { IWorkspacesMainService, IWorkspaceIdentifier, WORKSPACE_FILTER, ISingleFolderWorkspaceIdentifier, isSingleFolderWorkspaceIdentifier, WORKSPACE_EXTENSION } from 'vs/platform/workspaces/common/workspaces';
B
Benjamin Pasero 已提交
30
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
31
import { mnemonicButtonLabel } from 'vs/base/common/labels';
J
Johannes Rieken 已提交
32
import { Schemas } from 'vs/base/common/network';
33
import { normalizeNFC } from 'vs/base/common/normalization';
34
import { URI } from 'vs/base/common/uri';
35
import { Queue, timeout } from 'vs/base/common/async';
B
Benjamin Pasero 已提交
36
import { exists } from 'vs/base/node/pfs';
M
Martin Aeschlimann 已提交
37
import { getComparisonKey, isEqual, normalizePath, basename as resourcesBasename, fsPath } from 'vs/base/common/resources';
38
import { endsWith } from 'vs/base/common/strings';
M
Martin Aeschlimann 已提交
39
import { getRemoteAuthority } from 'vs/platform/remote/common/remoteHosts';
M
Martin Aeschlimann 已提交
40
import { restoreWindowsState, WindowsStateStorageData, getWindowsStateStoreData } from 'vs/code/electron-main/windowsStateStorage';
E
Erich Gamma 已提交
41

42 43 44
const enum WindowError {
	UNRESPONSIVE = 1,
	CRASHED = 2
E
Erich Gamma 已提交
45 46
}

M
Martin Aeschlimann 已提交
47 48 49 50 51 52 53 54 55 56 57 58 59 60
export interface IWindowState {
	workspace?: IWorkspaceIdentifier;
	folderUri?: URI;
	backupPath?: string;
	remoteAuthority?: string;
	uiState: ISingleWindowState;
}

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

61 62 63 64
interface INewWindowState extends ISingleWindowState {
	hasDefaultState?: boolean;
}

65
type RestoreWindowsSetting = 'all' | 'folders' | 'one' | 'none';
66

B
Benjamin Pasero 已提交
67 68 69
interface IOpenBrowserWindowOptions {
	userEnv?: IProcessEnvironment;
	cli?: ParsedArgs;
70

71
	workspace?: IWorkspaceIdentifier;
72
	folderUri?: URI;
B
Benjamin Pasero 已提交
73

M
Matt Bierner 已提交
74
	remoteAuthority?: string;
M
Martin Aeschlimann 已提交
75

B
Benjamin Pasero 已提交
76 77
	initialStartup?: boolean;

78
	fileInputs?: IFileInputs;
B
Benjamin Pasero 已提交
79 80

	forceNewWindow?: boolean;
81
	forceNewTabbedWindow?: boolean;
82
	windowToUse?: ICodeWindow;
B
Benjamin Pasero 已提交
83

84 85 86 87 88 89 90
	emptyWindowBackupInfo?: IEmptyWindowBackupInfo;
}

interface IPathParseOptions {
	ignoreFileNotFound?: boolean;
	gotoLineMode?: boolean;
	forceOpenWorkspaceAsFile?: boolean;
M
Martin Aeschlimann 已提交
91
	remoteAuthority?: string;
92 93 94 95 96 97 98
}

interface IFileInputs {
	filesToOpen: IPath[];
	filesToCreate: IPath[];
	filesToDiff: IPath[];
	filesToWait?: IPathsToWaitFor;
M
Martin Aeschlimann 已提交
99
	remoteAuthority?: string;
B
Benjamin Pasero 已提交
100 101
}

B
Benjamin Pasero 已提交
102
interface IPathToOpen extends IPath {
103

104
	// the workspace for a Code instance to open
105
	workspace?: IWorkspaceIdentifier;
106

107
	// the folder path for a Code instance to open
108
	folderUri?: URI;
109

110
	// the backup path for a Code instance to use
111 112
	backupPath?: string;

M
Martin Aeschlimann 已提交
113 114 115
	// the remote authority for the Code instance to open. Undefined if not remote.
	remoteAuthority?: string;

116 117 118 119
	// indicator to create the file path in the Code instance
	createFilePath?: boolean;
}

J
Joao Moreno 已提交
120
export class WindowsManager implements IWindowsMainService {
J
Joao Moreno 已提交
121

122
	_serviceBrand: any;
E
Erich Gamma 已提交
123

124
	private static readonly windowsStateStorageKey = 'windowsState';
E
Erich Gamma 已提交
125

126
	private static WINDOWS: ICodeWindow[] = [];
E
Erich Gamma 已提交
127

B
Benjamin Pasero 已提交
128
	private initialUserEnv: IProcessEnvironment;
129

E
Erich Gamma 已提交
130
	private windowsState: IWindowsState;
131
	private lastClosedWindowState?: IWindowState;
E
Erich Gamma 已提交
132

133
	private dialogs: Dialogs;
134
	private workspacesManager: WorkspacesManager;
B
Benjamin Pasero 已提交
135

136 137
	private _onWindowReady = new Emitter<ICodeWindow>();
	onWindowReady: CommonEvent<ICodeWindow> = this._onWindowReady.event;
138 139 140 141

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

142 143 144
	private _onWindowLoad = new Emitter<number>();
	onWindowLoad: CommonEvent<number> = this._onWindowLoad.event;

B
Benjamin Pasero 已提交
145 146 147
	private _onWindowsCountChanged = new Emitter<IWindowsCountChangedEvent>();
	onWindowsCountChanged: CommonEvent<IWindowsCountChangedEvent> = this._onWindowsCountChanged.event;

J
Joao Moreno 已提交
148
	constructor(
B
Benjamin Pasero 已提交
149
		private readonly machineId: string,
150 151 152 153 154 155 156 157 158 159
		@ILogService private readonly logService: ILogService,
		@IStateService private readonly stateService: IStateService,
		@IEnvironmentService private readonly environmentService: IEnvironmentService,
		@ILifecycleService private readonly lifecycleService: ILifecycleService,
		@IBackupMainService private readonly backupMainService: IBackupMainService,
		@ITelemetryService private readonly telemetryService: ITelemetryService,
		@IConfigurationService private readonly configurationService: IConfigurationService,
		@IHistoryMainService private readonly historyMainService: IHistoryMainService,
		@IWorkspacesMainService private readonly workspacesMainService: IWorkspacesMainService,
		@IInstantiationService private readonly instantiationService: IInstantiationService
160
	) {
M
Martin Aeschlimann 已提交
161
		const windowsStateStoreData = this.stateService.getItem<WindowsStateStorageData>(WindowsManager.windowsStateStorageKey);
162 163

		this.windowsState = restoreWindowsState(windowsStateStoreData);
164 165 166
		if (!Array.isArray(this.windowsState.openedWindows)) {
			this.windowsState.openedWindows = [];
		}
167

B
Benjamin Pasero 已提交
168
		this.dialogs = new Dialogs(environmentService, telemetryService, stateService, this);
169
		this.workspacesManager = new WorkspacesManager(workspacesMainService, backupMainService, environmentService, historyMainService, this);
170
	}
J
Joao Moreno 已提交
171

B
Benjamin Pasero 已提交
172
	ready(initialUserEnv: IProcessEnvironment): void {
173
		this.initialUserEnv = initialUserEnv;
174 175

		this.registerListeners();
E
Erich Gamma 已提交
176 177 178
	}

	private registerListeners(): void {
179

B
Benjamin Pasero 已提交
180 181 182
		// React to workbench ready events from windows
		ipc.on('vscode:workbenchReady', (event: any, windowId: number) => {
			this.logService.trace('IPC#vscode-workbenchReady');
E
Erich Gamma 已提交
183

B
Benjamin Pasero 已提交
184
			const win = this.getWindowById(windowId);
E
Erich Gamma 已提交
185 186 187 188
			if (win) {
				win.setReady();

				// Event
189
				this._onWindowReady.fire(win);
E
Erich Gamma 已提交
190 191 192
			}
		});

193 194 195 196 197 198 199 200 201 202 203
		// React to HC color scheme changes (Windows)
		if (isWindows) {
			systemPreferences.on('inverted-color-scheme-changed', () => {
				if (systemPreferences.isInvertedColorScheme()) {
					this.sendToAll('vscode:enterHighContrast');
				} else {
					this.sendToAll('vscode:leaveHighContrast');
				}
			});
		}

204 205
		// Handle various lifecycle events around windows
		this.lifecycleService.onBeforeWindowUnload(e => this.onBeforeWindowUnload(e));
B
Benjamin Pasero 已提交
206
		this.lifecycleService.onBeforeWindowClose(window => this.onBeforeWindowClose(window));
207
		this.lifecycleService.onBeforeShutdown(() => this.onBeforeShutdown());
208 209 210 211 212
		this.onWindowsCountChanged(e => {
			if (e.newCount - e.oldCount > 0) {
				// clear last closed window state when a new window opens. this helps on macOS where
				// otherwise closing the last window, opening a new window and then quitting would
				// use the state of the previously closed window when restarting.
R
Rob Lourens 已提交
213
				this.lastClosedWindowState = undefined;
214 215
			}
		});
216 217
	}

218
	// Note that onBeforeShutdown() and onBeforeWindowClose() are fired in different order depending on the OS:
219
	// - macOS: since the app will not quit when closing the last window, you will always first get
220
	//          the onBeforeShutdown() event followed by N onbeforeWindowClose() events for each window
221 222
	// - 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()
223
	//          and then onBeforeShutdown(). Using the quit action however will first issue onBeforeShutdown()
224
	//          and then onBeforeWindowClose().
225 226 227 228 229 230 231
	//
	// Here is the behaviour on different OS dependig on action taken (Electron 1.7.x):
	//
	// Legend
	// -  quit(N): quit application with N windows opened
	// - close(1): close one window via the window close button
	// - closeAll: close all windows via the taskbar command
232
	// - onBeforeShutdown(N): number of windows reported in this event handler
233 234 235
	// - onBeforeWindowClose(N, M): number of windows reported and quitRequested boolean in this event handler
	//
	// macOS
236 237 238
	// 	-     quit(1): onBeforeShutdown(1), onBeforeWindowClose(1, true)
	// 	-     quit(2): onBeforeShutdown(2), onBeforeWindowClose(2, true), onBeforeWindowClose(2, true)
	// 	-     quit(0): onBeforeShutdown(0)
239 240 241
	// 	-    close(1): onBeforeWindowClose(1, false)
	//
	// Windows
242 243
	// 	-     quit(1): onBeforeShutdown(1), onBeforeWindowClose(1, true)
	// 	-     quit(2): onBeforeShutdown(2), onBeforeWindowClose(2, true), onBeforeWindowClose(2, true)
244
	// 	-    close(1): onBeforeWindowClose(2, false)[not last window]
245 246
	// 	-    close(1): onBeforeWindowClose(1, false), onBeforeShutdown(0)[last window]
	// 	- closeAll(2): onBeforeWindowClose(2, false), onBeforeWindowClose(2, false), onBeforeShutdown(0)
247 248
	//
	// Linux
249 250
	// 	-     quit(1): onBeforeShutdown(1), onBeforeWindowClose(1, true)
	// 	-     quit(2): onBeforeShutdown(2), onBeforeWindowClose(2, true), onBeforeWindowClose(2, true)
251
	// 	-    close(1): onBeforeWindowClose(2, false)[not last window]
252 253
	// 	-    close(1): onBeforeWindowClose(1, false), onBeforeShutdown(0)[last window]
	// 	- closeAll(2): onBeforeWindowClose(2, false), onBeforeWindowClose(2, false), onBeforeShutdown(0)
254
	//
255
	private onBeforeShutdown(): void {
256
		const currentWindowsState: IWindowsState = {
257
			openedWindows: [],
258
			lastPluginDevelopmentHostWindow: this.windowsState.lastPluginDevelopmentHostWindow,
259
			lastActiveWindow: this.lastClosedWindowState
260 261 262 263 264 265 266 267
		};

		// 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 已提交
268

269
			if (activeWindow) {
270
				currentWindowsState.lastActiveWindow = this.toWindowState(activeWindow);
E
Erich Gamma 已提交
271
			}
272 273 274 275 276
		}

		// 2.) Find extension host window
		const extensionHostWindow = WindowsManager.WINDOWS.filter(w => w.isExtensionDevelopmentHost && !w.isExtensionTestHost)[0];
		if (extensionHostWindow) {
277
			currentWindowsState.lastPluginDevelopmentHostWindow = this.toWindowState(extensionHostWindow);
278
		}
E
Erich Gamma 已提交
279

280
		// 3.) All windows (except extension host) for N >= 2 to support restoreWindows: all or for auto update
281 282 283 284 285
		//
		// 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) {
286
			currentWindowsState.openedWindows = WindowsManager.WINDOWS.filter(w => !w.isExtensionDevelopmentHost).map(w => this.toWindowState(w));
287
		}
E
Erich Gamma 已提交
288

289
		// Persist
290
		this.stateService.setItem(WindowsManager.windowsStateStorageKey, getWindowsStateStoreData(currentWindowsState));
291
	}
292

293
	// See note on #onBeforeShutdown() for details how these events are flowing
294
	private onBeforeWindowClose(win: ICodeWindow): void {
B
Benjamin Pasero 已提交
295
		if (this.lifecycleService.quitRequested) {
296 297 298 299
			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
300
		const state: IWindowState = this.toWindowState(win);
301 302 303 304
		if (win.isExtensionDevelopmentHost && !win.isExtensionTestHost) {
			this.windowsState.lastPluginDevelopmentHostWindow = state; // do not let test run window state overwrite our extension development state
		}

305
		// Any non extension host window with same workspace or folder
306
		else if (!win.isExtensionDevelopmentHost && (!!win.openedWorkspace || !!win.openedFolderUri)) {
307
			this.windowsState.openedWindows.forEach(o => {
B
fix npe  
Benjamin Pasero 已提交
308
				const sameWorkspace = win.openedWorkspace && o.workspace && o.workspace.id === win.openedWorkspace.id;
309
				const sameFolder = win.openedFolderUri && o.folderUri && isEqual(o.folderUri, win.openedFolderUri);
310 311

				if (sameWorkspace || sameFolder) {
312 313 314 315 316 317 318
					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.
319 320 321
		// On macOS we keep the last closed window state ready in case the user wants to quit right after or
		// wants to open another window, in which case we use this state over the persisted one.
		if (this.getWindowCount() === 1) {
322 323
			this.lastClosedWindowState = state;
		}
E
Erich Gamma 已提交
324 325
	}

326
	private toWindowState(win: ICodeWindow): IWindowState {
327
		return {
328
			workspace: win.openedWorkspace,
329
			folderUri: win.openedFolderUri,
330
			backupPath: win.backupPath,
M
Martin Aeschlimann 已提交
331
			remoteAuthority: win.remoteAuthority,
332 333 334 335
			uiState: win.serializeWindowState()
		};
	}

B
Benjamin Pasero 已提交
336
	open(openConfig: IOpenConfiguration): ICodeWindow[] {
337
		this.logService.trace('windowsManager#open');
338
		openConfig = this.validateOpenConfig(openConfig);
339

340
		let pathsToOpen = this.getPathsToOpen(openConfig);
341 342 343

		// When run with --add, take the folders that are to be opened as
		// folders that should be added to the currently active window.
344
		let foldersToAdd: URI[] = [];
345
		if (openConfig.addMode) {
346
			foldersToAdd = pathsToOpen.filter(path => !!path.folderUri).map(path => path.folderUri!);
347
			pathsToOpen = pathsToOpen.filter(path => !path.folderUri);
348
		}
E
Erich Gamma 已提交
349

350
		// collect all file inputs
351
		let fileInputs: IFileInputs | undefined;
352 353 354
		for (const path of pathsToOpen) {
			if (path.fileUri) {
				if (!fileInputs) {
M
Martin Aeschlimann 已提交
355
					fileInputs = { filesToCreate: [], filesToOpen: [], filesToDiff: [], remoteAuthority: path.remoteAuthority };
356 357 358 359 360 361 362 363
				}
				if (!path.createFilePath) {
					fileInputs.filesToOpen.push(path);
				} else {
					fileInputs.filesToCreate.push(path);
				}
			}
		}
364 365 366

		// When run with --diff, take the files to open as files to diff
		// if there are exactly two files provided.
367 368 369 370
		if (fileInputs && openConfig.diffMode && fileInputs.filesToOpen.length === 2) {
			fileInputs.filesToDiff = fileInputs.filesToOpen;
			fileInputs.filesToOpen = [];
			fileInputs.filesToCreate = []; // diff ignores other files that do not exist
E
Erich Gamma 已提交
371 372
		}

373
		// When run with --wait, make sure we keep the paths to wait for
374 375
		if (fileInputs && openConfig.cli.wait && openConfig.cli.waitMarkerFilePath) {
			fileInputs.filesToWait = { paths: [...fileInputs.filesToDiff, ...fileInputs.filesToOpen, ...fileInputs.filesToCreate], waitMarkerFilePath: openConfig.cli.waitMarkerFilePath };
376 377
		}

378 379 380
		//
		// These are windows to open to show workspaces
		//
381
		const workspacesToOpen = arrays.distinct(arrays.coalesce(pathsToOpen.map(win => win.workspace)), workspace => workspace.id); // prevent duplicates
382 383 384 385

		//
		// These are windows to open to show either folders or files (including diffing files or creating them)
		//
386
		const foldersToOpen = arrays.distinct(arrays.coalesce(pathsToOpen.filter(win => win.folderUri && !win.fileUri).map(win => win.folderUri)), folder => getComparisonKey(folder)); // prevent duplicates
387

388
		//
389
		// These are windows to restore because of hot-exit or from previous session (only performed once on startup!)
390
		//
391
		let foldersToRestore: URI[] = [];
392
		let workspacesToRestore: IWorkspaceIdentifier[] = [];
393
		let emptyToRestore: IEmptyWindowBackupInfo[] = [];
B
Benjamin Pasero 已提交
394
		if (openConfig.initialStartup && !openConfig.cli.extensionDevelopmentPath && !openConfig.cli['disable-restore-windows']) {
395
			foldersToRestore = this.backupMainService.getFolderBackupPaths();
396

B
Benjamin Pasero 已提交
397 398
			workspacesToRestore = this.backupMainService.getWorkspaceBackups();						// collect from workspaces with hot-exit backups
			workspacesToRestore.push(...this.workspacesMainService.getUntitledWorkspacesSync());	// collect from previous window session
399

B
Benjamin Pasero 已提交
400
			emptyToRestore = this.backupMainService.getEmptyWindowBackupPaths();
401
			emptyToRestore.push(...pathsToOpen.filter(w => !w.workspace && !w.folderUri && w.backupPath).map(w => ({ backupFolder: basename(w.backupPath!), remoteAuthority: w.remoteAuthority }))); // add empty windows with backupPath
M
Matt Bierner 已提交
402
			emptyToRestore = arrays.distinct(emptyToRestore, info => info.backupFolder!); // prevent duplicates
403
		}
404

405 406 407
		//
		// These are empty windows to open
		//
408
		const emptyToOpen = pathsToOpen.filter(win => !win.workspace && !win.folderUri && !win.fileUri && !win.backupPath).length;
409

410
		// Open based on config
411
		const usedWindows = this.doOpen(openConfig, workspacesToOpen, workspacesToRestore, foldersToOpen, foldersToRestore, emptyToRestore, emptyToOpen, fileInputs, foldersToAdd);
412

413
		// Make sure to pass focus to the most relevant of the windows if we open multiple
414
		if (usedWindows.length > 1) {
415

416
			let focusLastActive = this.windowsState.lastActiveWindow && !openConfig.forceEmpty && !hasArgs(openConfig.cli._) && !hasArgs(openConfig.cli['file-uri']) && !hasArgs(openConfig.cli['folder-uri']) && !(openConfig.urisToOpen && openConfig.urisToOpen.length);
417 418
			let focusLastOpened = true;
			let focusLastWindow = true;
419

420 421
			// 1.) focus last active window if we are not instructed to open any paths
			if (focusLastActive) {
422 423 424
				const lastActiveWindow = usedWindows.filter(w => w.backupPath === this.windowsState.lastActiveWindow!.backupPath);
				if (lastActiveWindow.length) {
					lastActiveWindow[0].focus();
425 426
					focusLastOpened = false;
					focusLastWindow = false;
427 428 429
				}
			}

430 431 432 433 434
			// 2.) if instructed to open paths, focus last window which is not restored
			if (focusLastOpened) {
				for (let i = usedWindows.length - 1; i >= 0; i--) {
					const usedWindow = usedWindows[i];
					if (
M
Matt Bierner 已提交
435
						(usedWindow.openedWorkspace && workspacesToRestore.some(workspace => workspace.id === usedWindow.openedWorkspace!.id)) ||	// skip over restored workspace
436
						(usedWindow.openedFolderUri && foldersToRestore.some(folder => isEqual(folder, usedWindow.openedFolderUri))) ||				// skip over restored folder
M
Matt Bierner 已提交
437
						(usedWindow.backupPath && emptyToRestore.some(empty => empty.backupFolder === basename(usedWindow.backupPath!)))				// skip over restored empty window
438 439 440 441 442 443 444 445 446 447 448 449
					) {
						continue;
					}

					usedWindow.focus();
					focusLastWindow = false;
					break;
				}
			}

			// 3.) finally, always ensure to have at least last used window focused
			if (focusLastWindow) {
450
				usedWindows[usedWindows.length - 1].focus();
451 452
			}
		}
453

454 455
		// Remember in recent document list (unless this opens for extension development)
		// Also do not add paths when files are opened for diffing, only if opened individually
456
		if (!usedWindows.some(w => w.isExtensionDevelopmentHost) && !openConfig.diffMode) {
457
			const recentlyOpenedWorkspaces: Array<IWorkspaceIdentifier | ISingleFolderWorkspaceIdentifier> = [];
458
			const recentlyOpenedFiles: URI[] = [];
459

B
Benjamin Pasero 已提交
460
			pathsToOpen.forEach(win => {
M
Matt Bierner 已提交
461 462 463 464
				if (win.workspace) {
					recentlyOpenedWorkspaces.push(win.workspace);
				} else if (win.folderUri) {
					recentlyOpenedWorkspaces.push(win.folderUri);
465 466
				} else if (win.fileUri) {
					recentlyOpenedFiles.push(win.fileUri);
467 468 469
				}
			});

470 471 472
			if (!this.environmentService.skipAddToRecentlyOpened) {
				this.historyMainService.addRecentlyOpened(recentlyOpenedWorkspaces, recentlyOpenedFiles);
			}
473
		}
474

475
		// If we got started with --wait from the CLI, we need to signal to the outside when the window
476 477
		// used for the edit operation is closed or loaded to a different folder so that the waiting
		// process can continue. We do this by deleting the waitMarkerFilePath.
478
		if (openConfig.context === OpenContext.CLI && openConfig.cli.wait && openConfig.cli.waitMarkerFilePath && usedWindows.length === 1 && usedWindows[0]) {
M
Matt Bierner 已提交
479
			this.waitForWindowCloseOrLoad(usedWindows[0].id).then(() => fs.unlink(openConfig.cli.waitMarkerFilePath!, _error => undefined));
480 481
		}

482 483 484
		return usedWindows;
	}

485 486 487 488 489 490 491 492 493 494
	private validateOpenConfig(config: IOpenConfiguration): IOpenConfiguration {

		// Make sure addMode is only enabled if we have an active window
		if (config.addMode && (config.initialStartup || !this.getLastActiveWindow())) {
			config.addMode = false;
		}

		return config;
	}

495 496
	private doOpen(
		openConfig: IOpenConfiguration,
497 498
		workspacesToOpen: IWorkspaceIdentifier[],
		workspacesToRestore: IWorkspaceIdentifier[],
499 500
		foldersToOpen: URI[],
		foldersToRestore: URI[],
501
		emptyToRestore: IEmptyWindowBackupInfo[],
502
		emptyToOpen: number,
503
		fileInputs: IFileInputs | undefined,
504
		foldersToAdd: URI[]
505
	) {
506
		const usedWindows: ICodeWindow[] = [];
507

B
Benjamin Pasero 已提交
508 509
		// Settings can decide if files/folders open in new window or not
		let { openFolderInNewWindow, openFilesInNewWindow } = this.shouldOpenNewWindow(openConfig);
510

511 512
		// Handle folders to add by looking for the last active workspace (not on initial startup)
		if (!openConfig.initialStartup && foldersToAdd.length > 0) {
M
Martin Aeschlimann 已提交
513
			const authority = getRemoteAuthority(foldersToAdd[0]);
M
Matt Bierner 已提交
514
			const lastActiveWindow = authority ? this.getLastActiveWindowForAuthority(authority) : undefined;
515
			if (lastActiveWindow) {
516
				usedWindows.push(this.doAddFoldersToExistingWindow(lastActiveWindow, foldersToAdd));
517 518 519
			}
		}

B
Benjamin Pasero 已提交
520
		// 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
521
		const potentialWindowsCount = foldersToOpen.length + foldersToRestore.length + workspacesToOpen.length + workspacesToRestore.length + emptyToRestore.length;
522
		if (potentialWindowsCount === 0 && fileInputs) {
E
Erich Gamma 已提交
523

524
			// Find suitable window or folder path to open files in
525
			const fileToCheck = fileInputs.filesToOpen[0] || fileInputs.filesToCreate[0] || fileInputs.filesToDiff[0];
M
Martin Aeschlimann 已提交
526
			// only look at the windows with correct authority
M
Matt Bierner 已提交
527
			const windows = WindowsManager.WINDOWS.filter(w => w.remoteAuthority === fileInputs!.remoteAuthority);
528

529
			let bestWindowOrFolder = findBestWindowOrFolderForFile({
530
				windows,
531 532
				newWindow: openFilesInNewWindow,
				context: openConfig.context,
533
				fileUri: fileToCheck && fileToCheck.fileUri,
534
				workspaceResolver: workspace => workspace.configPath.scheme === Schemas.file ? this.workspacesMainService.resolveWorkspaceSync(fsPath(workspace.configPath)) : null
535
			});
B
Benjamin Pasero 已提交
536

537 538 539 540 541 542 543 544 545
			// We found a window to open the files in
			if (bestWindowOrFolder instanceof CodeWindow) {

				// Window is workspace
				if (bestWindowOrFolder.openedWorkspace) {
					workspacesToOpen.push(bestWindowOrFolder.openedWorkspace);
				}

				// Window is single folder
546 547
				else if (bestWindowOrFolder.openedFolderUri) {
					foldersToOpen.push(bestWindowOrFolder.openedFolderUri);
548 549 550 551 552 553
				}

				// Window is empty
				else {

					// Do open files
554
					usedWindows.push(this.doOpenFilesInExistingWindow(openConfig, bestWindowOrFolder, fileInputs));
555 556

					// Reset these because we handled them
R
Rob Lourens 已提交
557
					fileInputs = undefined;
558
				}
559 560 561
			}

			// Finally, if no window or folder is found, just open the files in an empty window
E
Erich Gamma 已提交
562
			else {
B
Benjamin Pasero 已提交
563
				usedWindows.push(this.openInBrowserWindow({
B
Benjamin Pasero 已提交
564 565 566
					userEnv: openConfig.userEnv,
					cli: openConfig.cli,
					initialStartup: openConfig.initialStartup,
567
					fileInputs,
568
					forceNewWindow: true,
M
Martin Aeschlimann 已提交
569
					remoteAuthority: fileInputs.remoteAuthority,
570
					forceNewTabbedWindow: openConfig.forceNewTabbedWindow
B
Benjamin Pasero 已提交
571
				}));
E
Erich Gamma 已提交
572

573
				// Reset these because we handled them
R
Rob Lourens 已提交
574
				fileInputs = undefined;
E
Erich Gamma 已提交
575 576 577
			}
		}

578
		// Handle workspaces to open (instructed and to restore)
579
		const allWorkspacesToOpen = arrays.distinct([...workspacesToRestore, ...workspacesToOpen], workspace => workspace.id); // prevent duplicates
580 581 582 583 584 585
		if (allWorkspacesToOpen.length > 0) {

			// Check for existing instances
			const windowsOnWorkspace = arrays.coalesce(allWorkspacesToOpen.map(workspaceToOpen => findWindowOnWorkspace(WindowsManager.WINDOWS, workspaceToOpen)));
			if (windowsOnWorkspace.length > 0) {
				const windowOnWorkspace = windowsOnWorkspace[0];
R
Rob Lourens 已提交
586
				const fileInputsForWindow = (fileInputs && fileInputs.remoteAuthority === windowOnWorkspace.remoteAuthority) ? fileInputs : undefined;
587 588

				// Do open files
589
				usedWindows.push(this.doOpenFilesInExistingWindow(openConfig, windowOnWorkspace, fileInputsForWindow));
590 591

				// Reset these because we handled them
592
				if (fileInputsForWindow) {
R
Rob Lourens 已提交
593
					fileInputs = undefined;
594
				}
595 596 597 598 599 600

				openFolderInNewWindow = true; // any other folders to open must open in new window then
			}

			// Open remaining ones
			allWorkspacesToOpen.forEach(workspaceToOpen => {
M
Matt Bierner 已提交
601
				if (windowsOnWorkspace.some(win => win.openedWorkspace!.id === workspaceToOpen.id)) {
602 603 604
					return; // ignore folders that are already open
				}

R
Rob Lourens 已提交
605
				const fileInputsForWindow = (fileInputs && !fileInputs.remoteAuthority) ? fileInputs : undefined;
606

607
				// Do open folder
608
				usedWindows.push(this.doOpenFolderOrWorkspace(openConfig, { workspace: workspaceToOpen }, openFolderInNewWindow, fileInputsForWindow));
609 610

				// Reset these because we handled them
611
				if (fileInputsForWindow) {
R
Rob Lourens 已提交
612
					fileInputs = undefined;
613
				}
614 615 616 617 618

				openFolderInNewWindow = true; // any other folders to open must open in new window then
			});
		}

619
		// Handle folders to open (instructed and to restore)
620 621
		const allFoldersToOpen = arrays.distinct([...foldersToRestore, ...foldersToOpen], folder => getComparisonKey(folder)); // prevent duplicates

622
		if (allFoldersToOpen.length > 0) {
E
Erich Gamma 已提交
623 624

			// Check for existing instances
625
			const windowsOnFolderPath = arrays.coalesce(allFoldersToOpen.map(folderToOpen => findWindowOnWorkspace(WindowsManager.WINDOWS, folderToOpen)));
626
			if (windowsOnFolderPath.length > 0) {
627
				const windowOnFolderPath = windowsOnFolderPath[0];
R
Rob Lourens 已提交
628
				const fileInputsForWindow = fileInputs && fileInputs.remoteAuthority === windowOnFolderPath.remoteAuthority ? fileInputs : undefined;
E
Erich Gamma 已提交
629

630
				// Do open files
631
				usedWindows.push(this.doOpenFilesInExistingWindow(openConfig, windowOnFolderPath, fileInputsForWindow));
632

E
Erich Gamma 已提交
633
				// Reset these because we handled them
634
				if (fileInputsForWindow) {
R
Rob Lourens 已提交
635
					fileInputs = undefined;
636
				}
E
Erich Gamma 已提交
637

B
Benjamin Pasero 已提交
638
				openFolderInNewWindow = true; // any other folders to open must open in new window then
E
Erich Gamma 已提交
639 640 641
			}

			// Open remaining ones
642
			allFoldersToOpen.forEach(folderToOpen => {
643

644
				if (windowsOnFolderPath.some(win => isEqual(win.openedFolderUri, folderToOpen))) {
E
Erich Gamma 已提交
645 646 647
					return; // ignore folders that are already open
				}

M
Martin Aeschlimann 已提交
648
				const remoteAuthority = getRemoteAuthority(folderToOpen);
R
Rob Lourens 已提交
649
				const fileInputsForWindow = (fileInputs && fileInputs.remoteAuthority === remoteAuthority) ? fileInputs : undefined;
650

651
				// Do open folder
M
Martin Aeschlimann 已提交
652
				usedWindows.push(this.doOpenFolderOrWorkspace(openConfig, { folderUri: folderToOpen, remoteAuthority }, openFolderInNewWindow, fileInputsForWindow));
E
Erich Gamma 已提交
653 654

				// Reset these because we handled them
655
				if (fileInputsForWindow) {
R
Rob Lourens 已提交
656
					fileInputs = undefined;
657
				}
E
Erich Gamma 已提交
658

B
Benjamin Pasero 已提交
659
				openFolderInNewWindow = true; // any other folders to open must open in new window then
E
Erich Gamma 已提交
660 661 662
			});
		}

663
		// Handle empty to restore
664
		if (emptyToRestore.length > 0) {
665
			emptyToRestore.forEach(emptyWindowBackupInfo => {
M
Martin Aeschlimann 已提交
666
				const remoteAuthority = emptyWindowBackupInfo.remoteAuthority;
R
Rob Lourens 已提交
667
				const fileInputsForWindow = (fileInputs && fileInputs.remoteAuthority === remoteAuthority) ? fileInputs : undefined;
668

B
Benjamin Pasero 已提交
669
				usedWindows.push(this.openInBrowserWindow({
B
Benjamin Pasero 已提交
670 671 672
					userEnv: openConfig.userEnv,
					cli: openConfig.cli,
					initialStartup: openConfig.initialStartup,
673
					fileInputs: fileInputsForWindow,
M
Martin Aeschlimann 已提交
674
					remoteAuthority,
B
Benjamin Pasero 已提交
675
					forceNewWindow: true,
676
					forceNewTabbedWindow: openConfig.forceNewTabbedWindow,
677
					emptyWindowBackupInfo
B
Benjamin Pasero 已提交
678
				}));
679

B
wip  
Benjamin Pasero 已提交
680
				// Reset these because we handled them
681
				if (fileInputsForWindow) {
R
Rob Lourens 已提交
682
					fileInputs = undefined;
683
				}
B
wip  
Benjamin Pasero 已提交
684

B
Benjamin Pasero 已提交
685
				openFolderInNewWindow = true; // any other folders to open must open in new window then
686 687
			});
		}
B
Benjamin Pasero 已提交
688

689
		// Handle empty to open (only if no other window opened)
690 691 692 693
		if (usedWindows.length === 0 || fileInputs) {
			if (fileInputs && !emptyToOpen) {
				emptyToOpen++;
			}
R
Rob Lourens 已提交
694
			const remoteAuthority = fileInputs ? fileInputs.remoteAuthority : (openConfig.cli && openConfig.cli.remote || undefined);
695
			for (let i = 0; i < emptyToOpen; i++) {
B
Benjamin Pasero 已提交
696
				usedWindows.push(this.openInBrowserWindow({
B
Benjamin Pasero 已提交
697 698 699
					userEnv: openConfig.userEnv,
					cli: openConfig.cli,
					initialStartup: openConfig.initialStartup,
M
Martin Aeschlimann 已提交
700
					remoteAuthority,
701
					forceNewWindow: openFolderInNewWindow,
702 703
					forceNewTabbedWindow: openConfig.forceNewTabbedWindow,
					fileInputs
B
Benjamin Pasero 已提交
704
				}));
E
Erich Gamma 已提交
705

706
				// Reset these because we handled them
R
Rob Lourens 已提交
707
				fileInputs = undefined;
708
				openFolderInNewWindow = true; // any other window to open must open in new window then
709 710
			}
		}
E
Erich Gamma 已提交
711

712
		return arrays.distinct(usedWindows);
E
Erich Gamma 已提交
713 714
	}

715
	private doOpenFilesInExistingWindow(configuration: IOpenConfiguration, window: ICodeWindow, fileInputs?: IFileInputs): ICodeWindow {
716 717
		window.focus(); // make sure window has focus

B
Benjamin Pasero 已提交
718 719 720 721 722 723 724 725 726 727 728 729 730
		const params: { filesToOpen?, filesToCreate?, filesToDiff?, filesToWait?, termProgram?} = {};
		if (fileInputs) {
			params.filesToOpen = fileInputs.filesToOpen;
			params.filesToCreate = fileInputs.filesToCreate;
			params.filesToDiff = fileInputs.filesToDiff;
			params.filesToWait = fileInputs.filesToWait;
		}

		if (configuration.userEnv) {
			params.termProgram = configuration.userEnv['TERM_PROGRAM'];
		}

		window.sendWhenReady('vscode:openFiles', params);
B
Benjamin Pasero 已提交
731 732

		return window;
733 734
	}

735
	private doAddFoldersToExistingWindow(window: ICodeWindow, foldersToAdd: URI[]): ICodeWindow {
736 737
		window.focus(); // make sure window has focus

B
Benjamin Pasero 已提交
738
		window.sendWhenReady('vscode:addFolders', { foldersToAdd });
739 740 741 742

		return window;
	}

M
Matt Bierner 已提交
743
	private doOpenFolderOrWorkspace(openConfig: IOpenConfiguration, folderOrWorkspace: IPathToOpen, forceNewWindow: boolean, fileInputs: IFileInputs | undefined, windowToUse?: ICodeWindow): ICodeWindow {
B
Benjamin Pasero 已提交
744 745 746 747
		if (!forceNewWindow && !windowToUse && typeof openConfig.contextWindowId === 'number') {
			windowToUse = this.getWindowById(openConfig.contextWindowId); // fix for https://github.com/Microsoft/vscode/issues/49587
		}

748 749 750 751
		const browserWindow = this.openInBrowserWindow({
			userEnv: openConfig.userEnv,
			cli: openConfig.cli,
			initialStartup: openConfig.initialStartup,
752
			workspace: folderOrWorkspace.workspace,
753
			folderUri: folderOrWorkspace.folderUri,
754
			fileInputs,
M
Martin Aeschlimann 已提交
755
			remoteAuthority: folderOrWorkspace.remoteAuthority,
B
Benjamin Pasero 已提交
756
			forceNewWindow,
757
			forceNewTabbedWindow: openConfig.forceNewTabbedWindow,
758
			windowToUse
759 760 761 762 763
		});

		return browserWindow;
	}

B
Benjamin Pasero 已提交
764 765
	private getPathsToOpen(openConfig: IOpenConfiguration): IPathToOpen[] {
		let windowsToOpen: IPathToOpen[];
766
		let isCommandLineOrAPICall = false;
E
Erich Gamma 已提交
767

768
		// Extract paths: from API
S
Sandeep Somavarapu 已提交
769
		if (openConfig.urisToOpen && openConfig.urisToOpen.length > 0) {
770
			windowsToOpen = this.doExtractPathsFromAPI(openConfig);
771
			isCommandLineOrAPICall = true;
E
Erich Gamma 已提交
772 773
		}

B
Benjamin Pasero 已提交
774 775
		// Check for force empty
		else if (openConfig.forceEmpty) {
776
			windowsToOpen = [Object.create(null)];
E
Erich Gamma 已提交
777 778
		}

779
		// Extract paths: from CLI
780
		else if (hasArgs(openConfig.cli._) || hasArgs(openConfig.cli['folder-uri']) || hasArgs(openConfig.cli['file-uri'])) {
781
			windowsToOpen = this.doExtractPathsFromCLI(openConfig.cli);
782
			isCommandLineOrAPICall = true;
B
Benjamin Pasero 已提交
783 784
		}

785
		// Extract windows: from previous session
B
Benjamin Pasero 已提交
786
		else {
787
			windowsToOpen = this.doGetWindowsFromLastSession();
B
Benjamin Pasero 已提交
788 789
		}

790 791
		// Convert multiple folders into workspace (if opened via API or CLI)
		// This will ensure to open these folders in one window instead of multiple
792 793
		// If we are in addMode, we should not do this because in that case all
		// folders should be added to the existing window.
794
		if (!openConfig.addMode && isCommandLineOrAPICall) {
795
			const foldersToOpen = windowsToOpen.filter(path => !!path.folderUri);
M
Matt Bierner 已提交
796 797
			if (foldersToOpen.length > 1 && foldersToOpen.every(f => f.folderUri!.scheme === Schemas.file)) {
				const workspace = this.workspacesMainService.createUntitledWorkspaceSync(foldersToOpen.map(folder => ({ uri: folder.folderUri! })));
798 799

				// Add workspace and remove folders thereby
M
Martin Aeschlimann 已提交
800
				windowsToOpen.push({ workspace, remoteAuthority: foldersToOpen[0].remoteAuthority });
801
				windowsToOpen = windowsToOpen.filter(path => !path.folderUri);
802 803 804
			}
		}

805
		return windowsToOpen;
E
Erich Gamma 已提交
806 807
	}

808
	private doExtractPathsFromAPI(openConfig: IOpenConfiguration): IPathToOpen[] {
M
Matt Bierner 已提交
809
		const pathsToOpen: IPathToOpen[] = [];
810 811
		const cli = openConfig.cli;
		let parseOptions: IPathParseOptions = { gotoLineMode: cli && cli.goto, forceOpenWorkspaceAsFile: openConfig.forceOpenWorkspaceAsFile };
M
Matt Bierner 已提交
812
		for (const pathToOpen of openConfig.urisToOpen || []) {
M
Martin Aeschlimann 已提交
813 814 815 816
			if (!pathToOpen) {
				continue;
			}

817
			const path = this.parseUri(pathToOpen.uri, pathToOpen.typeHint, parseOptions);
M
Martin Aeschlimann 已提交
818 819 820
			if (path) {
				pathsToOpen.push(path);
			} else {
821

B
Benjamin Pasero 已提交
822
				// Warn about the invalid URI or path
M
Martin Aeschlimann 已提交
823
				let message, detail;
824
				if (pathToOpen.uri.scheme === Schemas.file) {
M
Martin Aeschlimann 已提交
825
					message = localize('pathNotExistTitle', "Path does not exist");
826
					detail = localize('pathNotExistDetail', "The path '{0}' does not seem to exist anymore on disk.", pathToOpen.uri.fsPath);
M
Martin Aeschlimann 已提交
827 828
				} else {
					message = localize('uriInvalidTitle', "URI can not be opened");
829
					detail = localize('uriInvalidDetail', "The URI '{0}' is not valid and can not be opened.", pathToOpen.uri.toString());
M
Martin Aeschlimann 已提交
830
				}
831
				const options: Electron.MessageBoxOptions = {
832 833
					title: product.nameLong,
					type: 'info',
834
					buttons: [localize('ok', "OK")],
M
Martin Aeschlimann 已提交
835 836
					message,
					detail,
837 838 839
					noLink: true
				};

840
				this.dialogs.showMessageBox(options, this.getFocusedWindow());
841
			}
M
Martin Aeschlimann 已提交
842
		}
843 844 845 846
		return pathsToOpen;
	}

	private doExtractPathsFromCLI(cli: ParsedArgs): IPath[] {
M
Matt Bierner 已提交
847
		const pathsToOpen: IPathToOpen[] = [];
R
Rob Lourens 已提交
848
		const parseOptions: IPathParseOptions = { ignoreFileNotFound: true, gotoLineMode: cli.goto, remoteAuthority: cli.remote || undefined };
849 850

		// folder uris
851
		const folderUris = asArray(cli['folder-uri']);
M
Martin Aeschlimann 已提交
852
		for (let folderUri of folderUris) {
853
			const path = this.parseUri(this.argToUri(folderUri), 'folder', parseOptions);
M
Martin Aeschlimann 已提交
854 855 856
			if (path) {
				pathsToOpen.push(path);
			}
857 858 859 860
		}

		// file uris
		const fileUris = asArray(cli['file-uri']);
M
Martin Aeschlimann 已提交
861
		for (let fileUri of fileUris) {
862
			const path = this.parseUri(this.argToUri(fileUri), 'file');
M
Martin Aeschlimann 已提交
863 864 865
			if (path) {
				pathsToOpen.push(path);
			}
866 867 868
		}

		// folder or file paths
M
Martin Aeschlimann 已提交
869 870 871 872 873 874
		const cliArgs = asArray(cli._);
		for (let cliArg of cliArgs) {
			const path = this.parsePath(cliArg, parseOptions);
			if (path) {
				pathsToOpen.push(path);
			}
875 876
		}

M
Martin Aeschlimann 已提交
877
		if (pathsToOpen.length) {
878
			return pathsToOpen;
B
Benjamin Pasero 已提交
879 880 881 882 883 884
		}

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

B
Benjamin Pasero 已提交
885
	private doGetWindowsFromLastSession(): IPathToOpen[] {
886
		const restoreWindows = this.getRestoreWindowsSetting();
B
Benjamin Pasero 已提交
887

888
		switch (restoreWindows) {
B
Benjamin Pasero 已提交
889

890
			// none: we always open an empty window
891 892
			case 'none':
				return [Object.create(null)];
B
Benjamin Pasero 已提交
893

894
			// one: restore last opened workspace/folder or empty window
895 896
			// all: restore all windows
			// folders: restore last opened folders only
897
			case 'one':
898 899
			case 'all':
			case 'folders':
900 901 902
				const openedWindows: IWindowState[] = [];
				if (restoreWindows !== 'one') {
					openedWindows.push(...this.windowsState.openedWindows);
903
				}
904 905
				if (this.windowsState.lastActiveWindow) {
					openedWindows.push(this.windowsState.lastActiveWindow);
906
				}
907

908 909 910
				const windowsToOpen: IPathToOpen[] = [];
				for (const openedWindow of openedWindows) {
					if (openedWindow.workspace) { // Workspaces
911
						const pathToOpen = this.parseUri(openedWindow.workspace.configPath, 'file', { remoteAuthority: openedWindow.remoteAuthority });
912 913 914 915
						if (pathToOpen && pathToOpen.workspace) {
							windowsToOpen.push(pathToOpen);
						}
					} else if (openedWindow.folderUri) { // Folders
916
						const pathToOpen = this.parseUri(openedWindow.folderUri, 'folder', { remoteAuthority: openedWindow.remoteAuthority });
917 918 919 920
						if (pathToOpen && pathToOpen.folderUri) {
							windowsToOpen.push(pathToOpen);
						}
					} else if (restoreWindows !== 'folders' && openedWindow.backupPath) { // Windows that were Empty
M
Martin Aeschlimann 已提交
921
						windowsToOpen.push({ backupPath: openedWindow.backupPath, remoteAuthority: openedWindow.remoteAuthority });
922
					}
923 924 925 926 927 928 929
				}

				if (windowsToOpen.length > 0) {
					return windowsToOpen;
				}

				break;
B
Benjamin Pasero 已提交
930
		}
E
Erich Gamma 已提交
931

932
		// Always fallback to empty window
B
Benjamin Pasero 已提交
933
		return [Object.create(null)];
E
Erich Gamma 已提交
934 935
	}

936 937 938 939 940
	private getRestoreWindowsSetting(): RestoreWindowsSetting {
		let restoreWindows: RestoreWindowsSetting;
		if (this.lifecycleService.wasRestarted) {
			restoreWindows = 'all'; // always reopen all windows when an update was applied
		} else {
941
			const windowConfig = this.configurationService.getValue<IWindowSettings>('window');
942
			restoreWindows = ((windowConfig && windowConfig.restoreWindows) || 'one') as RestoreWindowsSetting;
943 944 945 946 947 948 949 950 951

			if (['all', 'folders', 'one', 'none'].indexOf(restoreWindows) === -1) {
				restoreWindows = 'one';
			}
		}

		return restoreWindows;
	}

952
	private argToUri(arg: string): URI | undefined {
M
Martin Aeschlimann 已提交
953 954 955
		try {
			let uri = URI.parse(arg);
			if (!uri.scheme) {
M
Martin Aeschlimann 已提交
956
				this.logService.error(`Invalid URI input string, scheme missing: ${arg}`);
957
				return undefined;
M
Martin Aeschlimann 已提交
958 959 960
			}
			return uri;
		} catch (e) {
M
Martin Aeschlimann 已提交
961
			this.logService.error(`Invalid URI input string: ${arg}, ${e.message}`);
962
		}
963
		return undefined;
964 965
	}

966
	private parseUri(uri: URI | undefined, typeHint?: URIType, options: IPathParseOptions = {}): IPathToOpen | undefined {
M
Martin Aeschlimann 已提交
967
		if (!uri || !uri.scheme) {
968
			return undefined;
969
		}
M
Martin Aeschlimann 已提交
970 971
		if (uri.scheme === Schemas.file) {
			return this.parsePath(uri.fsPath, options);
972
		}
M
Martin Aeschlimann 已提交
973 974

		// open remote if either specified in the cli or if it's a remotehost URI
975
		const remoteAuthority = options.remoteAuthority || getRemoteAuthority(uri);
M
Martin Aeschlimann 已提交
976

977 978
		// normalize URI
		uri = normalizePath(uri);
979 980 981


		// remove trailing slash
982
		const uriPath = uri.path;
983 984 985 986 987 988 989 990 991 992 993 994 995
		if (endsWith(uriPath, '/')) {
			if (uriPath.length > 2) {
				// only remove if the path has some content
				uri = uri.with({ path: uriPath.substr(0, uriPath.length - 1) });
			}
			if (!typeHint) {
				typeHint = 'folder';
			}
		}

		// if there's no type hint
		if (!typeHint && (extname(uri.path) === WORKSPACE_EXTENSION || options.gotoLineMode)) {
			typeHint = 'file';
996
		}
997 998 999

		if (typeHint === 'file') {
			if (options.gotoLineMode) {
1000 1001 1002 1003
				const parsedPath = parseLineAndColumnAware(uri.path);
				return {
					fileUri: uri.with({ path: parsedPath.path }),
					lineNumber: parsedPath.line,
M
Martin Aeschlimann 已提交
1004 1005
					columnNumber: parsedPath.column,
					remoteAuthority
1006 1007
				};
			}
1008 1009 1010 1011 1012 1013
			if (extname(uri.path) === WORKSPACE_EXTENSION && !options.forceOpenWorkspaceAsFile) {
				return {
					workspace: this.workspacesMainService.getWorkspaceIdentifier(uri),
					remoteAuthority
				};
			}
1014
			return {
M
Martin Aeschlimann 已提交
1015 1016
				fileUri: uri,
				remoteAuthority
1017 1018
			};
		}
1019
		return {
M
Martin Aeschlimann 已提交
1020 1021
			folderUri: uri,
			remoteAuthority
1022 1023 1024
		};
	}

1025
	private parsePath(anyPath: string, options: IPathParseOptions): IPathToOpen | undefined {
E
Erich Gamma 已提交
1026
		if (!anyPath) {
1027
			return undefined;
E
Erich Gamma 已提交
1028 1029
		}

1030
		let lineNumber, columnNumber: number | undefined;
1031

1032
		if (options.gotoLineMode) {
1033 1034 1035
			const parsedPath = parseLineAndColumnAware(anyPath);
			lineNumber = parsedPath.line;
			columnNumber = parsedPath.column;
1036

E
Erich Gamma 已提交
1037 1038 1039
			anyPath = parsedPath.path;
		}

M
Martin Aeschlimann 已提交
1040
		// open remote if either specified in the cli even if it is a local file. TODO: Future idea: resolve in remote host context.
1041
		const remoteAuthority = options.remoteAuthority;
M
Martin Aeschlimann 已提交
1042

1043
		const candidate = normalize(anyPath);
E
Erich Gamma 已提交
1044
		try {
B
Benjamin Pasero 已提交
1045
			const candidateStat = fs.statSync(candidate);
E
Erich Gamma 已提交
1046
			if (candidateStat) {
1047
				if (candidateStat.isFile()) {
1048

1049
					// Workspace (unless disabled via flag)
1050
					if (!options.forceOpenWorkspaceAsFile) {
B
Benjamin Pasero 已提交
1051
						const workspace = this.workspacesMainService.resolveWorkspaceSync(candidate);
1052
						if (workspace) {
M
Martin Aeschlimann 已提交
1053
							return { workspace: { id: workspace.id, configPath: workspace.configPath }, remoteAuthority };
1054
						}
1055 1056 1057
					}

					// File
1058
					return {
1059
						fileUri: URI.file(candidate),
1060 1061
						lineNumber,
						columnNumber,
M
Martin Aeschlimann 已提交
1062
						remoteAuthority
1063 1064 1065
					};
				}

1066 1067 1068 1069 1070
				// Folder (we check for isDirectory() because e.g. paths like /dev/null
				// are neither file nor folder but some external tools might pass them
				// over to us)
				else if (candidateStat.isDirectory()) {
					return {
M
Martin Aeschlimann 已提交
1071 1072
						folderUri: URI.file(candidate),
						remoteAuthority
1073 1074
					};
				}
E
Erich Gamma 已提交
1075 1076
			}
		} catch (error) {
S
Sandeep Somavarapu 已提交
1077
			this.historyMainService.removeFromRecentlyOpened([candidate]); // since file does not seem to exist anymore, remove from recent
1078

S
Sandeep Somavarapu 已提交
1079
			const fileUri = URI.file(candidate);
1080
			if (options && options.ignoreFileNotFound) {
M
Martin Aeschlimann 已提交
1081
				return { fileUri, createFilePath: true, remoteAuthority }; // assume this is a file that does not yet exist
E
Erich Gamma 已提交
1082 1083 1084
			}
		}

1085
		return undefined;
E
Erich Gamma 已提交
1086 1087
	}

B
Benjamin Pasero 已提交
1088 1089 1090
	private shouldOpenNewWindow(openConfig: IOpenConfiguration): { openFolderInNewWindow: boolean; openFilesInNewWindow: boolean; } {

		// let the user settings override how folders are open in a new window or same window unless we are forced
1091
		const windowConfig = this.configurationService.getValue<IWindowSettings>('window');
1092 1093 1094
		const openFolderInNewWindowConfig = (windowConfig && windowConfig.openFoldersInNewWindow) || 'default' /* default */;
		const openFilesInNewWindowConfig = (windowConfig && windowConfig.openFilesInNewWindow) || 'off' /* default */;

B
Benjamin Pasero 已提交
1095
		let openFolderInNewWindow = (openConfig.preferNewWindow || openConfig.forceNewWindow) && !openConfig.forceReuseWindow;
1096 1097
		if (!openConfig.forceNewWindow && !openConfig.forceReuseWindow && (openFolderInNewWindowConfig === 'on' || openFolderInNewWindowConfig === 'off')) {
			openFolderInNewWindow = (openFolderInNewWindowConfig === 'on');
B
Benjamin Pasero 已提交
1098 1099 1100
		}

		// 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)
M
Matt Bierner 已提交
1101
		let openFilesInNewWindow: boolean = false;
B
Benjamin Pasero 已提交
1102
		if (openConfig.forceNewWindow || openConfig.forceReuseWindow) {
M
Matt Bierner 已提交
1103
			openFilesInNewWindow = !!openConfig.forceNewWindow && !openConfig.forceReuseWindow;
B
Benjamin Pasero 已提交
1104
		} else {
1105 1106 1107 1108 1109 1110 1111 1112 1113 1114 1115 1116 1117

			// macOS: by default we open files in a new window if this is triggered via DOCK context
			if (isMacintosh) {
				if (openConfig.context === OpenContext.DOCK) {
					openFilesInNewWindow = true;
				}
			}

			// Linux/Windows: by default we open files in the new window unless triggered via DIALOG or MENU context
			else {
				if (openConfig.context !== OpenContext.DIALOG && openConfig.context !== OpenContext.MENU) {
					openFilesInNewWindow = true;
				}
B
Benjamin Pasero 已提交
1118 1119
			}

1120
			// finally check for overrides of default
1121 1122
			if (!openConfig.cli.extensionDevelopmentPath && (openFilesInNewWindowConfig === 'on' || openFilesInNewWindowConfig === 'off')) {
				openFilesInNewWindow = (openFilesInNewWindowConfig === 'on');
B
Benjamin Pasero 已提交
1123 1124 1125
			}
		}

M
Matt Bierner 已提交
1126
		return { openFolderInNewWindow: !!openFolderInNewWindow, openFilesInNewWindow };
B
Benjamin Pasero 已提交
1127 1128
	}

B
Benjamin Pasero 已提交
1129
	openExtensionDevelopmentHostWindow(openConfig: IOpenConfiguration): void {
E
Erich Gamma 已提交
1130

B
Benjamin Pasero 已提交
1131 1132 1133
		// Reload an existing extension development host window on the same path
		// We currently do not allow more than one extension development window
		// on the same extension path.
M
Matt Bierner 已提交
1134
		const existingWindow = openConfig.cli.extensionDevelopmentPath && findWindowOnExtensionDevelopmentPath(WindowsManager.WINDOWS, openConfig.cli.extensionDevelopmentPath);
1135 1136 1137
		if (existingWindow) {
			this.reload(existingWindow, openConfig.cli);
			existingWindow.focus(); // make sure it gets focus and is restored
E
Erich Gamma 已提交
1138

B
Benjamin Pasero 已提交
1139 1140
			return;
		}
1141 1142 1143
		let folderUris = asArray(openConfig.cli['folder-uri']);
		let fileUris = asArray(openConfig.cli['file-uri']);
		let cliArgs = openConfig.cli._;
E
Erich Gamma 已提交
1144

1145
		// Fill in previously opened workspace unless an explicit path is provided and we are not unit testing
1146
		if (!cliArgs.length && !folderUris.length && !fileUris.length && !openConfig.cli.extensionTestsPath) {
1147
			const extensionDevelopmentWindowState = this.windowsState.lastPluginDevelopmentHostWindow;
1148
			const workspaceToOpen = extensionDevelopmentWindowState && (extensionDevelopmentWindowState.workspace || extensionDevelopmentWindowState.folderUri);
1149
			if (workspaceToOpen) {
1150
				if (isSingleFolderWorkspaceIdentifier(workspaceToOpen)) {
1151
					if (workspaceToOpen.scheme === Schemas.file) {
1152
						cliArgs = [workspaceToOpen.fsPath];
1153
					} else {
1154
						folderUris = [workspaceToOpen.toString()];
1155 1156
					}
				} else {
M
Martin Aeschlimann 已提交
1157 1158 1159
					if (workspaceToOpen.configPath.scheme === Schemas.file) {
						cliArgs = [fsPath(workspaceToOpen.configPath)];
					} else {
1160
						fileUris = [workspaceToOpen.configPath.toString()];
M
Martin Aeschlimann 已提交
1161
					}
1162
				}
E
Erich Gamma 已提交
1163 1164 1165
			}
		}

1166
		// Make sure we are not asked to open a workspace or folder that is already opened
1167 1168
		if (cliArgs.length && cliArgs.some(path => !!findWindowOnWorkspaceOrFolderUri(WindowsManager.WINDOWS, URI.file(path)))) {
			cliArgs = [];
E
Erich Gamma 已提交
1169
		}
1170

M
Martin Aeschlimann 已提交
1171
		if (folderUris.length && folderUris.some(uri => !!findWindowOnWorkspaceOrFolderUri(WindowsManager.WINDOWS, this.argToUri(uri)))) {
1172 1173 1174
			folderUris = [];
		}

M
Martin Aeschlimann 已提交
1175
		if (fileUris.length && fileUris.some(uri => !!findWindowOnWorkspaceOrFolderUri(WindowsManager.WINDOWS, this.argToUri(uri)))) {
1176
			fileUris = [];
1177
		}
E
Erich Gamma 已提交
1178

1179 1180 1181 1182
		openConfig.cli._ = cliArgs;
		openConfig.cli['folder-uri'] = folderUris;
		openConfig.cli['file-uri'] = fileUris;

B
Benjamin Pasero 已提交
1183
		// Open it
1184
		this.open({ context: openConfig.context, cli: openConfig.cli, forceNewWindow: true, forceEmpty: !cliArgs.length && !folderUris.length && !fileUris.length, userEnv: openConfig.userEnv });
E
Erich Gamma 已提交
1185 1186
	}

1187
	private openInBrowserWindow(options: IOpenBrowserWindowOptions): ICodeWindow {
1188

B
Benjamin Pasero 已提交
1189 1190 1191
		// Build IWindowConfiguration from config and options
		const configuration: IWindowConfiguration = mixin({}, options.cli); // inherit all properties from CLI
		configuration.appRoot = this.environmentService.appRoot;
1192
		configuration.machineId = this.machineId;
1193
		configuration.nodeCachedDataDir = this.environmentService.nodeCachedDataDir;
1194
		configuration.mainPid = process.pid;
B
Benjamin Pasero 已提交
1195 1196 1197
		configuration.execPath = process.execPath;
		configuration.userEnv = assign({}, this.initialUserEnv, options.userEnv || {});
		configuration.isInitialStartup = options.initialStartup;
1198
		configuration.workspace = options.workspace;
1199
		configuration.folderUri = options.folderUri;
M
Martin Aeschlimann 已提交
1200
		configuration.remoteAuthority = options.remoteAuthority;
1201 1202 1203 1204 1205 1206 1207 1208

		const fileInputs = options.fileInputs;
		if (fileInputs) {
			configuration.filesToOpen = fileInputs.filesToOpen;
			configuration.filesToCreate = fileInputs.filesToCreate;
			configuration.filesToDiff = fileInputs.filesToDiff;
			configuration.filesToWait = fileInputs.filesToWait;
		}
B
Benjamin Pasero 已提交
1209

1210
		// if we know the backup folder upfront (for empty windows to restore), we can set it
1211
		// directly here which helps for restoring UI state associated with that window.
B
Benjamin Pasero 已提交
1212
		// For all other cases we first call into registerEmptyWindowBackupSync() to set it before
1213
		// loading the window.
1214
		if (options.emptyWindowBackupInfo) {
M
Matt Bierner 已提交
1215
			configuration.backupPath = join(this.environmentService.backupHome, options.emptyWindowBackupInfo.backupFolder!);
1216 1217
		}

1218
		let window: ICodeWindow | undefined;
1219
		if (!options.forceNewWindow && !options.forceNewTabbedWindow) {
1220 1221 1222
			window = options.windowToUse || this.getLastActiveWindow();
			if (window) {
				window.focus();
E
Erich Gamma 已提交
1223 1224 1225 1226
			}
		}

		// New window
1227
		if (!window) {
1228
			const windowConfig = this.configurationService.getValue<IWindowSettings>('window');
1229 1230 1231 1232 1233 1234 1235 1236 1237 1238
			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 {
1239
				allowFullscreen = this.lifecycleService.wasRestarted || (windowConfig && windowConfig.restoreFullscreen);
1240 1241 1242 1243 1244
			}

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

1246
			// Create the window
1247
			window = this.instantiationService.createInstance(CodeWindow, {
1248
				state,
1249
				extensionDevelopmentPath: configuration.extensionDevelopmentPath,
1250
				isExtensionTestHost: !!configuration.extensionTestsPath
1251
			});
1252

1253 1254 1255 1256 1257 1258 1259 1260
			// Add as window tab if configured (macOS only)
			if (options.forceNewTabbedWindow) {
				const activeWindow = this.getLastActiveWindow();
				if (activeWindow) {
					activeWindow.addTabbedWindow(window);
				}
			}

B
Benjamin Pasero 已提交
1261
			// Add to our list of windows
1262
			WindowsManager.WINDOWS.push(window);
E
Erich Gamma 已提交
1263

B
Benjamin Pasero 已提交
1264 1265 1266
			// Indicate number change via event
			this._onWindowsCountChanged.fire({ oldCount: WindowsManager.WINDOWS.length - 1, newCount: WindowsManager.WINDOWS.length });

E
Erich Gamma 已提交
1267
			// Window Events
1268
			window.win.webContents.removeAllListeners('devtools-reload-page'); // remove built in listener so we can handle this on our own
M
Matt Bierner 已提交
1269 1270 1271 1272
			window.win.webContents.on('devtools-reload-page', () => this.reload(window!));
			window.win.webContents.on('crashed', () => this.onWindowError(window!, WindowError.CRASHED));
			window.win.on('unresponsive', () => this.onWindowError(window!, WindowError.UNRESPONSIVE));
			window.win.on('closed', () => this.onWindowClosed(window!));
E
Erich Gamma 已提交
1273 1274

			// Lifecycle
B
Benjamin Pasero 已提交
1275
			(this.lifecycleService as LifecycleService).registerWindow(window);
E
Erich Gamma 已提交
1276 1277 1278 1279 1280 1281
		}

		// Existing window
		else {

			// Some configuration things get inherited if the window is being reused and we are
B
Benjamin Pasero 已提交
1282
			// in extension development host mode. These options are all development related.
1283
			const currentWindowConfig = window.config;
A
Alex Dima 已提交
1284 1285
			if (!configuration.extensionDevelopmentPath && currentWindowConfig && !!currentWindowConfig.extensionDevelopmentPath) {
				configuration.extensionDevelopmentPath = currentWindowConfig.extensionDevelopmentPath;
B
Benjamin Pasero 已提交
1286
				configuration.verbose = currentWindowConfig.verbose;
1287
				configuration['inspect-brk-extensions'] = currentWindowConfig['inspect-brk-extensions'];
1288
				configuration.debugId = currentWindowConfig.debugId;
1289
				configuration['inspect-extensions'] = currentWindowConfig['inspect-extensions'];
1290
				configuration['extensions-dir'] = currentWindowConfig['extensions-dir'];
E
Erich Gamma 已提交
1291 1292 1293
			}
		}

1294 1295 1296 1297 1298 1299
		// If the window was already loaded, make sure to unload it
		// first and only load the new configuration if that was
		// not vetoed
		if (window.isReady) {
			this.lifecycleService.unload(window, UnloadReason.LOAD).then(veto => {
				if (!veto) {
M
Matt Bierner 已提交
1300
					this.doOpenInBrowserWindow(window!, configuration, options);
B
Benjamin Pasero 已提交
1301
				}
1302 1303 1304 1305 1306 1307 1308
			});
		} else {
			this.doOpenInBrowserWindow(window, configuration, options);
		}

		return window;
	}
B
Benjamin Pasero 已提交
1309

1310
	private doOpenInBrowserWindow(window: ICodeWindow, configuration: IWindowConfiguration, options: IOpenBrowserWindowOptions): void {
1311

1312 1313 1314 1315 1316 1317 1318 1319 1320
		// Register window for backups
		if (!configuration.extensionDevelopmentPath) {
			if (configuration.workspace) {
				configuration.backupPath = this.backupMainService.registerWorkspaceBackupSync(configuration.workspace);
			} else if (configuration.folderUri) {
				configuration.backupPath = this.backupMainService.registerFolderBackupSync(configuration.folderUri);
			} else {
				const backupFolder = options.emptyWindowBackupInfo && options.emptyWindowBackupInfo.backupFolder;
				configuration.backupPath = this.backupMainService.registerEmptyWindowBackupSync({ backupFolder, remoteAuthority: configuration.remoteAuthority });
E
Erich Gamma 已提交
1321
			}
1322
		}
1323

1324 1325 1326 1327 1328
		// Load it
		window.load(configuration);

		// Signal event
		this._onWindowLoad.fire(window.id);
E
Erich Gamma 已提交
1329 1330
	}

1331
	private getNewWindowState(configuration: IWindowConfiguration): INewWindowState {
1332
		const lastActive = this.getLastActiveWindow();
E
Erich Gamma 已提交
1333

1334 1335
		// Restore state unless we are running extension tests
		if (!configuration.extensionTestsPath) {
E
Erich Gamma 已提交
1336

1337 1338 1339
			// extension development host Window - load from stored settings if any
			if (!!configuration.extensionDevelopmentPath && this.windowsState.lastPluginDevelopmentHostWindow) {
				return this.windowsState.lastPluginDevelopmentHostWindow.uiState;
1340 1341
			}

1342
			// Known Workspace - load from stored settings
M
Matt Bierner 已提交
1343 1344 1345
			const workspace = configuration.workspace;
			if (workspace) {
				const stateForWorkspace = this.windowsState.openedWindows.filter(o => o.workspace && o.workspace.id === workspace.id).map(o => o.uiState);
1346 1347 1348 1349 1350 1351
				if (stateForWorkspace.length) {
					return stateForWorkspace[0];
				}
			}

			// Known Folder - load from stored settings
1352
			if (configuration.folderUri) {
1353
				const stateForFolder = this.windowsState.openedWindows.filter(o => o.folderUri && isEqual(o.folderUri, configuration.folderUri)).map(o => o.uiState);
1354 1355 1356
				if (stateForFolder.length) {
					return stateForFolder[0];
				}
1357 1358
			}

1359 1360 1361 1362 1363 1364
			// Empty windows with backups
			else if (configuration.backupPath) {
				const stateForEmptyWindow = this.windowsState.openedWindows.filter(o => o.backupPath === configuration.backupPath).map(o => o.uiState);
				if (stateForEmptyWindow.length) {
					return stateForEmptyWindow[0];
				}
E
Erich Gamma 已提交
1365 1366
			}

1367 1368 1369 1370 1371
			// First Window
			const lastActiveState = this.lastClosedWindowState || this.windowsState.lastActiveWindow;
			if (!lastActive && lastActiveState) {
				return lastActiveState.uiState;
			}
E
Erich Gamma 已提交
1372 1373 1374 1375 1376 1377 1378
		}

		//
		// 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
1379
		let displayToUse: Electron.Display | undefined;
B
Benjamin Pasero 已提交
1380
		const displays = screen.getAllDisplays();
E
Erich Gamma 已提交
1381 1382 1383 1384 1385 1386 1387 1388 1389 1390

		// 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
B
Benjamin Pasero 已提交
1391
			if (isMacintosh) {
B
Benjamin Pasero 已提交
1392
				const cursorPoint = screen.getCursorScreenPoint();
E
Erich Gamma 已提交
1393 1394 1395 1396 1397 1398 1399 1400
				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());
			}

1401
			// fallback to primary display or first display
E
Erich Gamma 已提交
1402
			if (!displayToUse) {
1403
				displayToUse = screen.getPrimaryDisplay() || displays[0];
E
Erich Gamma 已提交
1404 1405 1406
			}
		}

1407 1408 1409
		// Compute x/y based on display bounds
		// Note: important to use Math.round() because Electron does not seem to be too happy about
		// display coordinates that are not absolute numbers.
1410
		let state = defaultWindowState() as INewWindowState;
M
Matt Bierner 已提交
1411 1412
		state.x = Math.round(displayToUse.bounds.x + (displayToUse.bounds.width / 2) - (state.width! / 2));
		state.y = Math.round(displayToUse.bounds.y + (displayToUse.bounds.height / 2) - (state.height! / 2));
E
Erich Gamma 已提交
1413

1414
		// Check for newWindowDimensions setting and adjust accordingly
1415
		const windowConfig = this.configurationService.getValue<IWindowSettings>('window');
1416 1417 1418 1419 1420 1421 1422 1423 1424
		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 已提交
1425 1426 1427 1428 1429 1430 1431
				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;
				}

1432 1433 1434 1435 1436 1437 1438 1439
				ensureNoOverlap = false;
			}
		}

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

1440 1441
		state.hasDefaultState = true; // flag as default state

1442
		return state;
E
Erich Gamma 已提交
1443 1444
	}

J
Joao Moreno 已提交
1445
	private ensureNoOverlap(state: ISingleWindowState): ISingleWindowState {
E
Erich Gamma 已提交
1446 1447 1448 1449
		if (WindowsManager.WINDOWS.length === 0) {
			return state;
		}

M
Matt Bierner 已提交
1450 1451 1452
		state.x = typeof state.x === 'number' ? state.x : 0;
		state.y = typeof state.y === 'number' ? state.y : 0;

1453 1454
		const existingWindowBounds = WindowsManager.WINDOWS.map(win => win.getBounds());
		while (existingWindowBounds.some(b => b.x === state.x || b.y === state.y)) {
E
Erich Gamma 已提交
1455 1456 1457 1458 1459 1460 1461
			state.x += 30;
			state.y += 30;
		}

		return state;
	}

B
Benjamin Pasero 已提交
1462
	reload(win: ICodeWindow, cli?: ParsedArgs): void {
B
Benjamin Pasero 已提交
1463 1464

		// Only reload when the window has not vetoed this
1465
		this.lifecycleService.unload(win, UnloadReason.RELOAD).then(veto => {
B
Benjamin Pasero 已提交
1466
			if (!veto) {
R
Rob Lourens 已提交
1467
				win.reload(undefined, cli);
B
Benjamin Pasero 已提交
1468 1469 1470 1471
			}
		});
	}

B
Benjamin Pasero 已提交
1472
	closeWorkspace(win: ICodeWindow): void {
1473 1474
		this.openInBrowserWindow({
			cli: this.environmentService.args,
M
Martin Aeschlimann 已提交
1475 1476
			windowToUse: win,
			remoteAuthority: win.remoteAuthority
1477 1478 1479
		});
	}

M
Matt Bierner 已提交
1480 1481
	enterWorkspace(win: ICodeWindow, path: URI): Promise<IEnterWorkspaceResult | undefined> {
		return this.workspacesManager.enterWorkspace(win, path).then(result => result ? this.doEnterWorkspace(win, result) : undefined);
1482 1483
	}

1484
	private doEnterWorkspace(win: ICodeWindow, result: IEnterWorkspaceResult): IEnterWorkspaceResult {
1485

1486
		// Mark as recently opened
B
Benjamin Pasero 已提交
1487
		this.historyMainService.addRecentlyOpened([result.workspace], []);
1488

1489 1490 1491
		// Trigger Eevent to indicate load of workspace into window
		this._onWindowReady.fire(win);

1492
		return result;
1493 1494
	}

B
Benjamin Pasero 已提交
1495
	pickWorkspaceAndOpen(options: INativeOpenDialogOptions): void {
1496
		this.workspacesManager.pickWorkspaceAndOpen(options);
1497 1498 1499
	}

	private onBeforeWindowUnload(e: IWindowUnloadEvent): void {
1500 1501
		const windowClosing = (e.reason === UnloadReason.CLOSE);
		const windowLoading = (e.reason === UnloadReason.LOAD);
1502 1503 1504 1505 1506
		if (!windowClosing && !windowLoading) {
			return; // only interested when window is closing or loading
		}

		const workspace = e.window.openedWorkspace;
B
Benjamin Pasero 已提交
1507
		if (!workspace || !this.workspacesMainService.isUntitledWorkspace(workspace)) {
1508 1509 1510
			return; // only care about untitled workspaces to ask for saving
		}

1511
		if (e.window.config && !!e.window.config.extensionDevelopmentPath) {
1512 1513 1514 1515
			// do not ask to save workspace when doing extension development
			// but still delete it.
			this.workspacesMainService.deleteUntitledWorkspaceSync(workspace);
			return;
1516 1517
		}

1518 1519 1520 1521
		if (windowClosing && !isMacintosh && this.getWindowCount() === 1) {
			return; // Windows/Linux: quits when last window is closed, so do not ask then
		}

1522
		// Handle untitled workspaces with prompt as needed
M
Matt Bierner 已提交
1523
		e.veto(this.workspacesManager.promptToSaveUntitledWorkspace(this.getWindowById(e.window.id), workspace).then((veto): boolean | Promise<boolean> => {
B
Benjamin Pasero 已提交
1524 1525 1526 1527 1528 1529 1530 1531
			if (veto) {
				return veto;
			}

			// Bug in electron: somehow we need this timeout so that the window closes properly. That
			// might be related to the fact that the untitled workspace prompt shows up async and this
			// code can execute before the dialog is fully closed which then blocks the window from closing.
			// Issue: https://github.com/Microsoft/vscode/issues/41989
1532
			return timeout(0).then(() => veto);
B
Benjamin Pasero 已提交
1533
		}));
1534 1535
	}

B
Benjamin Pasero 已提交
1536
	focusLastActive(cli: ParsedArgs, context: OpenContext): ICodeWindow {
B
Benjamin Pasero 已提交
1537
		const lastActive = this.getLastActiveWindow();
E
Erich Gamma 已提交
1538
		if (lastActive) {
B
Benjamin Pasero 已提交
1539
			lastActive.focus();
1540 1541

			return lastActive;
E
Erich Gamma 已提交
1542 1543
		}

B
Benjamin Pasero 已提交
1544
		// No window - open new empty one
1545
		return this.open({ context, cli, forceEmpty: true })[0];
E
Erich Gamma 已提交
1546 1547
	}

M
Matt Bierner 已提交
1548
	getLastActiveWindow(): ICodeWindow | undefined {
1549
		return getLastActiveWindow(WindowsManager.WINDOWS);
E
Erich Gamma 已提交
1550 1551
	}

M
Matt Bierner 已提交
1552
	getLastActiveWindowForAuthority(remoteAuthority: string): ICodeWindow | undefined {
M
Martin Aeschlimann 已提交
1553 1554 1555
		return getLastActiveWindow(WindowsManager.WINDOWS.filter(w => w.remoteAuthority === remoteAuthority));
	}

1556 1557
	openNewWindow(context: OpenContext, options?: INewWindowOptions): ICodeWindow[] {
		let cli = this.environmentService.args;
R
Rob Lourens 已提交
1558
		let remote = options && options.remoteAuthority || undefined;
M
Martin Aeschlimann 已提交
1559 1560 1561
		if (cli && (cli.remote !== remote)) {
			cli = { ...cli, remote };
		}
1562
		return this.open({ context, cli, forceNewWindow: true, forceEmpty: true });
E
Erich Gamma 已提交
1563 1564
	}

1565 1566 1567 1568
	openNewTabbedWindow(context: OpenContext): ICodeWindow[] {
		return this.open({ context, cli: this.environmentService.args, forceNewTabbedWindow: true, forceEmpty: true });
	}

J
Johannes Rieken 已提交
1569
	waitForWindowCloseOrLoad(windowId: number): Promise<void> {
B
Benjamin Pasero 已提交
1570
		return new Promise<void>(resolve => {
1571
			function handler(id: number) {
1572
				if (id === windowId) {
1573 1574 1575
					closeListener.dispose();
					loadListener.dispose();

1576
					resolve();
1577
				}
1578 1579 1580 1581
			}

			const closeListener = this.onWindowClose(id => handler(id));
			const loadListener = this.onWindowLoad(id => handler(id));
1582 1583 1584
		});
	}

B
Benjamin Pasero 已提交
1585
	sendToFocused(channel: string, ...args: any[]): void {
E
Erich Gamma 已提交
1586 1587 1588
		const focusedWindow = this.getFocusedWindow() || this.getLastActiveWindow();

		if (focusedWindow) {
1589
			focusedWindow.sendWhenReady(channel, ...args);
E
Erich Gamma 已提交
1590 1591 1592
		}
	}

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

1599
			w.sendWhenReady(channel, payload);
E
Erich Gamma 已提交
1600 1601 1602
		});
	}

M
Matt Bierner 已提交
1603
	getFocusedWindow(): ICodeWindow | undefined {
B
Benjamin Pasero 已提交
1604
		const win = BrowserWindow.getFocusedWindow();
E
Erich Gamma 已提交
1605 1606 1607 1608
		if (win) {
			return this.getWindowById(win.id);
		}

M
Matt Bierner 已提交
1609
		return undefined;
E
Erich Gamma 已提交
1610 1611
	}

M
Matt Bierner 已提交
1612
	getWindowById(windowId: number): ICodeWindow | undefined {
1613
		const res = WindowsManager.WINDOWS.filter(w => w.id === windowId);
E
Erich Gamma 已提交
1614 1615 1616 1617
		if (res && res.length === 1) {
			return res[0];
		}

M
Matt Bierner 已提交
1618
		return undefined;
E
Erich Gamma 已提交
1619 1620
	}

B
Benjamin Pasero 已提交
1621
	getWindows(): ICodeWindow[] {
E
Erich Gamma 已提交
1622 1623 1624
		return WindowsManager.WINDOWS;
	}

B
Benjamin Pasero 已提交
1625
	getWindowCount(): number {
E
Erich Gamma 已提交
1626 1627 1628
		return WindowsManager.WINDOWS.length;
	}

1629
	private onWindowError(window: ICodeWindow, error: WindowError): void {
B
Benjamin Pasero 已提交
1630
		this.logService.error(error === WindowError.CRASHED ? '[VS Code]: render process crashed!' : '[VS Code]: detected unresponsive');
E
Erich Gamma 已提交
1631

1632 1633
		/* __GDPR__
			"windowerror" : {
K
kieferrm 已提交
1634
				"type" : { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth", "isMeasurement": true }
1635 1636 1637 1638
			}
		*/
		this.telemetryService.publicLog('windowerror', { type: error });

E
Erich Gamma 已提交
1639 1640
		// Unresponsive
		if (error === WindowError.UNRESPONSIVE) {
1641 1642 1643 1644 1645 1646 1647 1648 1649 1650 1651
			if (window.isExtensionDevelopmentHost || window.isExtensionTestHost || (window.win && window.win.webContents && window.win.webContents.isDevToolsOpened())) {
				// TODO@Ben Workaround for https://github.com/Microsoft/vscode/issues/56994
				// In certain cases the window can report unresponsiveness because a breakpoint was hit
				// and the process is stopped executing. The most typical cases are:
				// - devtools are opened and debugging happens
				// - window is an extensions development host that is being debugged
				// - window is an extension test development host that is being debugged
				return;
			}

			// Show Dialog
1652
			this.dialogs.showMessageBox({
B
Benjamin Pasero 已提交
1653
				title: product.nameLong,
E
Erich Gamma 已提交
1654
				type: 'warning',
1655
				buttons: [mnemonicButtonLabel(localize({ key: 'reopen', comment: ['&& denotes a mnemonic'] }, "&&Reopen")), mnemonicButtonLabel(localize({ key: 'wait', comment: ['&& denotes a mnemonic'] }, "&&Keep Waiting")), mnemonicButtonLabel(localize({ key: 'close', comment: ['&& denotes a mnemonic'] }, "&&Close"))],
1656 1657
				message: localize('appStalled', "The window is no longer responding"),
				detail: localize('appStalledDetail', "You can reopen or close the window or keep waiting."),
E
Erich Gamma 已提交
1658
				noLink: true
1659 1660 1661 1662
			}, window).then(result => {
				if (!window.win) {
					return; // Return early if the window has been going down already
				}
1663

1664 1665 1666 1667 1668 1669 1670
				if (result.button === 0) {
					window.reload();
				} else if (result.button === 2) {
					this.onBeforeWindowClose(window); // 'close' event will not be fired on destroy(), so run it manually
					window.win.destroy(); // make sure to destroy the window as it is unresponsive
				}
			});
E
Erich Gamma 已提交
1671 1672 1673 1674
		}

		// Crashed
		else {
1675
			this.dialogs.showMessageBox({
B
Benjamin Pasero 已提交
1676
				title: product.nameLong,
E
Erich Gamma 已提交
1677
				type: 'warning',
1678
				buttons: [mnemonicButtonLabel(localize({ key: 'reopen', comment: ['&& denotes a mnemonic'] }, "&&Reopen")), mnemonicButtonLabel(localize({ key: 'close', comment: ['&& denotes a mnemonic'] }, "&&Close"))],
1679 1680
				message: localize('appCrashed', "The window has crashed"),
				detail: localize('appCrashedDetail', "We are sorry for the inconvenience! You can reopen the window to continue where you left off."),
E
Erich Gamma 已提交
1681
				noLink: true
1682 1683 1684 1685
			}, window).then(result => {
				if (!window.win) {
					return; // Return early if the window has been going down already
				}
1686

1687 1688 1689 1690 1691 1692 1693
				if (result.button === 0) {
					window.reload();
				} else if (result.button === 1) {
					this.onBeforeWindowClose(window); // 'close' event will not be fired on destroy(), so run it manually
					window.win.destroy(); // make sure to destroy the window as it has crashed
				}
			});
E
Erich Gamma 已提交
1694 1695 1696
		}
	}

1697
	private onWindowClosed(win: ICodeWindow): void {
E
Erich Gamma 已提交
1698 1699 1700 1701 1702

		// Tell window
		win.dispose();

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

		// Emit
B
Benjamin Pasero 已提交
1707
		this._onWindowsCountChanged.fire({ oldCount: WindowsManager.WINDOWS.length + 1, newCount: WindowsManager.WINDOWS.length });
1708
		this._onWindowClose.fire(win.id);
E
Erich Gamma 已提交
1709
	}
B
Benjamin Pasero 已提交
1710

B
Benjamin Pasero 已提交
1711
	pickFileFolderAndOpen(options: INativeOpenDialogOptions): void {
B
Benjamin Pasero 已提交
1712
		this.doPickAndOpen(options, true /* pick folders */, true /* pick files */);
1713 1714
	}

B
Benjamin Pasero 已提交
1715
	pickFolderAndOpen(options: INativeOpenDialogOptions): void {
B
Benjamin Pasero 已提交
1716
		this.doPickAndOpen(options, true /* pick folders */, false /* pick files */);
1717 1718
	}

B
Benjamin Pasero 已提交
1719
	pickFileAndOpen(options: INativeOpenDialogOptions): void {
B
Benjamin Pasero 已提交
1720 1721 1722 1723 1724 1725 1726 1727 1728
		this.doPickAndOpen(options, false /* pick folders */, true /* pick files */);
	}

	private doPickAndOpen(options: INativeOpenDialogOptions, pickFolders: boolean, pickFiles: boolean): void {
		const internalOptions = options as IInternalNativeOpenDialogOptions;

		internalOptions.pickFolders = pickFolders;
		internalOptions.pickFiles = pickFiles;

1729 1730
		const dialogOptions: OpenDialogOptions = internalOptions.dialogOptions || Object.create(null);
		internalOptions.dialogOptions = dialogOptions;
B
Benjamin Pasero 已提交
1731 1732 1733 1734 1735 1736 1737 1738 1739 1740 1741 1742 1743

		if (!internalOptions.dialogOptions.title) {
			if (pickFolders && pickFiles) {
				internalOptions.dialogOptions.title = localize('open', "Open");
			} else if (pickFolders) {
				internalOptions.dialogOptions.title = localize('openFolder', "Open Folder");
			} else {
				internalOptions.dialogOptions.title = localize('openFile', "Open File");
			}
		}

		if (!internalOptions.telemetryEventName) {
			if (pickFolders && pickFiles) {
K
kieferrm 已提交
1744
				// __GDPR__TODO__ classify event
B
Benjamin Pasero 已提交
1745 1746 1747 1748 1749 1750 1751 1752
				internalOptions.telemetryEventName = 'openFileFolder';
			} else if (pickFolders) {
				internalOptions.telemetryEventName = 'openFolder';
			} else {
				internalOptions.telemetryEventName = 'openFile';
			}
		}

1753 1754 1755
		this.dialogs.pickAndOpen(internalOptions);
	}

J
Johannes Rieken 已提交
1756
	showMessageBox(options: Electron.MessageBoxOptions, win?: ICodeWindow): Promise<IMessageBoxResult> {
1757 1758 1759
		return this.dialogs.showMessageBox(options, win);
	}

J
Johannes Rieken 已提交
1760
	showSaveDialog(options: Electron.SaveDialogOptions, win?: ICodeWindow): Promise<string> {
1761 1762 1763
		return this.dialogs.showSaveDialog(options, win);
	}

J
Johannes Rieken 已提交
1764
	showOpenDialog(options: Electron.OpenDialogOptions, win?: ICodeWindow): Promise<string[]> {
1765
		return this.dialogs.showOpenDialog(options, win);
B
Benjamin Pasero 已提交
1766 1767
	}

B
Benjamin Pasero 已提交
1768
	quit(): void {
B
Benjamin Pasero 已提交
1769 1770 1771

		// 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.
1772 1773 1774
		const window = this.getFocusedWindow();
		if (window && window.isExtensionDevelopmentHost && this.getWindowCount() > 1) {
			window.win.close();
B
Benjamin Pasero 已提交
1775 1776 1777 1778 1779 1780 1781 1782
		}

		// Otherwise: normal quit
		else {
			setTimeout(() => {
				this.lifecycleService.quit();
			}, 10 /* delay to unwind callback stack (IPC) */);
		}
1783
	}
B
Benjamin Pasero 已提交
1784 1785
}

B
Benjamin Pasero 已提交
1786
interface IInternalNativeOpenDialogOptions extends INativeOpenDialogOptions {
B
Benjamin Pasero 已提交
1787 1788 1789 1790
	pickFolders?: boolean;
	pickFiles?: boolean;
}

1791
class Dialogs {
B
Benjamin Pasero 已提交
1792

1793
	private static readonly workingDirPickerStorageKey = 'pickerWorkingDir';
1794

1795 1796 1797
	private mapWindowToDialogQueue: Map<number, Queue<any>>;
	private noWindowDialogQueue: Queue<any>;

B
Benjamin Pasero 已提交
1798 1799 1800
	constructor(
		private environmentService: IEnvironmentService,
		private telemetryService: ITelemetryService,
B
Benjamin Pasero 已提交
1801
		private stateService: IStateService,
B
Benjamin Pasero 已提交
1802
		private windowsMainService: IWindowsMainService,
B
Benjamin Pasero 已提交
1803
	) {
1804 1805
		this.mapWindowToDialogQueue = new Map<number, Queue<any>>();
		this.noWindowDialogQueue = new Queue<any>();
B
Benjamin Pasero 已提交
1806 1807
	}

B
Benjamin Pasero 已提交
1808
	pickAndOpen(options: INativeOpenDialogOptions): void {
1809
		this.getFileOrFolderUris(options).then(paths => {
B
Benjamin Pasero 已提交
1810 1811 1812 1813
			const numberOfPaths = paths ? paths.length : 0;

			// Telemetry
			if (options.telemetryEventName) {
K
kieferrm 已提交
1814
				// __GDPR__TODO__ Dynamic event names and dynamic properties. Can not be registered statically.
B
Benjamin Pasero 已提交
1815 1816 1817 1818 1819 1820 1821 1822 1823
				this.telemetryService.publicLog(options.telemetryEventName, {
					...options.telemetryExtraData,
					outcome: numberOfPaths ? 'success' : 'canceled',
					numberOfPaths
				});
			}

			// Open
			if (numberOfPaths) {
1824 1825
				this.windowsMainService.open({
					context: OpenContext.DIALOG,
B
Benjamin Pasero 已提交
1826
					contextWindowId: options.windowId,
1827
					cli: this.environmentService.args,
S
Sandeep Somavarapu 已提交
1828
					urisToOpen: paths,
1829 1830 1831
					forceNewWindow: options.forceNewWindow,
					forceOpenWorkspaceAsFile: options.dialogOptions && !equals(options.dialogOptions.filters, WORKSPACE_FILTER)
				});
1832 1833 1834 1835
			}
		});
	}

1836
	private getFileOrFolderUris(options: IInternalNativeOpenDialogOptions): Promise<IURIToOpen[] | undefined> {
1837

B
Benjamin Pasero 已提交
1838
		// Ensure dialog options
M
Matt Bierner 已提交
1839 1840
		const dialogOptions = options.dialogOptions || Object.create(null);
		options.dialogOptions = dialogOptions;
B
Benjamin Pasero 已提交
1841 1842

		// Ensure defaultPath
M
Matt Bierner 已提交
1843 1844
		if (!dialogOptions.defaultPath) {
			dialogOptions.defaultPath = this.stateService.getItem<string>(Dialogs.workingDirPickerStorageKey);
1845 1846
		}

B
Benjamin Pasero 已提交
1847 1848
		// Ensure properties
		if (typeof options.pickFiles === 'boolean' || typeof options.pickFolders === 'boolean') {
M
Matt Bierner 已提交
1849
			dialogOptions.properties = undefined; // let it override based on the booleans
B
Benjamin Pasero 已提交
1850 1851

			if (options.pickFiles && options.pickFolders) {
M
Matt Bierner 已提交
1852
				dialogOptions.properties = ['multiSelections', 'openDirectory', 'openFile', 'createDirectory'];
B
Benjamin Pasero 已提交
1853 1854 1855
			}
		}

M
Matt Bierner 已提交
1856 1857
		if (!dialogOptions.properties) {
			dialogOptions.properties = ['multiSelections', options.pickFolders ? 'openDirectory' : 'openFile', 'createDirectory'];
B
Benjamin Pasero 已提交
1858 1859
		}

1860
		if (isMacintosh) {
M
Matt Bierner 已提交
1861
			dialogOptions.properties.push('treatPackageAsDirectory'); // always drill into .app files
1862 1863
		}

B
Benjamin Pasero 已提交
1864
		// Show Dialog
M
Matt Bierner 已提交
1865
		const focusedWindow = (typeof options.windowId === 'number' ? this.windowsMainService.getWindowById(options.windowId) : undefined) || this.windowsMainService.getFocusedWindow();
1866

M
Matt Bierner 已提交
1867
		return this.showOpenDialog(dialogOptions, focusedWindow).then(paths => {
1868 1869 1870 1871 1872
			if (paths && paths.length > 0) {

				// Remember path in storage for next time
				this.stateService.setItem(Dialogs.workingDirPickerStorageKey, dirname(paths[0]));

1873 1874 1875 1876 1877 1878
				const result: IURIToOpen[] = [];
				for (const path of paths) {
					result.push({ uri: URI.file(path) });
				}

				return result;
1879 1880
			}

R
Rob Lourens 已提交
1881
			return undefined;
1882 1883 1884 1885 1886 1887 1888 1889 1890 1891 1892 1893 1894 1895 1896 1897 1898
		});
	}

	private getDialogQueue(window?: ICodeWindow): Queue<any> {
		if (!window) {
			return this.noWindowDialogQueue;
		}

		let windowDialogQueue = this.mapWindowToDialogQueue.get(window.id);
		if (!windowDialogQueue) {
			windowDialogQueue = new Queue<any>();
			this.mapWindowToDialogQueue.set(window.id, windowDialogQueue);
		}

		return windowDialogQueue;
	}

J
Johannes Rieken 已提交
1899
	showMessageBox(options: Electron.MessageBoxOptions, window?: ICodeWindow): Promise<IMessageBoxResult> {
1900
		return this.getDialogQueue(window).queue(() => {
B
Benjamin Pasero 已提交
1901
			return new Promise(resolve => {
M
Matt Bierner 已提交
1902
				dialog.showMessageBox(window ? window.win : undefined!, options, (response: number, checkboxChecked: boolean) => {
B
Benjamin Pasero 已提交
1903
					resolve({ button: response, checkboxChecked });
B
Benjamin Pasero 已提交
1904
				});
1905 1906 1907 1908
			});
		});
	}

J
Johannes Rieken 已提交
1909
	showSaveDialog(options: Electron.SaveDialogOptions, window?: ICodeWindow): Promise<string> {
B
Benjamin Pasero 已提交
1910

1911 1912 1913
		function normalizePath(path: string): string {
			if (path && isMacintosh) {
				path = normalizeNFC(path); // normalize paths returned from the OS
1914
			}
1915

1916 1917
			return path;
		}
1918

1919
		return this.getDialogQueue(window).queue(() => {
B
Benjamin Pasero 已提交
1920
			return new Promise(resolve => {
M
Matt Bierner 已提交
1921
				dialog.showSaveDialog(window ? window.win : undefined!, options, path => {
B
Benjamin Pasero 已提交
1922
					resolve(normalizePath(path));
B
Benjamin Pasero 已提交
1923
				});
1924 1925 1926 1927
			});
		});
	}

J
Johannes Rieken 已提交
1928
	showOpenDialog(options: Electron.OpenDialogOptions, window?: ICodeWindow): Promise<string[]> {
B
Benjamin Pasero 已提交
1929

1930 1931 1932 1933 1934 1935
		function normalizePaths(paths: string[]): string[] {
			if (paths && paths.length > 0 && isMacintosh) {
				paths = paths.map(path => normalizeNFC(path)); // normalize paths returned from the OS
			}

			return paths;
1936
		}
B
Benjamin Pasero 已提交
1937

1938
		return this.getDialogQueue(window).queue(() => {
B
Benjamin Pasero 已提交
1939
			return new Promise(resolve => {
B
Benjamin Pasero 已提交
1940 1941

				// Ensure the path exists (if provided)
B
Benjamin Pasero 已提交
1942
				let validatePathPromise: Promise<void> = Promise.resolve();
B
Benjamin Pasero 已提交
1943 1944 1945
				if (options.defaultPath) {
					validatePathPromise = exists(options.defaultPath).then(exists => {
						if (!exists) {
R
Rob Lourens 已提交
1946
							options.defaultPath = undefined;
B
Benjamin Pasero 已提交
1947 1948 1949 1950 1951 1952
						}
					});
				}

				// Show dialog and wrap as promise
				validatePathPromise.then(() => {
M
Matt Bierner 已提交
1953
					dialog.showOpenDialog(window ? window.win : undefined!, options, paths => {
B
Benjamin Pasero 已提交
1954
						resolve(normalizePaths(paths));
B
Benjamin Pasero 已提交
1955
					});
B
Benjamin Pasero 已提交
1956
				});
1957 1958
			});
		});
1959
	}
1960 1961 1962 1963 1964
}

class WorkspacesManager {

	constructor(
1965 1966 1967 1968 1969 1970
		private readonly workspacesMainService: IWorkspacesMainService,
		private readonly backupMainService: IBackupMainService,
		private readonly environmentService: IEnvironmentService,
		private readonly historyMainService: IHistoryMainService,
		private readonly windowsMainService: IWindowsMainService,
	) { }
1971

M
Martin Aeschlimann 已提交
1972
	enterWorkspace(window: ICodeWindow, path: URI): Promise<IEnterWorkspaceResult | null> {
B
Benjamin Pasero 已提交
1973
		if (!window || !window.win || !window.isReady) {
B
Benjamin Pasero 已提交
1974
			return Promise.resolve(null); // return early if the window is not ready or disposed
1975 1976 1977 1978
		}

		return this.isValidTargetWorkspacePath(window, path).then(isValid => {
			if (!isValid) {
B
Benjamin Pasero 已提交
1979
				return null; // return early if the workspace is not valid
1980
			}
M
Martin Aeschlimann 已提交
1981 1982
			const workspaceIdentifier = this.workspacesMainService.getWorkspaceIdentifier(path);
			return this.doOpenWorkspace(window, workspaceIdentifier);
1983 1984 1985 1986
		});

	}

M
Martin Aeschlimann 已提交
1987
	private isValidTargetWorkspacePath(window: ICodeWindow, path?: URI): Promise<boolean> {
1988
		if (!path) {
B
Benjamin Pasero 已提交
1989
			return Promise.resolve(true);
1990 1991
		}

M
Martin Aeschlimann 已提交
1992
		if (window.openedWorkspace && isEqual(window.openedWorkspace.configPath, path)) {
B
Benjamin Pasero 已提交
1993
			return Promise.resolve(false); // window is already opened on a workspace with that path
1994 1995 1996
		}

		// Prevent overwriting a workspace that is currently opened in another window
M
Martin Aeschlimann 已提交
1997
		if (findWindowOnWorkspace(this.windowsMainService.getWindows(), this.workspacesMainService.getWorkspaceIdentifier(path))) {
1998 1999 2000 2001
			const options: Electron.MessageBoxOptions = {
				title: product.nameLong,
				type: 'info',
				buttons: [localize('ok', "OK")],
M
Martin Aeschlimann 已提交
2002
				message: localize('workspaceOpenedMessage', "Unable to save workspace '{0}'", resourcesBasename(path)),
2003
				detail: localize('workspaceOpenedDetail', "The workspace is already opened in another window. Please close that window first and then try again."),
2004 2005 2006
				noLink: true
			};

2007
			return this.windowsMainService.showMessageBox(options, this.windowsMainService.getFocusedWindow()).then(() => false);
2008 2009
		}

B
Benjamin Pasero 已提交
2010
		return Promise.resolve(true); // OK
2011 2012
	}

2013 2014
	private doOpenWorkspace(window: ICodeWindow, workspace: IWorkspaceIdentifier): IEnterWorkspaceResult {
		window.focus();
2015

2016
		// Register window for backups and migrate current backups over
2017
		let backupPath: string | undefined;
2018 2019 2020
		if (!window.config.extensionDevelopmentPath) {
			backupPath = this.backupMainService.registerWorkspaceBackupSync(workspace, window.config.backupPath);
		}
2021

2022 2023 2024 2025 2026
		// if the window was opened on an untitled workspace, delete it.
		if (window.openedWorkspace && this.workspacesMainService.isUntitledWorkspace(window.openedWorkspace)) {
			this.workspacesMainService.deleteUntitledWorkspaceSync(window.openedWorkspace);
		}

2027
		// Update window configuration properly based on transition to workspace
R
Rob Lourens 已提交
2028
		window.config.folderUri = undefined;
2029 2030 2031 2032
		window.config.workspace = workspace;
		window.config.backupPath = backupPath;

		return { workspace, backupPath };
2033 2034
	}

B
Benjamin Pasero 已提交
2035
	pickWorkspaceAndOpen(options: INativeOpenDialogOptions): void {
M
Matt Bierner 已提交
2036
		const window = (typeof options.windowId === 'number' ? this.windowsMainService.getWindowById(options.windowId) : undefined) || this.windowsMainService.getFocusedWindow() || this.windowsMainService.getLastActiveWindow();
2037 2038

		this.windowsMainService.pickFileAndOpen({
R
Rob Lourens 已提交
2039
			windowId: window ? window.id : undefined,
2040 2041 2042 2043 2044
			dialogOptions: {
				buttonLabel: mnemonicButtonLabel(localize({ key: 'openWorkspace', comment: ['&& denotes a mnemonic'] }, "&&Open")),
				title: localize('openWorkspaceTitle', "Open Workspace"),
				filters: WORKSPACE_FILTER,
				properties: ['openFile'],
2045
				defaultPath: options.dialogOptions && options.dialogOptions.defaultPath
2046
			},
2047 2048 2049
			forceNewWindow: options.forceNewWindow,
			telemetryEventName: options.telemetryEventName,
			telemetryExtraData: options.telemetryExtraData
2050 2051 2052
		});
	}

M
Matt Bierner 已提交
2053
	promptToSaveUntitledWorkspace(window: ICodeWindow | undefined, workspace: IWorkspaceIdentifier): Promise<boolean> {
2054 2055 2056 2057 2058 2059 2060 2061 2062 2063 2064 2065 2066 2067 2068 2069 2070 2071 2072 2073 2074 2075 2076 2077 2078 2079 2080 2081 2082 2083 2084 2085 2086
		enum ConfirmResult {
			SAVE,
			DONT_SAVE,
			CANCEL
		}

		const save = { label: mnemonicButtonLabel(localize({ key: 'save', comment: ['&& denotes a mnemonic'] }, "&&Save")), result: ConfirmResult.SAVE };
		const dontSave = { label: mnemonicButtonLabel(localize({ key: 'doNotSave', comment: ['&& denotes a mnemonic'] }, "Do&&n't Save")), result: ConfirmResult.DONT_SAVE };
		const cancel = { label: localize('cancel', "Cancel"), result: ConfirmResult.CANCEL };

		const buttons: { label: string; result: ConfirmResult; }[] = [];
		if (isWindows) {
			buttons.push(save, dontSave, cancel);
		} else if (isLinux) {
			buttons.push(dontSave, cancel, save);
		} else {
			buttons.push(save, cancel, dontSave);
		}

		const options: Electron.MessageBoxOptions = {
			title: this.environmentService.appNameLong,
			message: localize('saveWorkspaceMessage', "Do you want to save your workspace configuration as a file?"),
			detail: localize('saveWorkspaceDetail', "Save your workspace if you plan to open it again."),
			noLink: true,
			type: 'warning',
			buttons: buttons.map(button => button.label),
			cancelId: buttons.indexOf(cancel)
		};

		if (isLinux) {
			options.defaultId = 2;
		}

2087 2088 2089 2090 2091 2092 2093 2094 2095
		return this.windowsMainService.showMessageBox(options, window).then(res => {
			switch (buttons[res.button].result) {

				// Cancel: veto unload
				case ConfirmResult.CANCEL:
					return true;

				// Don't Save: delete workspace
				case ConfirmResult.DONT_SAVE:
2096
					this.workspacesMainService.deleteUntitledWorkspaceSync(workspace);
2097 2098 2099 2100 2101 2102 2103 2104 2105 2106 2107
					return false;

				// Save: save workspace, but do not veto unload
				case ConfirmResult.SAVE: {
					return this.windowsMainService.showSaveDialog({
						buttonLabel: mnemonicButtonLabel(localize({ key: 'save', comment: ['&& denotes a mnemonic'] }, "&&Save")),
						title: localize('saveWorkspace', "Save Workspace"),
						filters: WORKSPACE_FILTER,
						defaultPath: this.getUntitledWorkspaceSaveDialogDefaultPath(workspace)
					}, window).then(target => {
						if (target) {
2108
							return this.workspacesMainService.saveWorkspaceAs(workspace, target).then(savedWorkspace => {
2109
								this.historyMainService.addRecentlyOpened([savedWorkspace], []);
2110
								this.workspacesMainService.deleteUntitledWorkspaceSync(workspace);
2111
								return false;
B
Benjamin Pasero 已提交
2112
							}, () => false);
2113
						}
2114

2115 2116
						return true; // keep veto if no target was provided
					});
2117 2118
				}
			}
2119
		});
2120 2121
	}

2122
	private getUntitledWorkspaceSaveDialogDefaultPath(workspace?: IWorkspaceIdentifier | ISingleFolderWorkspaceIdentifier): string | undefined {
2123
		if (workspace) {
2124
			if (isSingleFolderWorkspaceIdentifier(workspace)) {
R
Rob Lourens 已提交
2125
				return workspace.scheme === Schemas.file ? dirname(workspace.fsPath) : undefined;
J
Johannes Rieken 已提交
2126 2127
			}

M
Martin Aeschlimann 已提交
2128
			const resolvedWorkspace = workspace.configPath.scheme === Schemas.file && this.workspacesMainService.resolveWorkspaceSync(workspace.configPath.fsPath);
J
Johannes Rieken 已提交
2129 2130 2131 2132 2133
			if (resolvedWorkspace && resolvedWorkspace.folders.length > 0) {
				for (const folder of resolvedWorkspace.folders) {
					if (folder.uri.scheme === Schemas.file) {
						return dirname(folder.uri.fsPath);
					}
2134 2135 2136
				}
			}
		}
2137

R
Rob Lourens 已提交
2138
		return undefined;
2139
	}
J
Johannes Rieken 已提交
2140
}