windows.ts 75.7 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 * as fs from 'fs';
7
import { basename, normalize, join, dirname } from 'vs/base/common/path';
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/product/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';
28
import { IProcessEnvironment, isMacintosh, isWindows } from 'vs/base/common/platform';
29
import { IWorkspacesMainService, IWorkspaceIdentifier, WORKSPACE_FILTER, ISingleFolderWorkspaceIdentifier, isSingleFolderWorkspaceIdentifier, hasWorkspaceFileExtension } 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 } from 'vs/base/common/async';
B
Benjamin Pasero 已提交
36
import { exists } from 'vs/base/node/pfs';
37
import { getComparisonKey, isEqual, normalizePath, basename as resourcesBasename, originalFSPath, hasTrailingPathSeparator, removeTrailingPathSeparator } from 'vs/base/common/resources';
M
Martin Aeschlimann 已提交
38
import { getRemoteAuthority } from 'vs/platform/remote/common/remoteHosts';
M
Martin Aeschlimann 已提交
39
import { restoreWindowsState, WindowsStateStorageData, getWindowsStateStoreData } from 'vs/code/electron-main/windowsStateStorage';
E
Erich Gamma 已提交
40

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150
function isFolderPathToOpen(path: IPathToOpen): path is IFolderPathToOpen {
	return !!path.folderUri;
}

interface IFolderPathToOpen {

	// the folder path for a Code instance to open
	folderUri: URI;

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

	// the remote authority for the Code instance to open. Undefined if not remote.
	remoteAuthority?: string;
}

function isWorkspacePathToOpen(path: IPathToOpen): path is IWorkspacePathToOpen {
	return !!path.workspace;
}

interface IWorkspacePathToOpen {

	// the workspace for a Code instance to open
	workspace: IWorkspaceIdentifier;

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

	// the remote authority for the Code instance to open. Undefined if not remote.
	remoteAuthority?: string;
}

J
Joao Moreno 已提交
151
export class WindowsManager implements IWindowsMainService {
J
Joao Moreno 已提交
152

153
	_serviceBrand: any;
E
Erich Gamma 已提交
154

155
	private static readonly windowsStateStorageKey = 'windowsState';
E
Erich Gamma 已提交
156

157
	private static WINDOWS: ICodeWindow[] = [];
E
Erich Gamma 已提交
158

B
Benjamin Pasero 已提交
159
	private initialUserEnv: IProcessEnvironment;
160

E
Erich Gamma 已提交
161
	private windowsState: IWindowsState;
162
	private lastClosedWindowState?: IWindowState;
E
Erich Gamma 已提交
163

164
	private dialogs: Dialogs;
165
	private workspacesManager: WorkspacesManager;
B
Benjamin Pasero 已提交
166

167 168
	private _onWindowReady = new Emitter<ICodeWindow>();
	onWindowReady: CommonEvent<ICodeWindow> = this._onWindowReady.event;
169 170 171 172

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

173 174 175
	private _onWindowLoad = new Emitter<number>();
	onWindowLoad: CommonEvent<number> = this._onWindowLoad.event;

B
Benjamin Pasero 已提交
176 177 178
	private _onWindowsCountChanged = new Emitter<IWindowsCountChangedEvent>();
	onWindowsCountChanged: CommonEvent<IWindowsCountChangedEvent> = this._onWindowsCountChanged.event;

J
Joao Moreno 已提交
179
	constructor(
B
Benjamin Pasero 已提交
180
		private readonly machineId: string,
181 182 183 184 185 186 187 188 189 190
		@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
191
	) {
M
Martin Aeschlimann 已提交
192
		const windowsStateStoreData = this.stateService.getItem<WindowsStateStorageData>(WindowsManager.windowsStateStorageKey);
193 194

		this.windowsState = restoreWindowsState(windowsStateStoreData);
195 196 197
		if (!Array.isArray(this.windowsState.openedWindows)) {
			this.windowsState.openedWindows = [];
		}
198

B
Benjamin Pasero 已提交
199
		this.dialogs = new Dialogs(environmentService, telemetryService, stateService, this);
200
		this.workspacesManager = new WorkspacesManager(workspacesMainService, backupMainService, this);
201
	}
J
Joao Moreno 已提交
202

B
Benjamin Pasero 已提交
203
	ready(initialUserEnv: IProcessEnvironment): void {
204
		this.initialUserEnv = initialUserEnv;
205 206

		this.registerListeners();
E
Erich Gamma 已提交
207 208 209
	}

	private registerListeners(): void {
210

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

B
Benjamin Pasero 已提交
215
			const win = this.getWindowById(windowId);
E
Erich Gamma 已提交
216 217 218 219
			if (win) {
				win.setReady();

				// Event
220
				this._onWindowReady.fire(win);
E
Erich Gamma 已提交
221 222 223
			}
		});

224 225 226 227 228 229 230 231 232 233 234
		// 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');
				}
			});
		}

235 236
		// Handle various lifecycle events around windows
		this.lifecycleService.onBeforeWindowUnload(e => this.onBeforeWindowUnload(e));
B
Benjamin Pasero 已提交
237
		this.lifecycleService.onBeforeWindowClose(window => this.onBeforeWindowClose(window));
238
		this.lifecycleService.onBeforeShutdown(() => this.onBeforeShutdown());
239 240 241 242 243
		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 已提交
244
				this.lastClosedWindowState = undefined;
245 246
			}
		});
247 248
	}

249
	// Note that onBeforeShutdown() and onBeforeWindowClose() are fired in different order depending on the OS:
250
	// - macOS: since the app will not quit when closing the last window, you will always first get
251
	//          the onBeforeShutdown() event followed by N onbeforeWindowClose() events for each window
252 253
	// - 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()
254
	//          and then onBeforeShutdown(). Using the quit action however will first issue onBeforeShutdown()
255
	//          and then onBeforeWindowClose().
256 257 258 259 260 261 262
	//
	// 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
263
	// - onBeforeShutdown(N): number of windows reported in this event handler
264 265 266
	// - onBeforeWindowClose(N, M): number of windows reported and quitRequested boolean in this event handler
	//
	// macOS
267 268 269
	// 	-     quit(1): onBeforeShutdown(1), onBeforeWindowClose(1, true)
	// 	-     quit(2): onBeforeShutdown(2), onBeforeWindowClose(2, true), onBeforeWindowClose(2, true)
	// 	-     quit(0): onBeforeShutdown(0)
270 271 272
	// 	-    close(1): onBeforeWindowClose(1, false)
	//
	// Windows
273 274
	// 	-     quit(1): onBeforeShutdown(1), onBeforeWindowClose(1, true)
	// 	-     quit(2): onBeforeShutdown(2), onBeforeWindowClose(2, true), onBeforeWindowClose(2, true)
275
	// 	-    close(1): onBeforeWindowClose(2, false)[not last window]
276 277
	// 	-    close(1): onBeforeWindowClose(1, false), onBeforeShutdown(0)[last window]
	// 	- closeAll(2): onBeforeWindowClose(2, false), onBeforeWindowClose(2, false), onBeforeShutdown(0)
278 279
	//
	// Linux
280 281
	// 	-     quit(1): onBeforeShutdown(1), onBeforeWindowClose(1, true)
	// 	-     quit(2): onBeforeShutdown(2), onBeforeWindowClose(2, true), onBeforeWindowClose(2, true)
282
	// 	-    close(1): onBeforeWindowClose(2, false)[not last window]
283 284
	// 	-    close(1): onBeforeWindowClose(1, false), onBeforeShutdown(0)[last window]
	// 	- closeAll(2): onBeforeWindowClose(2, false), onBeforeWindowClose(2, false), onBeforeShutdown(0)
285
	//
286
	private onBeforeShutdown(): void {
287
		const currentWindowsState: IWindowsState = {
288
			openedWindows: [],
289
			lastPluginDevelopmentHostWindow: this.windowsState.lastPluginDevelopmentHostWindow,
290
			lastActiveWindow: this.lastClosedWindowState
291 292 293 294 295 296 297 298
		};

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

300
			if (activeWindow) {
301
				currentWindowsState.lastActiveWindow = this.toWindowState(activeWindow);
E
Erich Gamma 已提交
302
			}
303 304 305 306 307
		}

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

311
		// 3.) All windows (except extension host) for N >= 2 to support restoreWindows: all or for auto update
312 313 314 315 316
		//
		// 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) {
317
			currentWindowsState.openedWindows = WindowsManager.WINDOWS.filter(w => !w.isExtensionDevelopmentHost).map(w => this.toWindowState(w));
318
		}
E
Erich Gamma 已提交
319

320
		// Persist
321
		this.stateService.setItem(WindowsManager.windowsStateStorageKey, getWindowsStateStoreData(currentWindowsState));
322
	}
323

324
	// See note on #onBeforeShutdown() for details how these events are flowing
