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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

	private registerListeners(): void {
179

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

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

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

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

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

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

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

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

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

280
		// 3.) All windows (except extension host) for N >= 2 to support restoreWindows: all or for auto update
281 282 283 284 285
		//
		// Carefull here: asking a window for its window state after it has been closed returns bogus values (width: 0, height: 0)
		// so if we ever want to persist the UI state of the last closed window (window count === 1), it has
		// to come from the stored lastClosedWindowState on Win/Linux at least
		if (this.getWindowCount() > 1) {
286
			currentWindowsState.openedWindows = WindowsManager.WINDOWS.filter(w => !w.isExtensionDevelopmentHost).map(w => this.toWindowState(w));
287
		}
E
Erich Gamma 已提交
288

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

293
	// See note on #onBeforeShutdown() for details how these events are flowing
294
	private onBeforeWindowClose(win: ICodeWindow): void {
B
Benjamin Pasero 已提交
295
		if (this.lifecycleService.quitRequested) {
296 297 298 299
			return; // during quit, many windows close in parallel so let it be handled in the before-quit handler
		}

		// On Window close, update our stored UI state of this window
300
		const state: IWindowState = this.toWindowState(win);
301 302 303 304
		if (win.isExtensionDevelopmentHost && !win.isExtensionTestHost) {
			this.windowsState.lastPluginDevelopmentHostWindow = state; // do not let test run window state overwrite our extension development state
		}

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

				if (sameWorkspace || sameFolder) {
312 313 314 315 316 317 318
					o.uiState = state.uiState;
				}
			});
		}

		// On Windows and Linux closing the last window will trigger quit. Since we are storing all UI state
		// before quitting, we need to remember the UI state of this window to be able to persist it.
319 320 321
		// On macOS we keep the last closed window state ready in case the user wants to quit right after or
		// wants to open another window, in which case we use this state over the persisted one.
		if (this.getWindowCount() === 1) {
322 323
			this.lastClosedWindowState = state;
		}
E
Erich Gamma 已提交
324 325
	}

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

482 483 484
		return usedWindows;
	}

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

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

		return config;
	}

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

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

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

B
Benjamin Pasero 已提交
520
		// Handle files to open/diff or to create when we dont open a folder and we do not restore any folder/untitled from hot-exit
521
		const potentialWindowsCount = foldersToOpen.length + foldersToRestore.length + workspacesToOpen.length + workspacesToRestore.length + emptyToRestore.length;
522
		if (potentialWindowsCount === 0 && fileInputs) {
E
Erich Gamma 已提交
523

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

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

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

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

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

				// Window is empty
				else {

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

		return window;
733 734
	}

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

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

		return window;
	}

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

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

		return browserWindow;
	}

B
Benjamin Pasero 已提交
764
	private getPathsToOpen(openConfig: IOpenConfiguration): IPathToOpen[] {
765 766
		debugger;

B
Benjamin Pasero 已提交
767
		let windowsToOpen: IPathToOpen[];
768
		let isCommandLineOrAPICall = false;
E
Erich Gamma 已提交
769

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

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

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

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

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

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

807
		return windowsToOpen;
E
Erich Gamma 已提交
808 809
	}

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

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

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

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

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

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

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

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

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

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

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

890
		switch (restoreWindows) {
B
Benjamin Pasero 已提交
891

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

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

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

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

				break;
B
Benjamin Pasero 已提交
932
		}
E
Erich Gamma 已提交
933

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

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

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

		return restoreWindows;
	}

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

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

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

979 980
		// normalize URI
		uri = normalizePath(uri);
981 982 983


		// remove trailing slash
984
		const uriPath = uri.path;
985

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

		// if there's no type hint
997
		if (!typeHint && (hasWorkspaceFileExtension(uri.path) || options.gotoLineMode)) {
998
			typeHint = 'file';
999
		}
1000 1001 1002

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

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

1033
		let lineNumber, columnNumber: number | undefined;
1034

1035
		if (options.gotoLineMode) {
1036 1037 1038
			const parsedPath = parseLineAndColumnAware(anyPath);
			lineNumber = parsedPath.line;
			columnNumber = parsedPath.column;
1039

E
Erich Gamma 已提交
1040 1041 1042
			anyPath = parsedPath.path;
		}

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

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

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

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

1069 1070 1071 1072 1073
				// 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 已提交
1074 1075
						folderUri: URI.file(candidate),
						remoteAuthority
1076 1077
					};
				}
E
Erich Gamma 已提交
1078 1079
			}
		} catch (error) {
S
Sandeep Somavarapu 已提交
1080
			this.historyMainService.removeFromRecentlyOpened([candidate]); // since file does not seem to exist anymore, remove from recent
1081

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

1088
		return undefined;
E
Erich Gamma 已提交
1089 1090
	}

B
Benjamin Pasero 已提交
1091 1092 1093
	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
1094
		const windowConfig = this.configurationService.getValue<IWindowSettings>('window');
1095 1096 1097
		const openFolderInNewWindowConfig = (windowConfig && windowConfig.openFoldersInNewWindow) || 'default' /* default */;
		const openFilesInNewWindowConfig = (windowConfig && windowConfig.openFilesInNewWindow) || 'off' /* default */;

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

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

			// 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 已提交