325
	private onBeforeWindowClose(win: ICodeWindow): void {
B
Benjamin Pasero 已提交
326
		if (this.lifecycleService.quitRequested) {
327 328 329 330
			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
331
		const state: IWindowState = this.toWindowState(win);
332 333 334 335
		if (win.isExtensionDevelopmentHost && !win.isExtensionTestHost) {
			this.windowsState.lastPluginDevelopmentHostWindow = state; // do not let test run window state overwrite our extension development state
		}

336
		// Any non extension host window with same workspace or folder
337
		else if (!win.isExtensionDevelopmentHost && (!!win.openedWorkspace || !!win.openedFolderUri)) {
338
			this.windowsState.openedWindows.forEach(o => {
B
fix npe  
Benjamin Pasero 已提交
339
				const sameWorkspace = win.openedWorkspace && o.workspace && o.workspace.id === win.openedWorkspace.id;
340
				const sameFolder = win.openedFolderUri && o.folderUri && isEqual(o.folderUri, win.openedFolderUri);
341 342

				if (sameWorkspace || sameFolder) {
343 344 345 346 347 348 349
					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.
350 351 352
		// 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) {
353 354
			this.lastClosedWindowState = state;
		}
E
Erich Gamma 已提交
355 356
	}

357
	private toWindowState(win: ICodeWindow): IWindowState {
358
		return {
359
			workspace: win.openedWorkspace,
360
			folderUri: win.openedFolderUri,
361
			backupPath: win.backupPath,
M
Martin Aeschlimann 已提交
362
			remoteAuthority: win.remoteAuthority,
363 364 365 366
			uiState: win.serializeWindowState()
		};
	}

B
Benjamin Pasero 已提交
367
	open(openConfig: IOpenConfiguration): ICodeWindow[] {
368
		this.logService.trace('windowsManager#open');
369
		openConfig = this.validateOpenConfig(openConfig);
370

371
		let pathsToOpen = this.getPathsToOpen(openConfig);
372

373 374 375 376 377 378
		const foldersToAdd: IFolderPathToOpen[] = [];
		const foldersToOpen: IFolderPathToOpen[] = [];
		const workspacesToOpen: IWorkspacePathToOpen[] = [];
		const emptyToRestore: IEmptyWindowBackupInfo[] = []; // empty windows with backupPath
		let emptyToOpen: number = 0;
		let fileInputs: IFileInputs | undefined; 		// collect all file inputs
379
		for (const path of pathsToOpen) {
380 381 382 383 384 385 386 387 388 389 390
			if (isFolderPathToOpen(path)) {
				if (openConfig.addMode) {
					// When run with --add, take the folders that are to be opened as
					// folders that should be added to the currently active window.
					foldersToAdd.push(path);
				} else {
					foldersToOpen.push(path);
				}
			} else if (isWorkspacePathToOpen(path)) {
				workspacesToOpen.push(path);
			} else if (path.fileUri) {
391
				if (!fileInputs) {
M
Martin Aeschlimann 已提交
392
					fileInputs = { filesToCreate: [], filesToOpen: [], filesToDiff: [], remoteAuthority: path.remoteAuthority };
393 394 395 396 397 398
				}
				if (!path.createFilePath) {
					fileInputs.filesToOpen.push(path);
				} else {
					fileInputs.filesToCreate.push(path);
				}
399 400 401 402
			} else if (path.backupPath) {
				emptyToRestore.push({ backupFolder: basename(path.backupPath), remoteAuthority: path.remoteAuthority });
			} else {
				emptyToOpen++;
403 404
			}
		}
405 406 407

		// When run with --diff, take the files to open as files to diff
		// if there are exactly two files provided.
408 409 410 411
		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 已提交
412 413
		}

414
		// When run with --wait, make sure we keep the paths to wait for
415 416
		if (fileInputs && openConfig.cli.wait && openConfig.cli.waitMarkerFilePath) {
			fileInputs.filesToWait = { paths: [...fileInputs.filesToDiff, ...fileInputs.filesToOpen, ...fileInputs.filesToCreate], waitMarkerFilePath: openConfig.cli.waitMarkerFilePath };
417 418
		}

419
		//
420
		// These are windows to restore because of hot-exit or from previous session (only performed once on startup!)
421
		//
422 423
		let foldersToRestore;
		let workspacesToRestore;
B
Benjamin Pasero 已提交
424
		if (openConfig.initialStartup && !openConfig.cli.extensionDevelopmentPath && !openConfig.cli['disable-restore-windows']) {
425
			foldersToRestore = this.backupMainService.getFolderBackupPaths();
426
			foldersToAdd.push(...foldersToRestore.map(f => ({ folderUri: f, remoteAuhority: getRemoteAuthority(f), isRestored: true })));
427

428 429 430
			// collect from workspaces with hot-exit backups and from previous window session
			workspacesToRestore = [...this.backupMainService.getWorkspaceBackups(), ...this.workspacesMainService.getUntitledWorkspacesSync()];
			workspacesToOpen.push(...workspacesToRestore);
431

432 433 434
			emptyToRestore.push(...this.backupMainService.getEmptyWindowBackupPaths());
		} else {
			emptyToRestore.length = 0;
435
		}
436 437

		// Open based on config
438
		const usedWindows = this.doOpen(openConfig, workspacesToOpen, foldersToOpen, emptyToRestore, emptyToOpen, fileInputs, foldersToAdd);
439

440
		// Make sure to pass focus to the most relevant of the windows if we open multiple
441
		if (usedWindows.length > 1) {
442

443
			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);
444 445
			let focusLastOpened = true;
			let focusLastWindow = true;
446

447 448
			// 1.) focus last active window if we are not instructed to open any paths
			if (focusLastActive) {
449 450 451
				const lastActiveWindow = usedWindows.filter(w => w.backupPath === this.windowsState.lastActiveWindow!.backupPath);
				if (lastActiveWindow.length) {
					lastActiveWindow[0].focus();
452 453
					focusLastOpened = false;
					focusLastWindow = false;
454 455 456
				}
			}

457 458 459 460 461
			// 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 (
462 463
						(usedWindow.openedWorkspace && workspacesToRestore.some(workspace => workspace.workspace.id === usedWindow.openedWorkspace!.id)) ||	// skip over restored workspace
						(usedWindow.openedFolderUri && foldersToRestore.some(folder => isEqual(folder.folderUri, usedWindow.openedFolderUri))) ||			// skip over restored folder
M
Matt Bierner 已提交
464
						(usedWindow.backupPath && emptyToRestore.some(empty => empty.backupFolder === basename(usedWindow.backupPath!)))				// skip over restored empty window
465 466 467 468 469 470 471 472 473 474 475 476
					) {
						continue;
					}

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

			// 3.) finally, always ensure to have at least last used window focused
			if (focusLastWindow) {
477
				usedWindows[usedWindows.length - 1].focus();
478 479
			}
		}
480

481 482
		// 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
483
		if (!usedWindows.some(w => w.isExtensionDevelopmentHost) && !openConfig.diffMode) {
484
			const recentlyOpenedWorkspaces: Array<IWorkspaceIdentifier | ISingleFolderWorkspaceIdentifier> = [];
485
			const recentlyOpenedFiles: URI[] = [];
486

B
Benjamin Pasero 已提交
487
			pathsToOpen.forEach(win => {
M
Matt Bierner 已提交
488 489 490 491
				if (win.workspace) {
					recentlyOpenedWorkspaces.push(win.workspace);
				} else if (win.folderUri) {
					recentlyOpenedWorkspaces.push(win.folderUri);
492 493
				} else if (win.fileUri) {
					recentlyOpenedFiles.push(win.fileUri);
494 495 496
				}
			});

497 498 499
			if (!this.environmentService.skipAddToRecentlyOpened) {
				this.historyMainService.addRecentlyOpened(recentlyOpenedWorkspaces, recentlyOpenedFiles);
			}
500
		}
501

502
		// If we got started with --wait from the CLI, we need to signal to the outside when the window
503 504
		// 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.
505
		if (openConfig.context === OpenContext.CLI && openConfig.cli.wait && openConfig.cli.waitMarkerFilePath && usedWindows.length === 1 && usedWindows[0]) {
M
Matt Bierner 已提交
506
			this.waitForWindowCloseOrLoad(usedWindows[0].id).then(() => fs.unlink(openConfig.cli.waitMarkerFilePath!, _error => undefined));
507 508
		}

509 510 511
		return usedWindows;
	}

512 513 514 515 516 517 518 519 520 521
	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;
	}

522 523
	private doOpen(
		openConfig: IOpenConfiguration,
524 525
		workspacesToOpen: IWorkspacePathToOpen[],
		foldersToOpen: IFolderPathToOpen[],
526
		emptyToRestore: IEmptyWindowBackupInfo[],
527
		emptyToOpen: number,
528
		fileInputs: IFileInputs | undefined,
529
		foldersToAdd: IFolderPathToOpen[]
530
	) {
531
		const usedWindows: ICodeWindow[] = [];
532

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

536 537
		// Handle folders to add by looking for the last active workspace (not on initial startup)
		if (!openConfig.initialStartup && foldersToAdd.length > 0) {
538
			const authority = foldersToAdd[0].remoteAuthority;
539
			const lastActiveWindow = this.getLastActiveWindowForAuthority(authority);
540
			if (lastActiveWindow) {
541
				usedWindows.push(this.doAddFoldersToExistingWindow(lastActiveWindow, foldersToAdd.map(f => f.folderUri)));
542 543 544
			}
		}

B
Benjamin Pasero 已提交
545
		// 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
546
		const potentialWindowsCount = foldersToOpen.length + workspacesToOpen.length + emptyToRestore.length;
547
		if (potentialWindowsCount === 0 && fileInputs) {
E
Erich Gamma 已提交
548

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

554
			let bestWindowOrFolder = findBestWindowOrFolderForFile({
555
				windows,
556 557
				newWindow: openFilesInNewWindow,
				context: openConfig.context,
558
				fileUri: fileToCheck && fileToCheck.fileUri,
559
				localWorkspaceResolver: workspace => workspace.configPath.scheme === Schemas.file ? this.workspacesMainService.resolveLocalWorkspaceSync(workspace.configPath) : null
560
			});
B
Benjamin Pasero 已提交
561

562 563 564 565 566
			// We found a window to open the files in
			if (bestWindowOrFolder instanceof CodeWindow) {

				// Window is workspace
				if (bestWindowOrFolder.openedWorkspace) {
567
					workspacesToOpen.push({ workspace: bestWindowOrFolder.openedWorkspace, remoteAuthority: bestWindowOrFolder.remoteAuthority });
568 569 570
				}

				// Window is single folder
571
				else if (bestWindowOrFolder.openedFolderUri) {
572
					foldersToOpen.push({ folderUri: bestWindowOrFolder.openedFolderUri, remoteAuthority: bestWindowOrFolder.remoteAuthority });
573 574 575 576 577 578
				}

				// Window is empty
				else {

					// Do open files
579
					usedWindows.push(this.doOpenFilesInExistingWindow(openConfig, bestWindowOrFolder, fileInputs));
580 581

					// Reset these because we handled them
R
Rob Lourens 已提交
582
					fileInputs = undefined;
583
				}
584 585 586
			}

			// Finally, if no window or folder is found, just open the files in an empty window
E
Erich Gamma 已提交
587
			else {
B
Benjamin Pasero 已提交
588
				usedWindows.push(this.openInBrowserWindow({
B
Benjamin Pasero 已提交
589 590 591
					userEnv: openConfig.userEnv,
					cli: openConfig.cli,
					initialStartup: openConfig.initialStartup,
592
					fileInputs,
593
					forceNewWindow: true,
M
Martin Aeschlimann 已提交
594
					remoteAuthority: fileInputs.remoteAuthority,
595
					forceNewTabbedWindow: openConfig.forceNewTabbedWindow
B
Benjamin Pasero 已提交
596
				}));
E
Erich Gamma 已提交
597

598
				// Reset these because we handled them
R
Rob Lourens 已提交
599
				fileInputs = undefined;
E
Erich Gamma 已提交
600 601 602
			}
		}

603
		// Handle workspaces to open (instructed and to restore)
604
		const allWorkspacesToOpen = arrays.distinct(workspacesToOpen, workspace => workspace.workspace.id); // prevent duplicates
605 606 607
		if (allWorkspacesToOpen.length > 0) {

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

				// Do open files
614
				usedWindows.push(this.doOpenFilesInExistingWindow(openConfig, windowOnWorkspace, fileInputsForWindow));
615 616

				// Reset these because we handled them
617
				if (fileInputsForWindow) {
R
Rob Lourens 已提交
618
					fileInputs = undefined;
619
				}
620 621 622 623 624 625

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

			// Open remaining ones
			allWorkspacesToOpen.forEach(workspaceToOpen => {
626
				if (windowsOnWorkspace.some(win => win.openedWorkspace!.id === workspaceToOpen.workspace.id)) {
627 628 629
					return; // ignore folders that are already open
				}

630
				const remoteAuthority = workspaceToOpen.remoteAuthority;
631
				const fileInputsForWindow = (fileInputs && fileInputs.remoteAuthority === remoteAuthority) ? fileInputs : undefined;
632

633
				// Do open folder
634
				usedWindows.push(this.doOpenFolderOrWorkspace(openConfig, workspaceToOpen, openFolderInNewWindow, fileInputsForWindow));
635 636

				// Reset these because we handled them
637
				if (fileInputsForWindow) {
R
Rob Lourens 已提交
638
					fileInputs = undefined;
639
				}
640 641 642 643 644

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

645
		// Handle folders to open (instructed and to restore)
646
		const allFoldersToOpen = arrays.distinct(foldersToOpen, folder => getComparisonKey(folder.folderUri)); // prevent duplicates
647

648
		if (allFoldersToOpen.length > 0) {
E
Erich Gamma 已提交
649 650

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

656
				// Do open files
657
				usedWindows.push(this.doOpenFilesInExistingWindow(openConfig, windowOnFolderPath, fileInputsForWindow));
658

E
Erich Gamma 已提交
659
				// Reset these because we handled them
660
				if (fileInputsForWindow) {
R
Rob Lourens 已提交
661
					fileInputs = undefined;
662
				}
E
Erich Gamma 已提交
663

B
Benjamin Pasero 已提交
664
				openFolderInNewWindow = true; // any other folders to open must open in new window then
E
Erich Gamma 已提交
665 666 667
			}

			// Open remaining ones
668
			allFoldersToOpen.forEach(folderToOpen => {
669

670
				if (windowsOnFolderPath.some(win => isEqual(win.openedFolderUri, folderToOpen.folderUri))) {
E
Erich Gamma 已提交
671 672 673
					return; // ignore folders that are already open
				}

674
				const remoteAuthority = folderToOpen.remoteAuthority;
R
Rob Lourens 已提交
675
				const fileInputsForWindow = (fileInputs && fileInputs.remoteAuthority === remoteAuthority) ? fileInputs : undefined;
676

677
				// Do open folder
678
				usedWindows.push(this.doOpenFolderOrWorkspace(openConfig, folderToOpen, openFolderInNewWindow, fileInputsForWindow));
E
Erich Gamma 已提交
679 680

				// Reset these because we handled them
681
				if (fileInputsForWindow) {
R
Rob Lourens 已提交
682
					fileInputs = undefined;
683
				}
E
Erich Gamma 已提交
684

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

689
		// Handle empty to restore
690
		if (emptyToRestore.length > 0) {
691
			emptyToRestore.forEach(emptyWindowBackupInfo => {
M
Martin Aeschlimann 已提交
692
				const remoteAuthority = emptyWindowBackupInfo.remoteAuthority;
R
Rob Lourens 已提交
693
				const fileInputsForWindow = (fileInputs && fileInputs.remoteAuthority === remoteAuthority) ? fileInputs : undefined;
694

B
Benjamin Pasero 已提交
695
				usedWindows.push(this.openInBrowserWindow({
B
Benjamin Pasero 已提交
696 697 698
					userEnv: openConfig.userEnv,
					cli: openConfig.cli,
					initialStartup: openConfig.initialStartup,
699
					fileInputs: fileInputsForWindow,
M
Martin Aeschlimann 已提交
700
					remoteAuthority,
B
Benjamin Pasero 已提交
701
					forceNewWindow: true,
702
					forceNewTabbedWindow: openConfig.forceNewTabbedWindow,
703
					emptyWindowBackupInfo
B
Benjamin Pasero 已提交
704
				}));
705

B
wip  
Benjamin Pasero 已提交
706
				// Reset these because we handled them
707
				if (fileInputsForWindow) {
R
Rob Lourens 已提交
708
					fileInputs = undefined;
709
				}
B
wip  
Benjamin Pasero 已提交
710

B
Benjamin Pasero 已提交
711
				openFolderInNewWindow = true; // any other folders to open must open in new window then
712 713
			});
		}
B
Benjamin Pasero 已提交
714

715
		// Handle empty to open (only if no other window opened)
716 717 718 719
		if (usedWindows.length === 0 || fileInputs) {
			if (fileInputs && !emptyToOpen) {
				emptyToOpen++;
			}
R
Rob Lourens 已提交
720
			const remoteAuthority = fileInputs ? fileInputs.remoteAuthority : (openConfig.cli && openConfig.cli.remote || undefined);
721
			for (let i = 0; i < emptyToOpen; i++) {
B
Benjamin Pasero 已提交
722
				usedWindows.push(this.openInBrowserWindow({
B
Benjamin Pasero 已提交
723 724 725
					userEnv: openConfig.userEnv,
					cli: openConfig.cli,
					initialStartup: openConfig.initialStartup,
M
Martin Aeschlimann 已提交
726
					remoteAuthority,
727
					forceNewWindow: openFolderInNewWindow,
728 729
					forceNewTabbedWindow: openConfig.forceNewTabbedWindow,
					fileInputs
B
Benjamin Pasero 已提交
730
				}));
E
Erich Gamma 已提交
731

732
				// Reset these because we handled them
R
Rob Lourens 已提交
733
				fileInputs = undefined;
734
				openFolderInNewWindow = true; // any other window to open must open in new window then
735 736
			}
		}
E
Erich Gamma 已提交
737

738
		return arrays.distinct(usedWindows);
E
Erich Gamma 已提交
739 740
	}

741
	private doOpenFilesInExistingWindow(configuration: IOpenConfiguration, window: ICodeWindow, fileInputs?: IFileInputs): ICodeWindow {
742 743
		window.focus(); // make sure window has focus

B
Benjamin Pasero 已提交
744 745 746 747 748 749 750 751 752 753 754 755 756
		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 已提交
757 758

		return window;
759 760
	}

761
	private doAddFoldersToExistingWindow(window: ICodeWindow, foldersToAdd: URI[]): ICodeWindow {
762 763
		window.focus(); // make sure window has focus

B
Benjamin Pasero 已提交
764
		window.sendWhenReady('vscode:addFolders', { foldersToAdd });
765 766 767 768

		return window;
	}

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

774 775 776 777
		const browserWindow = this.openInBrowserWindow({
			userEnv: openConfig.userEnv,
			cli: openConfig.cli,
			initialStartup: openConfig.initialStartup,
778
			workspace: folderOrWorkspace.workspace,
779
			folderUri: folderOrWorkspace.folderUri,
780
			fileInputs,
M
Martin Aeschlimann 已提交
781
			remoteAuthority: folderOrWorkspace.remoteAuthority,
B
Benjamin Pasero 已提交
782
			forceNewWindow,
783
			forceNewTabbedWindow: openConfig.forceNewTabbedWindow,
784
			windowToUse
785 786 787 788 789
		});

		return browserWindow;
	}

B
Benjamin Pasero 已提交
790 791
	private getPathsToOpen(openConfig: IOpenConfiguration): IPathToOpen[] {
		let windowsToOpen: IPathToOpen[];
792
		let isCommandLineOrAPICall = false;
E
Erich Gamma 已提交
793

794
		// Extract paths: from API
S
Sandeep Somavarapu 已提交
795
		if (openConfig.urisToOpen && openConfig.urisToOpen.length > 0) {
796
			windowsToOpen = this.doExtractPathsFromAPI(openConfig);
797
			isCommandLineOrAPICall = true;
E
Erich Gamma 已提交
798 799
		}

B
Benjamin Pasero 已提交
800 801
		// Check for force empty
		else if (openConfig.forceEmpty) {
802
			windowsToOpen = [Object.create(null)];
E
Erich Gamma 已提交
803 804
		}

805
		// Extract paths: from CLI
806
		else if (hasArgs(openConfig.cli._) || hasArgs(openConfig.cli['folder-uri']) || hasArgs(openConfig.cli['file-uri'])) {
807
			windowsToOpen = this.doExtractPathsFromCLI(openConfig.cli);
808
			isCommandLineOrAPICall = true;
B
Benjamin Pasero 已提交
809 810
		}

811
		// Extract windows: from previous session
B
Benjamin Pasero 已提交
812
		else {
813
			windowsToOpen = this.doGetWindowsFromLastSession();
B
Benjamin Pasero 已提交
814 815
		}

816 817
		// Convert multiple folders into workspace (if opened via API or CLI)
		// This will ensure to open these folders in one window instead of multiple
818 819
		// If we are in addMode, we should not do this because in that case all
		// folders should be added to the existing window.
820
		if (!openConfig.addMode && isCommandLineOrAPICall) {
821
			const foldersToOpen = windowsToOpen.filter(path => !!path.folderUri);
822 823 824 825 826 827 828 829 830
			if (foldersToOpen.length > 1) {
				let remoteAuthority = foldersToOpen[0].remoteAuthority;
				if (foldersToOpen.every(f => f.remoteAuthority === remoteAuthority)) { // only if all folder have the same authority
					const workspace = this.workspacesMainService.createUntitledWorkspaceSync(foldersToOpen.map(folder => ({ uri: folder.folderUri! })));

					// Add workspace and remove folders thereby
					windowsToOpen.push({ workspace, remoteAuthority });
					windowsToOpen = windowsToOpen.filter(path => !path.folderUri);
				}
831 832 833
			}
		}

834
		return windowsToOpen;
E
Erich Gamma 已提交
835 836
	}

837
	private doExtractPathsFromAPI(openConfig: IOpenConfiguration): IPathToOpen[] {
M
Matt Bierner 已提交
838
		const pathsToOpen: IPathToOpen[] = [];
839 840
		const cli = openConfig.cli;
		let parseOptions: IPathParseOptions = { gotoLineMode: cli && cli.goto, forceOpenWorkspaceAsFile: openConfig.forceOpenWorkspaceAsFile };
M
Matt Bierner 已提交
841
		for (const pathToOpen of openConfig.urisToOpen || []) {
M
Martin Aeschlimann 已提交
842 843 844 845
			if (!pathToOpen) {
				continue;
			}

846
			const path = this.parseUri(pathToOpen.uri, pathToOpen.typeHint, parseOptions);
M
Martin Aeschlimann 已提交
847 848 849
			if (path) {
				pathsToOpen.push(path);
			} else {
850

B
Benjamin Pasero 已提交
851
				// Warn about the invalid URI or path
M
Martin Aeschlimann 已提交
852
				let message, detail;
853
				if (pathToOpen.uri.scheme === Schemas.file) {
M
Martin Aeschlimann 已提交
854
					message = localize('pathNotExistTitle', "Path does not exist");
855
					detail = localize('pathNotExistDetail', "The path '{0}' does not seem to exist anymore on disk.", pathToOpen.uri.fsPath);
M
Martin Aeschlimann 已提交
856 857
				} else {
					message = localize('uriInvalidTitle', "URI can not be opened");
858
					detail = localize('uriInvalidDetail', "The URI '{0}' is not valid and can not be opened.", pathToOpen.uri.toString());
M
Martin Aeschlimann 已提交
859
				}
860
				const options: Electron.MessageBoxOptions = {
861 862
					title: product.nameLong,
					type: 'info',
863
					buttons: [localize('ok', "OK")],
M
Martin Aeschlimann 已提交
864 865
					message,
					detail,
866 867 868
					noLink: true
				};

869
				this.dialogs.showMessageBox(options, this.getFocusedWindow());
870
			}
M
Martin Aeschlimann 已提交
871
		}
872 873 874 875
		return pathsToOpen;
	}

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

		// folder uris
880
		const folderUris = asArray(cli['folder-uri']);
M
Martin Aeschlimann 已提交
881
		for (let folderUri of folderUris) {
882
			const path = this.parseUri(this.argToUri(folderUri), 'folder', parseOptions);
M
Martin Aeschlimann 已提交
883 884 885
			if (path) {
				pathsToOpen.push(path);
			}
886 887 888 889
		}

		// file uris
		const fileUris = asArray(cli['file-uri']);
M
Martin Aeschlimann 已提交
890
		for (let fileUri of fileUris) {
891
			const path = this.parseUri(this.argToUri(fileUri), 'file');
M
Martin Aeschlimann 已提交
892 893 894
			if (path) {
				pathsToOpen.push(path);
			}
895 896 897
		}

		// folder or file paths
M
Martin Aeschlimann 已提交
898 899 900 901 902 903
		const cliArgs = asArray(cli._);
		for (let cliArg of cliArgs) {
			const path = this.parsePath(cliArg, parseOptions);
			if (path) {
				pathsToOpen.push(path);
			}
904 905
		}

M
Martin Aeschlimann 已提交
906
		if (pathsToOpen.length) {
907
			return pathsToOpen;
B
Benjamin Pasero 已提交
908 909 910 911 912 913
		}

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

B
Benjamin Pasero 已提交
914
	private doGetWindowsFromLastSession(): IPathToOpen[] {
915
		const restoreWindows = this.getRestoreWindowsSetting();
B
Benjamin Pasero 已提交
916

917
		switch (restoreWindows) {
B
Benjamin Pasero 已提交
918

919
			// none: we always open an empty window
920 921
			case 'none':
				return [Object.create(null)];
B
Benjamin Pasero 已提交
922

923
			// one: restore last opened workspace/folder or empty window
924 925
			// all: restore all windows
			// folders: restore last opened folders only
926
			case 'one':
927 928
			case 'all':
			case 'folders':
929 930 931
				const openedWindows: IWindowState[] = [];
				if (restoreWindows !== 'one') {
					openedWindows.push(...this.windowsState.openedWindows);
932
				}
933 934
				if (this.windowsState.lastActiveWindow) {
					openedWindows.push(this.windowsState.lastActiveWindow);
935
				}
936

937 938 939
				const windowsToOpen: IPathToOpen[] = [];
				for (const openedWindow of openedWindows) {
					if (openedWindow.workspace) { // Workspaces
940
						const pathToOpen = this.parseUri(openedWindow.workspace.configPath, 'file', { remoteAuthority: openedWindow.remoteAuthority });
941 942 943 944
						if (pathToOpen && pathToOpen.workspace) {
							windowsToOpen.push(pathToOpen);
						}
					} else if (openedWindow.folderUri) { // Folders
945
						const pathToOpen = this.parseUri(openedWindow.folderUri, 'folder', { remoteAuthority: openedWindow.remoteAuthority });
946 947 948 949
						if (pathToOpen && pathToOpen.folderUri) {
							windowsToOpen.push(pathToOpen);
						}
					} else if (restoreWindows !== 'folders' && openedWindow.backupPath) { // Windows that were Empty
M
Martin Aeschlimann 已提交
950
						windowsToOpen.push({ backupPath: openedWindow.backupPath, remoteAuthority: openedWindow.remoteAuthority });
951
					}
952 953 954 955 956 957 958
				}

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

				break;
B
Benjamin Pasero 已提交
959
		}
E
Erich Gamma 已提交
960

961
		// Always fallback to empty window
B
Benjamin Pasero 已提交
962
		return [Object.create(null)];
E
Erich Gamma 已提交
963 964
	}

965 966 967 968 969
	private getRestoreWindowsSetting(): RestoreWindowsSetting {
		let restoreWindows: RestoreWindowsSetting;
		if (this.lifecycleService.wasRestarted) {
			restoreWindows = 'all'; // always reopen all windows when an update was applied
		} else {
970
			const windowConfig = this.configurationService.getValue<IWindowSettings>('window');
971
			restoreWindows = ((windowConfig && windowConfig.restoreWindows) || 'one') as RestoreWindowsSetting;
972 973 974 975 976 977 978 979 980

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

		return restoreWindows;
	}

981
	private argToUri(arg: string): URI | undefined {
M
Martin Aeschlimann 已提交
982 983 984
		try {
			let uri = URI.parse(arg);
			if (!uri.scheme) {
M
Martin Aeschlimann 已提交
985
				this.logService.error(`Invalid URI input string, scheme missing: ${arg}`);
986
				return undefined;
M
Martin Aeschlimann 已提交
987 988 989
			}
			return uri;
		} catch (e) {
M
Martin Aeschlimann 已提交
990
			this.logService.error(`Invalid URI input string: ${arg}, ${e.message}`);
991
		}
992
		return undefined;
993 994
	}

995
	private parseUri(uri: URI | undefined, typeHint?: URIType, options: IPathParseOptions = {}): IPathToOpen | undefined {
M
Martin Aeschlimann 已提交
996
		if (!uri || !uri.scheme) {
997
			return undefined;
998
		}
M
Martin Aeschlimann 已提交
999 1000
		if (uri.scheme === Schemas.file) {
			return this.parsePath(uri.fsPath, options);
1001
		}
M
Martin Aeschlimann 已提交
1002 1003

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

1006 1007
		// normalize URI
		uri = normalizePath(uri);
1008 1009 1010


		// remove trailing slash
1011 1012
		if (hasTrailingPathSeparator(uri)) {
			uri = removeTrailingPathSeparator(uri);
1013 1014 1015 1016 1017 1018
			if (!typeHint) {
				typeHint = 'folder';
			}
		}

		// if there's no type hint
1019
		if (!typeHint && (hasWorkspaceFileExtension(uri.path) || options.gotoLineMode)) {
1020
			typeHint = 'file';
1021
		}
1022 1023 1024

		if (typeHint === 'file') {
			if (options.gotoLineMode) {
1025 1026 1027 1028
				const parsedPath = parseLineAndColumnAware(uri.path);
				return {
					fileUri: uri.with({ path: parsedPath.path }),
					lineNumber: parsedPath.line,
M
Martin Aeschlimann 已提交
1029 1030
					columnNumber: parsedPath.column,
					remoteAuthority
1031 1032
				};
			}
1033
			if (hasWorkspaceFileExtension(uri.path) && !options.forceOpenWorkspaceAsFile) {
1034 1035 1036 1037 1038
				return {
					workspace: this.workspacesMainService.getWorkspaceIdentifier(uri),
					remoteAuthority
				};
			}
1039
			return {
M
Martin Aeschlimann 已提交
1040 1041
				fileUri: uri,
				remoteAuthority
1042 1043
			};
		}
1044
		return {
M
Martin Aeschlimann 已提交
1045 1046
			folderUri: uri,
			remoteAuthority
1047 1048 1049
		};
	}

1050
	private parsePath(anyPath: string, options: IPathParseOptions): IPathToOpen | undefined {
E
Erich Gamma 已提交
1051
		if (!anyPath) {
1052
			return undefined;
E
Erich Gamma 已提交
1053 1054
		}

1055
		let lineNumber, columnNumber: number | undefined;
1056

1057
		if (options.gotoLineMode) {
1058 1059 1060
			const parsedPath = parseLineAndColumnAware(anyPath);
			lineNumber = parsedPath.line;
			columnNumber = parsedPath.column;
1061

E
Erich Gamma 已提交
1062 1063 1064
			anyPath = parsedPath.path;
		}

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

1068
		const candidate = normalize(anyPath);
E
Erich Gamma 已提交
1069
		try {
B
Benjamin Pasero 已提交
1070
			const candidateStat = fs.statSync(candidate);
E
Erich Gamma 已提交
1071
			if (candidateStat) {
1072
				if (candidateStat.isFile()) {
1073

1074
					// Workspace (unless disabled via flag)
1075
					if (!options.forceOpenWorkspaceAsFile) {
1076
						const workspace = this.workspacesMainService.resolveLocalWorkspaceSync(URI.file(candidate));
1077
						if (workspace) {
M
Martin Aeschlimann 已提交
1078
							return { workspace: { id: workspace.id, configPath: workspace.configPath }, remoteAuthority };
1079
						}
1080 1081 1082
					}

					// File
1083
					return {
1084
						fileUri: URI.file(candidate),
1085 1086
						lineNumber,
						columnNumber,
M
Martin Aeschlimann 已提交
1087
						remoteAuthority
1088 1089 1090
					};
				}

1091 1092 1093 1094 1095
				// 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 已提交
1096 1097
						folderUri: URI.file(candidate),
						remoteAuthority
1098 1099
					};
				}
E
Erich Gamma 已提交
1100 1101
			}
		} catch (error) {
S
Sandeep Somavarapu 已提交
1102
			this.historyMainService.removeFromRecentlyOpened([candidate]); // since file does not seem to exist anymore, remove from recent
1103

S
Sandeep Somavarapu 已提交
1104
			const fileUri = URI.file(candidate);
1105
			if (options && options.ignoreFileNotFound) {
M
Martin Aeschlimann 已提交
1106
				return { fileUri, createFilePath: true, remoteAuthority }; // assume this is a file that does not yet exist
E
Erich Gamma 已提交
1107 1108 1109
			}
		}

1110
		return undefined;
E
Erich Gamma 已提交
1111 1112
	}

B
Benjamin Pasero 已提交
1113 1114 1115
	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
1116
		const windowConfig = this.configurationService.getValue<IWindowSettings>('window');
1117 1118 1119
		const openFolderInNewWindowConfig = (windowConfig && windowConfig.openFoldersInNewWindow) || 'default' /* default */;
		const openFilesInNewWindowConfig = (windowConfig && windowConfig.openFilesInNewWindow) || 'off' /* default */;

B
Benjamin Pasero 已提交
1120
		let openFolderInNewWindow = (openConfig.preferNewWindow || openConfig.forceNewWindow) && !openConfig.forceReuseWindow;
1121 1122
		if (!openConfig.forceNewWindow && !openConfig.forceReuseWindow && (openFolderInNewWindowConfig === 'on' || openFolderInNewWindowConfig === 'off')) {
			openFolderInNewWindow = (openFolderInNewWindowConfig === 'on');
B
Benjamin Pasero 已提交
1123 1124 1125
		}

		// 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 已提交
1126
		let openFilesInNewWindow: boolean = false;
B
Benjamin Pasero 已提交
1127
		if (openConfig.forceNewWindow || openConfig.forceReuseWindow) {
M
Matt Bierner 已提交
1128
			openFilesInNewWindow = !!openConfig.forceNewWindow && !openConfig.forceReuseWindow;
B
Benjamin Pasero 已提交
1129
		} else {
1130 1131 1132 1133 1134 1135 1136 1137 1138 1139 1140 1141 1142

			// 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 已提交
1143 1144
			}

1145
			// finally check for overrides of default
1146 1147
			if (!openConfig.cli.extensionDevelopmentPath && (openFilesInNewWindowConfig === 'on' || openFilesInNewWindowConfig === 'off')) {
				openFilesInNewWindow = (openFilesInNewWindowConfig === 'on');
B
Benjamin Pasero 已提交
1148 1149 1150
			}
		}

M
Matt Bierner 已提交
1151
		return { openFolderInNewWindow: !!openFolderInNewWindow, openFilesInNewWindow };
B
Benjamin Pasero 已提交
1152 1153
	}

1154
	openExtensionDevelopmentHostWindow(extensionDevelopmentPath: string, openConfig: IOpenConfiguration): void {
E
Erich Gamma 已提交
1155

B
Benjamin Pasero 已提交
1156 1157 1158
		// 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.
1159
		const existingWindow = findWindowOnExtensionDevelopmentPath(WindowsManager.WINDOWS, extensionDevelopmentPath);
1160 1161 1162
		if (existingWindow) {
			this.reload(existingWindow, openConfig.cli);
			existingWindow.focus(); // make sure it gets focus and is restored
E
Erich Gamma 已提交
1163

B
Benjamin Pasero 已提交
1164 1165
			return;
		}
1166 1167 1168
		let folderUris = asArray(openConfig.cli['folder-uri']);
		let fileUris = asArray(openConfig.cli['file-uri']);
		let cliArgs = openConfig.cli._;
E
Erich Gamma 已提交
1169

1170
		// Fill in previously opened workspace unless an explicit path is provided and we are not unit testing
1171
		if (!cliArgs.length && !folderUris.length && !fileUris.length && !openConfig.cli.extensionTestsPath) {
1172
			const extensionDevelopmentWindowState = this.windowsState.lastPluginDevelopmentHostWindow;
1173
			const workspaceToOpen = extensionDevelopmentWindowState && (extensionDevelopmentWindowState.workspace || extensionDevelopmentWindowState.folderUri);
1174
			if (workspaceToOpen) {
1175
				if (isSingleFolderWorkspaceIdentifier(workspaceToOpen)) {
1176
					if (workspaceToOpen.scheme === Schemas.file) {
1177
						cliArgs = [workspaceToOpen.fsPath];
1178
					} else {
1179
						folderUris = [workspaceToOpen.toString()];
1180 1181
					}
				} else {
M
Martin Aeschlimann 已提交
1182
					if (workspaceToOpen.configPath.scheme === Schemas.file) {
1183
						cliArgs = [originalFSPath(workspaceToOpen.configPath)];
M
Martin Aeschlimann 已提交
1184
					} else {
1185
						fileUris = [workspaceToOpen.configPath.toString()];
M
Martin Aeschlimann 已提交
1186
					}
1187
				}
E
Erich Gamma 已提交
1188 1189 1190
			}
		}

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

M
Martin Aeschlimann 已提交
1196
		if (folderUris.length && folderUris.some(uri => !!findWindowOnWorkspaceOrFolderUri(WindowsManager.WINDOWS, this.argToUri(uri)))) {
1197 1198 1199
			folderUris = [];
		}

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

1204 1205 1206 1207
		openConfig.cli._ = cliArgs;
		openConfig.cli['folder-uri'] = folderUris;
		openConfig.cli['file-uri'] = fileUris;

1208 1209
		const match = extensionDevelopmentPath.match(/^vscode-remote:\/\/([^\/]+)/);
		if (match) {
1210
			openConfig.cli['remote'] = URI.parse(extensionDevelopmentPath).authority;
1211 1212
		}

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

1217
	private openInBrowserWindow(options: IOpenBrowserWindowOptions): ICodeWindow {
1218

B
Benjamin Pasero 已提交
1219 1220 1221
		// Build IWindowConfiguration from config and options
		const configuration: IWindowConfiguration = mixin({}, options.cli); // inherit all properties from CLI
		configuration.appRoot = this.environmentService.appRoot;
1222
		configuration.machineId = this.machineId;
1223
		configuration.nodeCachedDataDir = this.environmentService.nodeCachedDataDir;
1224
		configuration.mainPid = process.pid;
B
Benjamin Pasero 已提交
1225 1226 1227
		configuration.execPath = process.execPath;
		configuration.userEnv = assign({}, this.initialUserEnv, options.userEnv || {});
		configuration.isInitialStartup = options.initialStartup;
1228
		configuration.workspace = options.workspace;
1229
		configuration.folderUri = options.folderUri;
M
Martin Aeschlimann 已提交
1230
		configuration.remoteAuthority = options.remoteAuthority;
1231 1232 1233 1234 1235 1236 1237 1238

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

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

1248
		let window: ICodeWindow | undefined;
1249
		if (!options.forceNewWindow && !options.forceNewTabbedWindow) {
1250 1251 1252
			window = options.windowToUse || this.getLastActiveWindow();
			if (window) {
				window.focus();
E
Erich Gamma 已提交
1253 1254 1255 1256
			}
		}

		// New window
1257
		if (!window) {
1258
			const windowConfig = this.configurationService.getValue<IWindowSettings>('window');
1259 1260 1261 1262 1263 1264 1265 1266 1267 1268
			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 {
1269
				allowFullscreen = this.lifecycleService.wasRestarted || (windowConfig && windowConfig.restoreFullscreen);
1270 1271 1272 1273 1274
			}

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

1276
			// Create the window
1277
			window = this.instantiationService.createInstance(CodeWindow, {
1278
				state,
1279
				extensionDevelopmentPath: configuration.extensionDevelopmentPath,
1280
				isExtensionTestHost: !!configuration.extensionTestsPath
1281
			});
1282

1283 1284 1285 1286 1287 1288 1289 1290
			// Add as window tab if configured (macOS only)
			if (options.forceNewTabbedWindow) {
				const activeWindow = this.getLastActiveWindow();
				if (activeWindow) {
					activeWindow.addTabbedWindow(window);
				}
			}

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

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

E
Erich Gamma 已提交
1297
			// Window Events
1298
			window.win.webContents.removeAllListeners('devtools-reload-page'); // remove built in listener so we can handle this on our own
M
Matt Bierner 已提交
1299 1300 1301 1302
			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 已提交
1303 1304

			// Lifecycle
B
Benjamin Pasero 已提交
1305
			(this.lifecycleService as LifecycleService).registerWindow(window);
E
Erich Gamma 已提交
1306 1307 1308 1309 1310 1311
		}

		// Existing window
		else {

			// Some configuration things get inherited if the window is being reused and we are
B
Benjamin Pasero 已提交
1312
			// in extension development host mode. These options are all development related.
1313
			const currentWindowConfig = window.config;
A
Alex Dima 已提交
1314 1315
			if (!configuration.extensionDevelopmentPath && currentWindowConfig && !!currentWindowConfig.extensionDevelopmentPath) {
				configuration.extensionDevelopmentPath = currentWindowConfig.extensionDevelopmentPath;
B
Benjamin Pasero 已提交
1316
				configuration.verbose = currentWindowConfig.verbose;
1317
				configuration['inspect-brk-extensions'] = currentWindowConfig['inspect-brk-extensions'];
1318
				configuration.debugId = currentWindowConfig.debugId;
1319
				configuration['inspect-extensions'] = currentWindowConfig['inspect-extensions'];
1320
				configuration['extensions-dir'] = currentWindowConfig['extensions-dir'];
E
Erich Gamma 已提交
1321 1322 1323
			}
		}

1324 1325 1326 1327 1328 1329
		// 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 已提交
1330
					this.doOpenInBrowserWindow(window!, configuration, options);
B
Benjamin Pasero 已提交
1331
				}
1332 1333 1334 1335 1336 1337 1338
			});
		} else {
			this.doOpenInBrowserWindow(window, configuration, options);
		}

		return window;
	}