1121 1122
			}

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

M
Matt Bierner 已提交
1129
		return { openFolderInNewWindow: !!openFolderInNewWindow, openFilesInNewWindow };
B
Benjamin Pasero 已提交
1130 1131
	}

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

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

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

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

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

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

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

1182 1183 1184 1185
		openConfig.cli._ = cliArgs;
		openConfig.cli['folder-uri'] = folderUris;
		openConfig.cli['file-uri'] = fileUris;

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

1190
	private openInBrowserWindow(options: IOpenBrowserWindowOptions): ICodeWindow {
1191

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

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

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

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

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

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

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

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

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

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

E
Erich Gamma 已提交
1270
			// Window Events
1271
			window.win.webContents.removeAllListeners('devtools-reload-page'); // remove built in listener so we can handle this on our own
M
Matt Bierner 已提交
1272 1273 1274 1275
			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 已提交
1276 1277

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

		// Existing window
		else {

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

1297 1298 1299 1300 1301 1302
		// 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 已提交
1303
					this.doOpenInBrowserWindow(window!, configuration, options);
B
Benjamin Pasero 已提交
1304
				}
1305 1306 1307 1308 1309 1310 1311
			});
		} else {
			this.doOpenInBrowserWindow(window, configuration, options);
		}

		return window;
	}
B
Benjamin Pasero 已提交
1312

1313
	private doOpenInBrowserWindow(window: ICodeWindow, configuration: IWindowConfiguration, options: IOpenBrowserWindowOptions): void {
1314

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

1327 1328 1329 1330 1331
		// Load it
		window.load(configuration);

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

1334
	private getNewWindowState(configuration: IWindowConfiguration): INewWindowState {
1335
		const lastActive = this.getLastActiveWindow();
E
Erich Gamma 已提交
1336

1337 1338
		// Restore state unless we are running extension tests
		if (!configuration.extensionTestsPath) {
E
Erich Gamma 已提交
1339

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

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

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

1362 1363 1364 1365 1366 1367
			// 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 已提交
1368 1369
			}

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

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

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

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

1410 1411 1412
		// 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.
1413
		let state = defaultWindowState() as INewWindowState;
M
Matt Bierner 已提交
1414 1415
		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 已提交
1416

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

1435 1436 1437 1438 1439 1440 1441 1442
				ensureNoOverlap = false;
			}
		}

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

1443 1444
		state.hasDefaultState = true; // flag as default state

1445
		return state;
E
Erich Gamma 已提交
1446 1447
	}

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

M
Matt Bierner 已提交
1453 1454 1455
		state.x = typeof state.x === 'number' ? state.x : 0;
		state.y = typeof state.y === 'number' ? state.y : 0;

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

		return state;
	}

B
Benjamin Pasero 已提交
1465
	reload(win: ICodeWindow, cli?: ParsedArgs): void {
B
Benjamin Pasero 已提交
1466 1467

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

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

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

1487
	private doEnterWorkspace(win: ICodeWindow, result: IEnterWorkspaceResult): IEnterWorkspaceResult {
1488

1489
		// Mark as recently opened
B
Benjamin Pasero 已提交
1490
		this.historyMainService.addRecentlyOpened([result.workspace], []);
1491

1492 1493 1494
		// Trigger Eevent to indicate load of workspace into window
		this._onWindowReady.fire(win);

1495
		return result;
1496 1497
	}

B
Benjamin Pasero 已提交
1498
	pickWorkspaceAndOpen(options: INativeOpenDialogOptions): void {
1499
		this.workspacesManager.pickWorkspaceAndOpen(options);
1500 1501 1502
	}

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

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

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

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

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

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

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

			return lastActive;
E
Erich Gamma 已提交
1545 1546
		}

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

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

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

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

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

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

1579
					resolve();
1580
				}
1581 1582 1583 1584
			}

			const closeListener = this.onWindowClose(id => handler(id));
			const loadListener = this.onWindowLoad(id => handler(id));
1585 1586 1587
		});
	}

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

		if (focusedWindow) {
1592
			focusedWindow.sendWhenReady(channel, ...args);
E
Erich Gamma 已提交
1593 1594 1595
		}
	}

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

1602
			w.sendWhenReady(channel, payload);
E
Erich Gamma 已提交
1603 1604 1605
		});
	}

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

M
Matt Bierner 已提交
1612
		return undefined;
E
Erich Gamma 已提交
1613 1614
	}

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

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

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

B
Benjamin Pasero 已提交
1628
	getWindowCount(): number {
E
Erich Gamma 已提交
1629 1630 1631
		return WindowsManager.WINDOWS.length;
	}

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

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