B
Benjamin Pasero 已提交
1339

1340
	private doOpenInBrowserWindow(window: ICodeWindow, configuration: IWindowConfiguration, options: IOpenBrowserWindowOptions): void {
1341

1342 1343 1344
		// Register window for backups
		if (!configuration.extensionDevelopmentPath) {
			if (configuration.workspace) {
1345
				configuration.backupPath = this.backupMainService.registerWorkspaceBackupSync({ workspace: configuration.workspace, remoteAuthority: configuration.remoteAuthority });
1346 1347 1348 1349 1350
			} 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 已提交
1351
			}
1352
		}
1353

1354 1355 1356 1357 1358
		// Load it
		window.load(configuration);

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

1361
	private getNewWindowState(configuration: IWindowConfiguration): INewWindowState {
1362
		const lastActive = this.getLastActiveWindow();
E
Erich Gamma 已提交
1363

1364 1365
		// Restore state unless we are running extension tests
		if (!configuration.extensionTestsPath) {
E
Erich Gamma 已提交
1366

1367 1368 1369
			// extension development host Window - load from stored settings if any
			if (!!configuration.extensionDevelopmentPath && this.windowsState.lastPluginDevelopmentHostWindow) {
				return this.windowsState.lastPluginDevelopmentHostWindow.uiState;
1370 1371
			}

1372
			// Known Workspace - load from stored settings
M
Matt Bierner 已提交
1373 1374 1375
			const workspace = configuration.workspace;
			if (workspace) {
				const stateForWorkspace = this.windowsState.openedWindows.filter(o => o.workspace && o.workspace.id === workspace.id).map(o => o.uiState);
1376 1377 1378 1379 1380 1381
				if (stateForWorkspace.length) {
					return stateForWorkspace[0];
				}
			}

			// Known Folder - load from stored settings
1382
			if (configuration.folderUri) {
1383
				const stateForFolder = this.windowsState.openedWindows.filter(o => o.folderUri && isEqual(o.folderUri, configuration.folderUri)).map(o => o.uiState);
1384 1385 1386
				if (stateForFolder.length) {
					return stateForFolder[0];
				}
1387 1388
			}

1389 1390 1391 1392 1393 1394
			// 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 已提交
1395 1396
			}

1397 1398 1399 1400 1401
			// First Window
			const lastActiveState = this.lastClosedWindowState || this.windowsState.lastActiveWindow;
			if (!lastActive && lastActiveState) {
				return lastActiveState.uiState;
			}
E
Erich Gamma 已提交
1402 1403 1404 1405 1406 1407 1408
		}

		//
		// 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
1409
		let displayToUse: Electron.Display | undefined;
B
Benjamin Pasero 已提交
1410
		const displays = screen.getAllDisplays();
E
Erich Gamma 已提交
1411 1412 1413 1414 1415 1416 1417 1418 1419 1420

		// 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 已提交
1421
			if (isMacintosh) {
B
Benjamin Pasero 已提交
1422
				const cursorPoint = screen.getCursorScreenPoint();
E
Erich Gamma 已提交
1423 1424 1425 1426 1427 1428 1429 1430
				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());
			}

1431
			// fallback to primary display or first display
E
Erich Gamma 已提交
1432
			if (!displayToUse) {
1433
				displayToUse = screen.getPrimaryDisplay() || displays[0];
E
Erich Gamma 已提交
1434 1435 1436
			}
		}

1437 1438 1439
		// 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.
1440
		let state = defaultWindowState() as INewWindowState;
M
Matt Bierner 已提交
1441 1442
		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 已提交
1443

1444
		// Check for newWindowDimensions setting and adjust accordingly
1445
		const windowConfig = this.configurationService.getValue<IWindowSettings>('window');
1446 1447 1448 1449 1450 1451 1452 1453 1454
		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 已提交
1455 1456 1457 1458 1459 1460 1461
				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;
				}