E
Erich Gamma 已提交
1642 1643
		// Unresponsive
		if (error === WindowError.UNRESPONSIVE) {
1644 1645 1646 1647 1648 1649 1650 1651 1652 1653 1654
			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
1655
			this.dialogs.showMessageBox({
B
Benjamin Pasero 已提交
1656
				title: product.nameLong,
E
Erich Gamma 已提交
1657
				type: 'warning',
1658
				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"))],
1659 1660
				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 已提交
1661
				noLink: true
1662 1663 1664 1665
			}, window).then(result => {
				if (!window.win) {
					return; // Return early if the window has been going down already
				}
1666

1667 1668 1669 1670 1671 1672 1673
				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 已提交
1674 1675 1676 1677
		}

		// Crashed
		else {
1678
			this.dialogs.showMessageBox({
B
Benjamin Pasero 已提交
1679
				title: product.nameLong,
E
Erich Gamma 已提交
1680
				type: 'warning',
1681
				buttons: [mnemonicButtonLabel(localize({ key: 'reopen', comment: ['&& denotes a mnemonic'] }, "&&Reopen")), mnemonicButtonLabel(localize({ key: 'close', comment: ['&& denotes a mnemonic'] }, "&&Close"))],
1682 1683
				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 已提交
1684
				noLink: true
1685 1686 1687 1688
			}, window).then(result => {
				if (!window.win) {
					return; // Return early if the window has been going down already
				}
1689

1690 1691 1692 1693 1694 1695 1696
				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 已提交
1697 1698 1699
		}
	}

1700
	private onWindowClosed(win: ICodeWindow): void {
E
Erich Gamma 已提交
1701 1702 1703 1704 1705

		// Tell window
		win.dispose();

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

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

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

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

B
Benjamin Pasero 已提交
1722
	pickFileAndOpen(options: INativeOpenDialogOptions): void {
B
Benjamin Pasero 已提交
1723 1724 1725 1726 1727 1728 1729 1730 1731
		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;

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

		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 已提交
1747
				// __GDPR__TODO__ classify event
B
Benjamin Pasero 已提交
1748 1749 1750 1751 1752 1753 1754 1755
				internalOptions.telemetryEventName = 'openFileFolder';
			} else if (pickFolders) {
				internalOptions.telemetryEventName = 'openFolder';
			} else {
				internalOptions.telemetryEventName = 'openFile';
			}
		}

1756 1757 1758
		this.dialogs.pickAndOpen(internalOptions);
	}

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

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

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

B
Benjamin Pasero 已提交
1771
	quit(): void {
B
Benjamin Pasero 已提交
1772 1773 1774

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

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

B
Benjamin Pasero 已提交
1789
interface IInternalNativeOpenDialogOptions extends INativeOpenDialogOptions {
B
Benjamin Pasero 已提交
1790 1791 1792 1793
	pickFolders?: boolean;
	pickFiles?: boolean;
}

1794
class Dialogs {
B
Benjamin Pasero 已提交
1795

1796
	private static readonly workingDirPickerStorageKey = 'pickerWorkingDir';
1797

1798 1799 1800
	private mapWindowToDialogQueue: Map<number, Queue<any>>;
	private noWindowDialogQueue: Queue<any>;

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

				return result;
1882 1883
			}

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

	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 已提交
1902
	showMessageBox(options: Electron.MessageBoxOptions, window?: ICodeWindow): Promise<IMessageBoxResult> {
1903
		return this.getDialogQueue(window).queue(() => {
B
Benjamin Pasero 已提交
1904
			return new Promise(resolve => {
M
Matt Bierner 已提交
1905
				dialog.showMessageBox(window ? window.win : undefined!, options, (response: number, checkboxChecked: boolean) => {
B
Benjamin Pasero 已提交
1906
					resolve({ button: response, checkboxChecked });
B
Benjamin Pasero 已提交
1907
				});
1908 1909 1910 1911
			});
		});
	}

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

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

1919 1920
			return path;
		}
1921

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

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

1933 1934 1935 1936 1937 1938
		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;
1939
		}
B
Benjamin Pasero 已提交
1940

1941
		return this.getDialogQueue(window).queue(() => {
B
Benjamin Pasero 已提交
1942
			return new Promise(resolve => {
B
Benjamin Pasero 已提交
1943 1944

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

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

class WorkspacesManager {

	constructor(
1968 1969 1970 1971 1972 1973
		private readonly workspacesMainService: IWorkspacesMainService,
		private readonly backupMainService: IBackupMainService,
		private readonly environmentService: IEnvironmentService,
		private readonly historyMainService: IHistoryMainService,
		private readonly windowsMainService: IWindowsMainService,
	) { }
1974

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

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

	}

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

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

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

2010
			return this.windowsMainService.showMessageBox(options, this.windowsMainService.getFocusedWindow()).then(() => false);
2011 2012
		}

B
Benjamin Pasero 已提交
2013
		return Promise.resolve(true); // OK
2014 2015
	}

2016 2017
	private doOpenWorkspace(window: ICodeWindow, workspace: IWorkspaceIdentifier): IEnterWorkspaceResult {
		window.focus();
2018

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

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

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

		return { workspace, backupPath };
2036 2037
	}

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

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

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

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

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

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

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

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

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

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

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

2118 2119
						return true; // keep veto if no target was provided
					});
2120 2121
				}
			}
2122
		});
2123 2124
	}

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

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

R
Rob Lourens 已提交
2141
		return undefined;
2142
	}
J
Johannes Rieken 已提交
2143
}