1462 1463 1464 1465 1466 1467 1468 1469
				ensureNoOverlap = false;
			}
		}

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

1470 1471
		state.hasDefaultState = true; // flag as default state

1472
		return state;
E
Erich Gamma 已提交
1473 1474
	}

J
Joao Moreno 已提交
1475
	private ensureNoOverlap(state: ISingleWindowState): ISingleWindowState {
E
Erich Gamma 已提交
1476 1477 1478 1479
		if (WindowsManager.WINDOWS.length === 0) {
			return state;
		}

M
Matt Bierner 已提交
1480 1481 1482
		state.x = typeof state.x === 'number' ? state.x : 0;
		state.y = typeof state.y === 'number' ? state.y : 0;

1483 1484
		const existingWindowBounds = WindowsManager.WINDOWS.map(win => win.getBounds());
		while (existingWindowBounds.some(b => b.x === state.x || b.y === state.y)) {
E
Erich Gamma 已提交
1485 1486 1487 1488 1489 1490 1491
			state.x += 30;
			state.y += 30;
		}

		return state;
	}

B
Benjamin Pasero 已提交
1492
	reload(win: ICodeWindow, cli?: ParsedArgs): void {
B
Benjamin Pasero 已提交
1493 1494

		// Only reload when the window has not vetoed this
1495
		this.lifecycleService.unload(win, UnloadReason.RELOAD).then(veto => {
B
Benjamin Pasero 已提交
1496
			if (!veto) {
R
Rob Lourens 已提交
1497
				win.reload(undefined, cli);
B
Benjamin Pasero 已提交
1498 1499 1500 1501
			}
		});
	}

B
Benjamin Pasero 已提交
1502
	closeWorkspace(win: ICodeWindow): void {
1503 1504
		this.openInBrowserWindow({
			cli: this.environmentService.args,
M
Martin Aeschlimann 已提交
1505 1506
			windowToUse: win,
			remoteAuthority: win.remoteAuthority
1507 1508 1509
		});
	}

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

1514
	private doEnterWorkspace(win: ICodeWindow, result: IEnterWorkspaceResult): IEnterWorkspaceResult {
1515

1516
		// Mark as recently opened
B
Benjamin Pasero 已提交
1517
		this.historyMainService.addRecentlyOpened([result.workspace], []);
1518

1519 1520 1521
		// Trigger Eevent to indicate load of workspace into window
		this._onWindowReady.fire(win);

1522
		return result;
1523 1524
	}

B
Benjamin Pasero 已提交
1525
	pickWorkspaceAndOpen(options: INativeOpenDialogOptions): void {
1526
		this.workspacesManager.pickWorkspaceAndOpen(options);
1527 1528 1529
	}

	private onBeforeWindowUnload(e: IWindowUnloadEvent): void {
1530 1531
		const windowClosing = (e.reason === UnloadReason.CLOSE);
		const windowLoading = (e.reason === UnloadReason.LOAD);
1532 1533 1534 1535 1536
		if (!windowClosing && !windowLoading) {
			return; // only interested when window is closing or loading
		}

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

1541
		if (e.window.config && !!e.window.config.extensionDevelopmentPath) {
1542 1543 1544 1545
			// do not ask to save workspace when doing extension development
			// but still delete it.
			this.workspacesMainService.deleteUntitledWorkspaceSync(workspace);
			return;
1546
		}
1547 1548
	}

B
Benjamin Pasero 已提交
1549
	focusLastActive(cli: ParsedArgs, context: OpenContext): ICodeWindow {
B
Benjamin Pasero 已提交
1550
		const lastActive = this.getLastActiveWindow();
E
Erich Gamma 已提交
1551
		if (lastActive) {
B
Benjamin Pasero 已提交
1552
			lastActive.focus();
1553 1554

			return lastActive;
E
Erich Gamma 已提交
1555 1556
		}

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

M
Matt Bierner 已提交
1561
	getLastActiveWindow(): ICodeWindow | undefined {
1562
		return getLastActiveWindow(WindowsManager.WINDOWS);
E
Erich Gamma 已提交
1563 1564
	}

1565
	getLastActiveWindowForAuthority(remoteAuthority: string | undefined): ICodeWindow | undefined {
M
Martin Aeschlimann 已提交
1566 1567 1568
		return getLastActiveWindow(WindowsManager.WINDOWS.filter(w => w.remoteAuthority === remoteAuthority));
	}

1569 1570
	openNewWindow(context: OpenContext, options?: INewWindowOptions): ICodeWindow[] {
		let cli = this.environmentService.args;
R
Rob Lourens 已提交
1571
		let remote = options && options.remoteAuthority || undefined;
M
Martin Aeschlimann 已提交
1572 1573 1574
		if (cli && (cli.remote !== remote)) {
			cli = { ...cli, remote };
		}
1575
		return this.open({ context, cli, forceNewWindow: true, forceEmpty: true });
E
Erich Gamma 已提交
1576 1577
	}

1578 1579 1580 1581
	openNewTabbedWindow(context: OpenContext): ICodeWindow[] {
		return this.open({ context, cli: this.environmentService.args, forceNewTabbedWindow: true, forceEmpty: true });
	}

J
Johannes Rieken 已提交
1582
	waitForWindowCloseOrLoad(windowId: number): Promise<void> {
B
Benjamin Pasero 已提交
1583
		return new Promise<void>(resolve => {
1584
			function handler(id: number) {
1585
				if (id === windowId) {
1586 1587 1588
					closeListener.dispose();
					loadListener.dispose();

1589
					resolve();
1590
				}
1591 1592 1593 1594
			}

			const closeListener = this.onWindowClose(id => handler(id));
			const loadListener = this.onWindowLoad(id => handler(id));
1595 1596 1597
		});
	}

B
Benjamin Pasero 已提交
1598
	sendToFocused(channel: string, ...args: any[]): void {
E
Erich Gamma 已提交
1599 1600 1601
		const focusedWindow = this.getFocusedWindow() || this.getLastActiveWindow();

		if (focusedWindow) {
1602
			focusedWindow.sendWhenReady(channel, ...args);
E
Erich Gamma 已提交
1603 1604 1605
		}
	}

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

1612
			w.sendWhenReady(channel, payload);
E
Erich Gamma 已提交
1613 1614 1615
		});
	}

M
Matt Bierner 已提交
1616
	getFocusedWindow(): ICodeWindow | undefined {
B
Benjamin Pasero 已提交
1617
		const win = BrowserWindow.getFocusedWindow();
E
Erich Gamma 已提交
1618 1619 1620 1621
		if (win) {
			return this.getWindowById(win.id);
		}

M
Matt Bierner 已提交
1622
		return undefined;
E
Erich Gamma 已提交
1623 1624
	}

M
Matt Bierner 已提交
1625
	getWindowById(windowId: number): ICodeWindow | undefined {
1626
		const res = WindowsManager.WINDOWS.filter(w => w.id === windowId);
E
Erich Gamma 已提交
1627 1628 1629 1630
		if (res && res.length === 1) {
			return res[0];
		}

M
Matt Bierner 已提交
1631
		return undefined;
E
Erich Gamma 已提交
1632 1633
	}

B
Benjamin Pasero 已提交
1634
	getWindows(): ICodeWindow[] {
E
Erich Gamma 已提交
1635 1636 1637
		return WindowsManager.WINDOWS;
	}

B
Benjamin Pasero 已提交
1638
	getWindowCount(): number {
E
Erich Gamma 已提交
1639 1640 1641
		return WindowsManager.WINDOWS.length;
	}

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

1645 1646
		/* __GDPR__
			"windowerror" : {
K
kieferrm 已提交
1647
				"type" : { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth", "isMeasurement": true }
1648 1649 1650 1651
			}
		*/
		this.telemetryService.publicLog('windowerror', { type: error });

E
Erich Gamma 已提交
1652 1653
		// Unresponsive
		if (error === WindowError.UNRESPONSIVE) {
1654 1655 1656 1657 1658 1659 1660 1661 1662 1663 1664
			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
1665
			this.dialogs.showMessageBox({
B
Benjamin Pasero 已提交
1666
				title: product.nameLong,
E
Erich Gamma 已提交
1667
				type: 'warning',
1668
				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"))],
1669 1670
				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 已提交
1671
				noLink: true
1672 1673 1674 1675
			}, window).then(result => {
				if (!window.win) {
					return; // Return early if the window has been going down already
				}
1676

1677 1678 1679 1680 1681 1682 1683
				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 已提交
1684 1685 1686 1687
		}

		// Crashed
		else {
1688
			this.dialogs.showMessageBox({
B
Benjamin Pasero 已提交
1689
				title: product.nameLong,
E
Erich Gamma 已提交
1690
				type: 'warning',
1691
				buttons: [mnemonicButtonLabel(localize({ key: 'reopen', comment: ['&& denotes a mnemonic'] }, "&&Reopen")), mnemonicButtonLabel(localize({ key: 'close', comment: ['&& denotes a mnemonic'] }, "&&Close"))],
1692 1693
				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 已提交
1694
				noLink: true
1695 1696 1697 1698
			}, window).then(result => {
				if (!window.win) {
					return; // Return early if the window has been going down already
				}
1699

1700 1701 1702 1703 1704 1705 1706
				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 已提交
1707 1708 1709
		}
	}

1710
	private onWindowClosed(win: ICodeWindow): void {
E
Erich Gamma 已提交
1711 1712 1713 1714 1715

		// Tell window
		win.dispose();

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

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

B
Benjamin Pasero 已提交
1724
	pickFileFolderAndOpen(options: INativeOpenDialogOptions): void {
B
Benjamin Pasero 已提交
1725
		this.doPickAndOpen(options, true /* pick folders */, true /* pick files */);
1726 1727
	}

B
Benjamin Pasero 已提交
1728
	pickFolderAndOpen(options: INativeOpenDialogOptions): void {
B
Benjamin Pasero 已提交
1729
		this.doPickAndOpen(options, true /* pick folders */, false /* pick files */);
1730 1731
	}

B
Benjamin Pasero 已提交
1732
	pickFileAndOpen(options: INativeOpenDialogOptions): void {
B
Benjamin Pasero 已提交
1733 1734 1735 1736 1737 1738 1739 1740 1741
		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;

1742 1743
		const dialogOptions: OpenDialogOptions = internalOptions.dialogOptions || Object.create(null);
		internalOptions.dialogOptions = dialogOptions;
B
Benjamin Pasero 已提交
1744 1745 1746 1747 1748 1749 1750 1751 1752 1753 1754 1755 1756

		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 已提交
1757
				// __GDPR__TODO__ classify event
B
Benjamin Pasero 已提交
1758 1759 1760 1761 1762 1763 1764 1765
				internalOptions.telemetryEventName = 'openFileFolder';
			} else if (pickFolders) {
				internalOptions.telemetryEventName = 'openFolder';
			} else {
				internalOptions.telemetryEventName = 'openFile';
			}
		}

1766 1767 1768
		this.dialogs.pickAndOpen(internalOptions);
	}

J
Johannes Rieken 已提交
1769
	showMessageBox(options: Electron.MessageBoxOptions, win?: ICodeWindow): Promise<IMessageBoxResult> {
1770 1771 1772
		return this.dialogs.showMessageBox(options, win);
	}

J
Johannes Rieken 已提交
1773
	showSaveDialog(options: Electron.SaveDialogOptions, win?: ICodeWindow): Promise<string> {
1774 1775 1776
		return this.dialogs.showSaveDialog(options, win);
	}

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

B
Benjamin Pasero 已提交
1781
	quit(): void {
B
Benjamin Pasero 已提交
1782 1783 1784

		// 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.
1785 1786 1787
		const window = this.getFocusedWindow();
		if (window && window.isExtensionDevelopmentHost && this.getWindowCount() > 1) {
			window.win.close();
B
Benjamin Pasero 已提交
1788 1789 1790 1791 1792 1793 1794 1795
		}

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

B
Benjamin Pasero 已提交
1799
interface IInternalNativeOpenDialogOptions extends INativeOpenDialogOptions {
B
Benjamin Pasero 已提交
1800 1801 1802 1803
	pickFolders?: boolean;
	pickFiles?: boolean;
}

1804
class Dialogs {
B
Benjamin Pasero 已提交
1805

1806
	private static readonly workingDirPickerStorageKey = 'pickerWorkingDir';
1807

1808 1809 1810
	private mapWindowToDialogQueue: Map<number, Queue<any>>;
	private noWindowDialogQueue: Queue<any>;

B
Benjamin Pasero 已提交
1811 1812 1813
	constructor(
		private environmentService: IEnvironmentService,
		private telemetryService: ITelemetryService,
B
Benjamin Pasero 已提交
1814
		private stateService: IStateService,
B
Benjamin Pasero 已提交
1815
		private windowsMainService: IWindowsMainService,
B
Benjamin Pasero 已提交
1816
	) {
1817 1818
		this.mapWindowToDialogQueue = new Map<number, Queue<any>>();
		this.noWindowDialogQueue = new Queue<any>();
B
Benjamin Pasero 已提交
1819 1820
	}

B
Benjamin Pasero 已提交
1821
	pickAndOpen(options: INativeOpenDialogOptions): void {
1822
		this.getFileOrFolderUris(options).then(paths => {
B
Benjamin Pasero 已提交
1823 1824 1825 1826
			const numberOfPaths = paths ? paths.length : 0;

			// Telemetry
			if (options.telemetryEventName) {
K
kieferrm 已提交
1827
				// __GDPR__TODO__ Dynamic event names and dynamic properties. Can not be registered statically.
B
Benjamin Pasero 已提交
1828 1829 1830 1831 1832 1833 1834 1835 1836
				this.telemetryService.publicLog(options.telemetryEventName, {
					...options.telemetryExtraData,
					outcome: numberOfPaths ? 'success' : 'canceled',
					numberOfPaths
				});
			}

			// Open
			if (numberOfPaths) {
1837 1838
				this.windowsMainService.open({
					context: OpenContext.DIALOG,
B
Benjamin Pasero 已提交
1839
					contextWindowId: options.windowId,
1840
					cli: this.environmentService.args,
S
Sandeep Somavarapu 已提交
1841
					urisToOpen: paths,
1842 1843 1844
					forceNewWindow: options.forceNewWindow,
					forceOpenWorkspaceAsFile: options.dialogOptions && !equals(options.dialogOptions.filters, WORKSPACE_FILTER)
				});
1845 1846 1847 1848
			}
		});
	}

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

B
Benjamin Pasero 已提交
1851
		// Ensure dialog options
M
Matt Bierner 已提交
1852 1853
		const dialogOptions = options.dialogOptions || Object.create(null);
		options.dialogOptions = dialogOptions;
B
Benjamin Pasero 已提交
1854 1855

		// Ensure defaultPath
M
Matt Bierner 已提交
1856 1857
		if (!dialogOptions.defaultPath) {
			dialogOptions.defaultPath = this.stateService.getItem<string>(Dialogs.workingDirPickerStorageKey);
1858 1859
		}

B
Benjamin Pasero 已提交
1860 1861
		// Ensure properties
		if (typeof options.pickFiles === 'boolean' || typeof options.pickFolders === 'boolean') {
M
Matt Bierner 已提交
1862
			dialogOptions.properties = undefined; // let it override based on the booleans
B
Benjamin Pasero 已提交
1863 1864

			if (options.pickFiles && options.pickFolders) {
M
Matt Bierner 已提交
1865
				dialogOptions.properties = ['multiSelections', 'openDirectory', 'openFile', 'createDirectory'];
B
Benjamin Pasero 已提交
1866 1867 1868
			}
		}

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

1873
		if (isMacintosh) {
M
Matt Bierner 已提交
1874
			dialogOptions.properties.push('treatPackageAsDirectory'); // always drill into .app files
1875 1876
		}

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

M
Matt Bierner 已提交
1880
		return this.showOpenDialog(dialogOptions, focusedWindow).then(paths => {
1881 1882 1883 1884 1885
			if (paths && paths.length > 0) {

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

1886 1887 1888 1889 1890 1891
				const result: IURIToOpen[] = [];
				for (const path of paths) {
					result.push({ uri: URI.file(path) });
				}

				return result;
1892 1893
			}

R
Rob Lourens 已提交
1894
			return undefined;
1895 1896 1897 1898 1899 1900 1901 1902 1903 1904 1905 1906 1907 1908 1909 1910 1911
		});
	}

	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 已提交
1912
	showMessageBox(options: Electron.MessageBoxOptions, window?: ICodeWindow): Promise<IMessageBoxResult> {
1913
		return this.getDialogQueue(window).queue(() => {
B
Benjamin Pasero 已提交
1914
			return new Promise(resolve => {
M
Matt Bierner 已提交
1915
				dialog.showMessageBox(window ? window.win : undefined!, options, (response: number, checkboxChecked: boolean) => {
B
Benjamin Pasero 已提交
1916
					resolve({ button: response, checkboxChecked });
B
Benjamin Pasero 已提交
1917
				});
1918 1919 1920 1921
			});
		});
	}

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

1924 1925 1926
		function normalizePath(path: string): string {
			if (path && isMacintosh) {
				path = normalizeNFC(path); // normalize paths returned from the OS
1927
			}
1928

1929 1930
			return path;
		}
1931

1932
		return this.getDialogQueue(window).queue(() => {
B
Benjamin Pasero 已提交
1933
			return new Promise(resolve => {
M
Matt Bierner 已提交
1934
				dialog.showSaveDialog(window ? window.win : undefined!, options, path => {
B
Benjamin Pasero 已提交
1935
					resolve(normalizePath(path));
B
Benjamin Pasero 已提交
1936
				});
1937 1938 1939 1940
			});
		});
	}

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

1943 1944 1945 1946 1947 1948
		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;
1949
		}
B
Benjamin Pasero 已提交
1950

1951
		return this.getDialogQueue(window).queue(() => {
B
Benjamin Pasero 已提交
1952
			return new Promise(resolve => {
B
Benjamin Pasero 已提交
1953 1954

				// Ensure the path exists (if provided)
B
Benjamin Pasero 已提交
1955
				let validatePathPromise: Promise<void> = Promise.resolve();
B
Benjamin Pasero 已提交
1956 1957 1958
				if (options.defaultPath) {
					validatePathPromise = exists(options.defaultPath).then(exists => {
						if (!exists) {
R
Rob Lourens 已提交
1959
							options.defaultPath = undefined;
B
Benjamin Pasero 已提交
1960 1961 1962 1963 1964 1965
						}
					});
				}

				// Show dialog and wrap as promise
				validatePathPromise.then(() => {
M
Matt Bierner 已提交
1966
					dialog.showOpenDialog(window ? window.win : undefined!, options, paths => {
B
Benjamin Pasero 已提交
1967
						resolve(normalizePaths(paths));
B
Benjamin Pasero 已提交
1968
					});
B
Benjamin Pasero 已提交
1969
				});
1970 1971
			});
		});
1972
	}
1973 1974 1975 1976 1977
}

class WorkspacesManager {

	constructor(
1978 1979 1980 1981
		private readonly workspacesMainService: IWorkspacesMainService,
		private readonly backupMainService: IBackupMainService,
		private readonly windowsMainService: IWindowsMainService,
	) { }
1982

M
Martin Aeschlimann 已提交
1983
	enterWorkspace(window: ICodeWindow, path: URI): Promise<IEnterWorkspaceResult | null> {
B
Benjamin Pasero 已提交
1984
		if (!window || !window.win || !window.isReady) {
B
Benjamin Pasero 已提交
1985
			return Promise.resolve(null); // return early if the window is not ready or disposed
1986 1987 1988 1989
		}

		return this.isValidTargetWorkspacePath(window, path).then(isValid => {
			if (!isValid) {
B
Benjamin Pasero 已提交
1990
				return null; // return early if the workspace is not valid
1991
			}
M
Martin Aeschlimann 已提交
1992 1993
			const workspaceIdentifier = this.workspacesMainService.getWorkspaceIdentifier(path);
			return this.doOpenWorkspace(window, workspaceIdentifier);
1994 1995 1996 1997
		});

	}

M
Martin Aeschlimann 已提交
1998
	private isValidTargetWorkspacePath(window: ICodeWindow, path?: URI): Promise<boolean> {
1999
		if (!path) {
B
Benjamin Pasero 已提交
2000
			return Promise.resolve(true);
2001 2002
		}

M
Martin Aeschlimann 已提交
2003
		if (window.openedWorkspace && isEqual(window.openedWorkspace.configPath, path)) {
B
Benjamin Pasero 已提交
2004
			return Promise.resolve(false); // window is already opened on a workspace with that path
2005 2006 2007
		}

		// Prevent overwriting a workspace that is currently opened in another window
M
Martin Aeschlimann 已提交
2008
		if (findWindowOnWorkspace(this.windowsMainService.getWindows(), this.workspacesMainService.getWorkspaceIdentifier(path))) {
2009 2010 2011 2012
			const options: Electron.MessageBoxOptions = {
				title: product.nameLong,
				type: 'info',
				buttons: [localize('ok', "OK")],
M
Martin Aeschlimann 已提交
2013
				message: localize('workspaceOpenedMessage', "Unable to save workspace '{0}'", resourcesBasename(path)),
2014
				detail: localize('workspaceOpenedDetail', "The workspace is already opened in another window. Please close that window first and then try again."),
2015 2016 2017
				noLink: true
			};

2018
			return this.windowsMainService.showMessageBox(options, this.windowsMainService.getFocusedWindow()).then(() => false);
2019 2020
		}

B
Benjamin Pasero 已提交
2021
		return Promise.resolve(true); // OK
2022 2023
	}

2024 2025
	private doOpenWorkspace(window: ICodeWindow, workspace: IWorkspaceIdentifier): IEnterWorkspaceResult {
		window.focus();
2026

2027
		// Register window for backups and migrate current backups over
2028
		let backupPath: string | undefined;
2029
		if (!window.config.extensionDevelopmentPath) {
2030
			backupPath = this.backupMainService.registerWorkspaceBackupSync({ workspace, remoteAuthority: window.remoteAuthority }, window.config.backupPath);
2031
		}
2032

2033 2034 2035 2036 2037
		// if the window was opened on an untitled workspace, delete it.
		if (window.openedWorkspace && this.workspacesMainService.isUntitledWorkspace(window.openedWorkspace)) {
			this.workspacesMainService.deleteUntitledWorkspaceSync(window.openedWorkspace);
		}

2038
		// Update window configuration properly based on transition to workspace
R
Rob Lourens 已提交
2039
		window.config.folderUri = undefined;
2040 2041 2042 2043
		window.config.workspace = workspace;
		window.config.backupPath = backupPath;

		return { workspace, backupPath };
2044 2045
	}

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

		this.windowsMainService.pickFileAndOpen({
R
Rob Lourens 已提交
2050
			windowId: window ? window.id : undefined,
2051 2052 2053 2054 2055
			dialogOptions: {
				buttonLabel: mnemonicButtonLabel(localize({ key: 'openWorkspace', comment: ['&& denotes a mnemonic'] }, "&&Open")),
				title: localize('openWorkspaceTitle', "Open Workspace"),
				filters: WORKSPACE_FILTER,
				properties: ['openFile'],
2056
				defaultPath: options.dialogOptions && options.dialogOptions.defaultPath
2057
			},
2058 2059 2060
			forceNewWindow: options.forceNewWindow,
			telemetryEventName: options.telemetryEventName,
			telemetryExtraData: options.telemetryExtraData
2061 2062
		});
	}
J
Johannes Rieken 已提交
2063
}