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

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

	// optional label for the recent history
	label?: string;
121 122
}

123 124 125 126 127 128 129 130 131 132 133 134 135 136
function isFolderPathToOpen(path: IPathToOpen): path is IFolderPathToOpen {
	return !!path.folderUri;
}

interface IFolderPathToOpen {

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

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

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

	// optional label for the recent history
	label?: string;
140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155
}

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

interface IWorkspacePathToOpen {

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

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

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

	// optional label for the recent history
	label?: string;
159 160
}

J
Joao Moreno 已提交
161
export class WindowsManager implements IWindowsMainService {
J
Joao Moreno 已提交
162

163
	_serviceBrand: any;
E
Erich Gamma 已提交
164

165
	private static readonly windowsStateStorageKey = 'windowsState';
E
Erich Gamma 已提交
166

167
	private static WINDOWS: ICodeWindow[] = [];
E
Erich Gamma 已提交
168

B
Benjamin Pasero 已提交
169
	private initialUserEnv: IProcessEnvironment;
170

171
	private readonly windowsState: IWindowsState;
172
	private lastClosedWindowState?: IWindowState;
E
Erich Gamma 已提交
173

174 175
	private readonly dialogs: Dialogs;
	private readonly workspacesManager: WorkspacesManager;
B
Benjamin Pasero 已提交
176

177 178
	private _onWindowReady = new Emitter<ICodeWindow>();
	onWindowReady: CommonEvent<ICodeWindow> = this._onWindowReady.event;
179 180 181 182

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

183 184 185
	private _onWindowLoad = new Emitter<number>();
	onWindowLoad: CommonEvent<number> = this._onWindowLoad.event;

B
Benjamin Pasero 已提交
186 187 188
	private _onWindowsCountChanged = new Emitter<IWindowsCountChangedEvent>();
	onWindowsCountChanged: CommonEvent<IWindowsCountChangedEvent> = this._onWindowsCountChanged.event;

J
Joao Moreno 已提交
189
	constructor(
B
Benjamin Pasero 已提交
190
		private readonly machineId: string,
191 192 193 194 195 196 197 198 199 200
		@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
201
	) {
M
Martin Aeschlimann 已提交
202
		const windowsStateStoreData = this.stateService.getItem<WindowsStateStorageData>(WindowsManager.windowsStateStorageKey);
203 204

		this.windowsState = restoreWindowsState(windowsStateStoreData);
205 206 207
		if (!Array.isArray(this.windowsState.openedWindows)) {
			this.windowsState.openedWindows = [];
		}
208

B
Benjamin Pasero 已提交
209
		this.dialogs = new Dialogs(environmentService, telemetryService, stateService, this);
210
		this.workspacesManager = new WorkspacesManager(workspacesMainService, backupMainService, this);
211
	}
J
Joao Moreno 已提交
212

B
Benjamin Pasero 已提交
213
	ready(initialUserEnv: IProcessEnvironment): void {
214
		this.initialUserEnv = initialUserEnv;
215 216

		this.registerListeners();
E
Erich Gamma 已提交
217 218 219
	}

	private registerListeners(): void {
220

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

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

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

234 235 236 237 238 239 240 241 242 243 244
		// 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');
				}
			});
		}

245
		// Handle various lifecycle events around windows
B
Benjamin Pasero 已提交
246
		this.lifecycleService.onBeforeWindowClose(window => this.onBeforeWindowClose(window));
247
		this.lifecycleService.onBeforeShutdown(() => this.onBeforeShutdown());
248 249 250 251 252
		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 已提交
253
				this.lastClosedWindowState = undefined;
254 255
			}
		});
256 257
	}

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

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

309
			if (activeWindow) {
310
				currentWindowsState.lastActiveWindow = this.toWindowState(activeWindow);
E
Erich Gamma 已提交
311
			}
312 313 314 315 316
		}

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

320
		// 3.) All windows (except extension host) for N >= 2 to support restoreWindows: all or for auto update
321 322 323 324 325
		//
		// 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) {
326
			currentWindowsState.openedWindows = WindowsManager.WINDOWS.filter(w => !w.isExtensionDevelopmentHost).map(w => this.toWindowState(w));
327
		}
E
Erich Gamma 已提交
328

329
		// Persist
330
		this.stateService.setItem(WindowsManager.windowsStateStorageKey, getWindowsStateStoreData(currentWindowsState));
331
	}
332

333
	// See note on #onBeforeShutdown() for details how these events are flowing
334
	private onBeforeWindowClose(win: ICodeWindow): void {
B
Benjamin Pasero 已提交
335
		if (this.lifecycleService.quitRequested) {
336 337 338 339
			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
340
		const state: IWindowState = this.toWindowState(win);
341 342 343 344
		if (win.isExtensionDevelopmentHost && !win.isExtensionTestHost) {
			this.windowsState.lastPluginDevelopmentHostWindow = state; // do not let test run window state overwrite our extension development state
		}

345
		// Any non extension host window with same workspace or folder
346
		else if (!win.isExtensionDevelopmentHost && (!!win.openedWorkspace || !!win.openedFolderUri)) {
347
			this.windowsState.openedWindows.forEach(o => {
B
fix npe  
Benjamin Pasero 已提交
348
				const sameWorkspace = win.openedWorkspace && o.workspace && o.workspace.id === win.openedWorkspace.id;
349
				const sameFolder = win.openedFolderUri && o.folderUri && isEqual(o.folderUri, win.openedFolderUri);
350 351

				if (sameWorkspace || sameFolder) {
352 353 354 355 356 357 358
					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.
359 360 361
		// 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) {
362 363
			this.lastClosedWindowState = state;
		}
E
Erich Gamma 已提交
364 365
	}

366
	private toWindowState(win: ICodeWindow): IWindowState {
367
		return {
368
			workspace: win.openedWorkspace,
369
			folderUri: win.openedFolderUri,
370
			backupPath: win.backupPath,
M
Martin Aeschlimann 已提交
371
			remoteAuthority: win.remoteAuthority,
372 373 374 375
			uiState: win.serializeWindowState()
		};
	}

B
Benjamin Pasero 已提交
376
	open(openConfig: IOpenConfiguration): ICodeWindow[] {
377
		this.logService.trace('windowsManager#open');
378
		openConfig = this.validateOpenConfig(openConfig);
379

380
		const pathsToOpen = this.getPathsToOpen(openConfig);
381

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

		// When run with --diff, take the files to open as files to diff
		// if there are exactly two files provided.
417 418 419 420
		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 已提交
421 422
		}

423
		// When run with --wait, make sure we keep the paths to wait for
M
Martin Aeschlimann 已提交
424 425
		if (fileInputs && openConfig.waitMarkerFileURI) {
			fileInputs.filesToWait = { paths: [...fileInputs.filesToDiff, ...fileInputs.filesToOpen, ...fileInputs.filesToCreate], waitMarkerFileUri: openConfig.waitMarkerFileURI };
426 427
		}

428
		//
429
		// These are windows to restore because of hot-exit or from previous session (only performed once on startup!)
430
		//
431 432
		let foldersToRestore: URI[] = [];
		let workspacesToRestore: IWorkspacePathToOpen[] = [];
B
Benjamin Pasero 已提交
433
		if (openConfig.initialStartup && !openConfig.cli.extensionDevelopmentPath && !openConfig.cli['disable-restore-windows']) {
434
			let foldersToRestore = this.backupMainService.getFolderBackupPaths();
435
			foldersToAdd.push(...foldersToRestore.map(f => ({ folderUri: f, remoteAuhority: getRemoteAuthority(f), isRestored: true })));
436

437 438 439
			// collect from workspaces with hot-exit backups and from previous window session
			workspacesToRestore = [...this.backupMainService.getWorkspaceBackups(), ...this.workspacesMainService.getUntitledWorkspacesSync()];
			workspacesToOpen.push(...workspacesToRestore);
440

441 442 443
			emptyToRestore.push(...this.backupMainService.getEmptyWindowBackupPaths());
		} else {
			emptyToRestore.length = 0;
444
		}
445 446

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

449
		// Make sure to pass focus to the most relevant of the windows if we open multiple
450
		if (usedWindows.length > 1) {
451

452
			const focusLastActive = this.windowsState.lastActiveWindow && !openConfig.forceEmpty && !hasArgs(openConfig.cli._) && !hasArgs(openConfig.cli['file-uri']) && !hasArgs(openConfig.cli['folder-uri']) && !(openConfig.urisToOpen && openConfig.urisToOpen.length);
453 454
			let focusLastOpened = true;
			let focusLastWindow = true;
455

456 457
			// 1.) focus last active window if we are not instructed to open any paths
			if (focusLastActive) {
458 459 460
				const lastActiveWindow = usedWindows.filter(w => w.backupPath === this.windowsState.lastActiveWindow!.backupPath);
				if (lastActiveWindow.length) {
					lastActiveWindow[0].focus();
461 462
					focusLastOpened = false;
					focusLastWindow = false;
463 464 465
				}
			}

466 467 468 469 470
			// 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 (
471
						(usedWindow.openedWorkspace && workspacesToRestore.some(workspace => workspace.workspace.id === usedWindow.openedWorkspace!.id)) ||	// skip over restored workspace
472
						(usedWindow.openedFolderUri && foldersToRestore.some(uri => isEqual(uri, usedWindow.openedFolderUri))) ||			// skip over restored folder
M
Matt Bierner 已提交
473
						(usedWindow.backupPath && emptyToRestore.some(empty => empty.backupFolder === basename(usedWindow.backupPath!)))				// skip over restored empty window
474 475 476 477 478 479 480 481 482 483 484 485
					) {
						continue;
					}

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

			// 3.) finally, always ensure to have at least last used window focused
			if (focusLastWindow) {
486
				usedWindows[usedWindows.length - 1].focus();
487 488
			}
		}
489

490 491
		// 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
492
		if (!usedWindows.some(w => w.isExtensionDevelopmentHost) && !openConfig.diffMode && !openConfig.noRecentEntry) {
M
Martin Aeschlimann 已提交
493 494 495 496 497 498 499 500
			const recents: IRecent[] = [];
			for (let pathToOpen of pathsToOpen) {
				if (pathToOpen.workspace) {
					recents.push({ label: pathToOpen.label, workspace: pathToOpen.workspace });
				} else if (pathToOpen.folderUri) {
					recents.push({ label: pathToOpen.label, folderUri: pathToOpen.folderUri });
				} else if (pathToOpen.fileUri) {
					recents.push({ label: pathToOpen.label, fileUri: pathToOpen.fileUri });
501
				}
502
			}
M
Martin Aeschlimann 已提交
503
			this.historyMainService.addRecentlyOpened(recents);
504
		}
505

506
		// If we got started with --wait from the CLI, we need to signal to the outside when the window
507 508
		// 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.
M
Martin Aeschlimann 已提交
509 510 511
		const waitMarkerFileURI = openConfig.waitMarkerFileURI;
		if (openConfig.context === OpenContext.CLI && waitMarkerFileURI && usedWindows.length === 1 && usedWindows[0]) {
			this.waitForWindowCloseOrLoad(usedWindows[0].id).then(() => fs.unlink(waitMarkerFileURI.fsPath, _error => undefined));
512 513
		}

514 515 516
		return usedWindows;
	}

517 518 519 520 521 522 523 524 525 526
	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;
	}

527 528
	private doOpen(
		openConfig: IOpenConfiguration,
529 530
		workspacesToOpen: IWorkspacePathToOpen[],
		foldersToOpen: IFolderPathToOpen[],
531
		emptyToRestore: IEmptyWindowBackupInfo[],
532
		emptyToOpen: number,
533
		fileInputs: IFileInputs | undefined,
534
		foldersToAdd: IFolderPathToOpen[]
535
	) {
536
		const usedWindows: ICodeWindow[] = [];
537

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

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

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

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

559
			const bestWindowOrFolder = findBestWindowOrFolderForFile({
560
				windows,
561 562
				newWindow: openFilesInNewWindow,
				context: openConfig.context,
563
				fileUri: fileToCheck && fileToCheck.fileUri,
564
				localWorkspaceResolver: workspace => workspace.configPath.scheme === Schemas.file ? this.workspacesMainService.resolveLocalWorkspaceSync(workspace.configPath) : null
565
			});
B
Benjamin Pasero 已提交
566

567 568 569 570 571
			// We found a window to open the files in
			if (bestWindowOrFolder instanceof CodeWindow) {

				// Window is workspace
				if (bestWindowOrFolder.openedWorkspace) {
572
					workspacesToOpen.push({ workspace: bestWindowOrFolder.openedWorkspace, remoteAuthority: bestWindowOrFolder.remoteAuthority });
573 574 575
				}

				// Window is single folder
576
				else if (bestWindowOrFolder.openedFolderUri) {
577
					foldersToOpen.push({ folderUri: bestWindowOrFolder.openedFolderUri, remoteAuthority: bestWindowOrFolder.remoteAuthority });
578 579 580 581 582 583
				}

				// Window is empty
				else {

					// Do open files
584
					usedWindows.push(this.doOpenFilesInExistingWindow(openConfig, bestWindowOrFolder, fileInputs));
585 586

					// Reset these because we handled them
R
Rob Lourens 已提交
587
					fileInputs = undefined;
588
				}
589 590 591
			}

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

603
				// Reset these because we handled them
R
Rob Lourens 已提交
604
				fileInputs = undefined;
E
Erich Gamma 已提交
605 606 607
			}
		}

608
		// Handle workspaces to open (instructed and to restore)
609
		const allWorkspacesToOpen = arrays.distinct(workspacesToOpen, workspace => workspace.workspace.id); // prevent duplicates
610 611 612
		if (allWorkspacesToOpen.length > 0) {

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

				// Do open files
619
				usedWindows.push(this.doOpenFilesInExistingWindow(openConfig, windowOnWorkspace, fileInputsForWindow));
620 621

				// Reset these because we handled them
622
				if (fileInputsForWindow) {
R
Rob Lourens 已提交
623
					fileInputs = undefined;
624
				}
625 626 627 628 629 630

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

			// Open remaining ones
			allWorkspacesToOpen.forEach(workspaceToOpen => {
631
				if (windowsOnWorkspace.some(win => win.openedWorkspace!.id === workspaceToOpen.workspace.id)) {
632 633 634
					return; // ignore folders that are already open
				}

635
				const remoteAuthority = workspaceToOpen.remoteAuthority;
636
				const fileInputsForWindow = (fileInputs && fileInputs.remoteAuthority === remoteAuthority) ? fileInputs : undefined;
637

638
				// Do open folder
639
				usedWindows.push(this.doOpenFolderOrWorkspace(openConfig, workspaceToOpen, openFolderInNewWindow, fileInputsForWindow));
640 641

				// Reset these because we handled them
642
				if (fileInputsForWindow) {
R
Rob Lourens 已提交
643
					fileInputs = undefined;
644
				}
645 646 647 648 649

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

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

653
		if (allFoldersToOpen.length > 0) {
E
Erich Gamma 已提交
654 655

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

661
				// Do open files
662
				usedWindows.push(this.doOpenFilesInExistingWindow(openConfig, windowOnFolderPath, fileInputsForWindow));
663

E
Erich Gamma 已提交
664
				// Reset these because we handled them
665
				if (fileInputsForWindow) {
R
Rob Lourens 已提交
666
					fileInputs = undefined;
667
				}
E
Erich Gamma 已提交
668

B
Benjamin Pasero 已提交
669
				openFolderInNewWindow = true; // any other folders to open must open in new window then
E
Erich Gamma 已提交
670 671 672
			}

			// Open remaining ones
673
			allFoldersToOpen.forEach(folderToOpen => {
674

675
				if (windowsOnFolderPath.some(win => isEqual(win.openedFolderUri, folderToOpen.folderUri))) {
E
Erich Gamma 已提交
676 677 678
					return; // ignore folders that are already open
				}

679
				const remoteAuthority = folderToOpen.remoteAuthority;
R
Rob Lourens 已提交
680
				const fileInputsForWindow = (fileInputs && fileInputs.remoteAuthority === remoteAuthority) ? fileInputs : undefined;
681

682
				// Do open folder
683
				usedWindows.push(this.doOpenFolderOrWorkspace(openConfig, folderToOpen, openFolderInNewWindow, fileInputsForWindow));
E
Erich Gamma 已提交
684 685

				// Reset these because we handled them
686
				if (fileInputsForWindow) {
R
Rob Lourens 已提交
687
					fileInputs = undefined;
688
				}
E
Erich Gamma 已提交
689

B
Benjamin Pasero 已提交
690
				openFolderInNewWindow = true; // any other folders to open must open in new window then
E
Erich Gamma 已提交
691 692 693
			});
		}

694
		// Handle empty to restore
695
		const allEmptyToRestore = arrays.distinct(emptyToRestore, info => info.backupFolder); // prevent duplicates
696 697
		if (allEmptyToRestore.length > 0) {
			allEmptyToRestore.forEach(emptyWindowBackupInfo => {
M
Martin Aeschlimann 已提交
698
				const remoteAuthority = emptyWindowBackupInfo.remoteAuthority;
R
Rob Lourens 已提交
699
				const fileInputsForWindow = (fileInputs && fileInputs.remoteAuthority === remoteAuthority) ? fileInputs : undefined;
700

B
Benjamin Pasero 已提交
701
				usedWindows.push(this.openInBrowserWindow({
B
Benjamin Pasero 已提交
702 703 704
					userEnv: openConfig.userEnv,
					cli: openConfig.cli,
					initialStartup: openConfig.initialStartup,
705
					fileInputs: fileInputsForWindow,
M
Martin Aeschlimann 已提交
706
					remoteAuthority,
B
Benjamin Pasero 已提交
707
					forceNewWindow: true,
708
					forceNewTabbedWindow: openConfig.forceNewTabbedWindow,
709
					emptyWindowBackupInfo
B
Benjamin Pasero 已提交
710
				}));
711

B
wip  
Benjamin Pasero 已提交
712
				// Reset these because we handled them
713
				if (fileInputsForWindow) {
R
Rob Lourens 已提交
714
					fileInputs = undefined;
715
				}
B
wip  
Benjamin Pasero 已提交
716

B
Benjamin Pasero 已提交
717
				openFolderInNewWindow = true; // any other folders to open must open in new window then
718 719
			});
		}
B
Benjamin Pasero 已提交
720

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

738
				// Reset these because we handled them
R
Rob Lourens 已提交
739
				fileInputs = undefined;
740
				openFolderInNewWindow = true; // any other window to open must open in new window then
741 742
			}
		}
E
Erich Gamma 已提交
743

744
		return arrays.distinct(usedWindows);
E
Erich Gamma 已提交
745 746
	}

747
	private doOpenFilesInExistingWindow(configuration: IOpenConfiguration, window: ICodeWindow, fileInputs?: IFileInputs): ICodeWindow {
748 749
		window.focus(); // make sure window has focus

M
Matt Bierner 已提交
750
		const params: { filesToOpen?: IPath[], filesToCreate?: IPath[], filesToDiff?: IPath[], filesToWait?: IPathsToWaitFor, termProgram?: string } = {};
B
Benjamin Pasero 已提交
751 752 753 754 755 756 757 758 759 760 761 762
		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 已提交
763 764

		return window;
765 766
	}

767
	private doAddFoldersToExistingWindow(window: ICodeWindow, foldersToAdd: URI[]): ICodeWindow {
768 769
		window.focus(); // make sure window has focus

B
Benjamin Pasero 已提交
770
		window.sendWhenReady('vscode:addFolders', { foldersToAdd });
771 772 773 774

		return window;
	}

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

780 781 782 783
		const browserWindow = this.openInBrowserWindow({
			userEnv: openConfig.userEnv,
			cli: openConfig.cli,
			initialStartup: openConfig.initialStartup,
784
			workspace: folderOrWorkspace.workspace,
785
			folderUri: folderOrWorkspace.folderUri,
786
			fileInputs,
M
Martin Aeschlimann 已提交
787
			remoteAuthority: folderOrWorkspace.remoteAuthority,
B
Benjamin Pasero 已提交
788
			forceNewWindow,
789
			forceNewTabbedWindow: openConfig.forceNewTabbedWindow,
790
			windowToUse
791 792 793 794 795
		});

		return browserWindow;
	}

B
Benjamin Pasero 已提交
796 797
	private getPathsToOpen(openConfig: IOpenConfiguration): IPathToOpen[] {
		let windowsToOpen: IPathToOpen[];
798
		let isCommandLineOrAPICall = false;
E
Erich Gamma 已提交
799

800
		// Extract paths: from API
S
Sandeep Somavarapu 已提交
801
		if (openConfig.urisToOpen && openConfig.urisToOpen.length > 0) {
802
			windowsToOpen = this.doExtractPathsFromAPI(openConfig);
803
			isCommandLineOrAPICall = true;
E
Erich Gamma 已提交
804 805
		}

B
Benjamin Pasero 已提交
806 807
		// Check for force empty
		else if (openConfig.forceEmpty) {
808
			windowsToOpen = [Object.create(null)];
E
Erich Gamma 已提交
809 810
		}

811
		// Extract paths: from CLI
812
		else if (hasArgs(openConfig.cli._) || hasArgs(openConfig.cli['folder-uri']) || hasArgs(openConfig.cli['file-uri'])) {
813
			windowsToOpen = this.doExtractPathsFromCLI(openConfig.cli);
814
			isCommandLineOrAPICall = true;
B
Benjamin Pasero 已提交
815 816
		}

817
		// Extract windows: from previous session
B
Benjamin Pasero 已提交
818
		else {
819
			windowsToOpen = this.doGetWindowsFromLastSession();
B
Benjamin Pasero 已提交
820 821
		}

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

					// Add workspace and remove folders thereby
					windowsToOpen.push({ workspace, remoteAuthority });
					windowsToOpen = windowsToOpen.filter(path => !path.folderUri);
				}
837 838 839
			}
		}

840
		return windowsToOpen;
E
Erich Gamma 已提交
841 842
	}

843
	private doExtractPathsFromAPI(openConfig: IOpenConfiguration): IPathToOpen[] {
M
Matt Bierner 已提交
844
		const pathsToOpen: IPathToOpen[] = [];
845
		const cli = openConfig.cli;
846
		const parseOptions: IPathParseOptions = { gotoLineMode: cli && cli.goto, forceOpenWorkspaceAsFile: openConfig.forceOpenWorkspaceAsFile };
M
Matt Bierner 已提交
847
		for (const pathToOpen of openConfig.urisToOpen || []) {
M
Martin Aeschlimann 已提交
848 849 850 851
			if (!pathToOpen) {
				continue;
			}

852
			const path = this.parseUri(pathToOpen.uri, pathToOpen.typeHint, parseOptions);
M
Martin Aeschlimann 已提交
853
			if (path) {
M
Martin Aeschlimann 已提交
854
				path.label = pathToOpen.label;
M
Martin Aeschlimann 已提交
855 856
				pathsToOpen.push(path);
			} else {
857

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

876
				this.dialogs.showMessageBox(options, this.getFocusedWindow());
877
			}
M
Martin Aeschlimann 已提交
878
		}
879 880 881 882
		return pathsToOpen;
	}

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

		// folder uris
887
		const folderUris = asArray(cli['folder-uri']);
M
Martin Aeschlimann 已提交
888
		for (let folderUri of folderUris) {
889
			const path = this.parseUri(this.argToUri(folderUri), 'folder', parseOptions);
M
Martin Aeschlimann 已提交
890 891 892
			if (path) {
				pathsToOpen.push(path);
			}
893 894 895 896
		}

		// file uris
		const fileUris = asArray(cli['file-uri']);
M
Martin Aeschlimann 已提交
897
		for (let fileUri of fileUris) {
898
			const path = this.parseUri(this.argToUri(fileUri), 'file');
M
Martin Aeschlimann 已提交
899 900 901
			if (path) {
				pathsToOpen.push(path);
			}
902 903 904
		}

		// folder or file paths
M
Martin Aeschlimann 已提交
905 906 907 908 909 910
		const cliArgs = asArray(cli._);
		for (let cliArg of cliArgs) {
			const path = this.parsePath(cliArg, parseOptions);
			if (path) {
				pathsToOpen.push(path);
			}
911 912
		}

M
Martin Aeschlimann 已提交
913
		if (pathsToOpen.length) {
914
			return pathsToOpen;
B
Benjamin Pasero 已提交
915 916 917 918 919 920
		}

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

B
Benjamin Pasero 已提交
921
	private doGetWindowsFromLastSession(): IPathToOpen[] {
922
		const restoreWindows = this.getRestoreWindowsSetting();
B
Benjamin Pasero 已提交
923

924
		switch (restoreWindows) {
B
Benjamin Pasero 已提交
925

926
			// none: we always open an empty window
927 928
			case 'none':
				return [Object.create(null)];
B
Benjamin Pasero 已提交
929

930
			// one: restore last opened workspace/folder or empty window
931 932
			// all: restore all windows
			// folders: restore last opened folders only
933
			case 'one':
934 935
			case 'all':
			case 'folders':
936 937 938
				const openedWindows: IWindowState[] = [];
				if (restoreWindows !== 'one') {
					openedWindows.push(...this.windowsState.openedWindows);
939
				}
940 941
				if (this.windowsState.lastActiveWindow) {
					openedWindows.push(this.windowsState.lastActiveWindow);
942
				}
943

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

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

				break;
B
Benjamin Pasero 已提交
966
		}
E
Erich Gamma 已提交
967

968
		// Always fallback to empty window
B
Benjamin Pasero 已提交
969
		return [Object.create(null)];
E
Erich Gamma 已提交
970 971
	}

972 973 974 975 976
	private getRestoreWindowsSetting(): RestoreWindowsSetting {
		let restoreWindows: RestoreWindowsSetting;
		if (this.lifecycleService.wasRestarted) {
			restoreWindows = 'all'; // always reopen all windows when an update was applied
		} else {
977
			const windowConfig = this.configurationService.getValue<IWindowSettings>('window');
978
			restoreWindows = ((windowConfig && windowConfig.restoreWindows) || 'one') as RestoreWindowsSetting;
979 980 981 982 983 984 985 986 987

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

		return restoreWindows;
	}

988
	private argToUri(arg: string): URI | undefined {
M
Martin Aeschlimann 已提交
989
		try {
990
			const uri = URI.parse(arg);
M
Martin Aeschlimann 已提交
991
			if (!uri.scheme) {
M
Martin Aeschlimann 已提交
992
				this.logService.error(`Invalid URI input string, scheme missing: ${arg}`);
993
				return undefined;
M
Martin Aeschlimann 已提交
994 995 996
			}
			return uri;
		} catch (e) {
M
Martin Aeschlimann 已提交
997
			this.logService.error(`Invalid URI input string: ${arg}, ${e.message}`);
998
		}
999
		return undefined;
1000 1001
	}

1002
	private parseUri(uri: URI | undefined, typeHint?: URIType, options: IPathParseOptions = {}): IPathToOpen | undefined {
M
Martin Aeschlimann 已提交
1003
		if (!uri || !uri.scheme) {
1004
			return undefined;
1005
		}
M
Martin Aeschlimann 已提交
1006 1007
		if (uri.scheme === Schemas.file) {
			return this.parsePath(uri.fsPath, options);
1008
		}
M
Martin Aeschlimann 已提交
1009 1010

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

1013 1014
		// normalize URI
		uri = normalizePath(uri);
1015 1016 1017


		// remove trailing slash
1018 1019
		if (hasTrailingPathSeparator(uri)) {
			uri = removeTrailingPathSeparator(uri);
1020 1021 1022 1023 1024 1025
			if (!typeHint) {
				typeHint = 'folder';
			}
		}

		// if there's no type hint
1026
		if (!typeHint && (hasWorkspaceFileExtension(uri.path) || options.gotoLineMode)) {
1027
			typeHint = 'file';
1028
		}
1029 1030 1031

		if (typeHint === 'file') {
			if (options.gotoLineMode) {
1032 1033 1034 1035
				const parsedPath = parseLineAndColumnAware(uri.path);
				return {
					fileUri: uri.with({ path: parsedPath.path }),
					lineNumber: parsedPath.line,
M
Martin Aeschlimann 已提交
1036 1037
					columnNumber: parsedPath.column,
					remoteAuthority
1038 1039
				};
			}
1040
			if (hasWorkspaceFileExtension(uri.path) && !options.forceOpenWorkspaceAsFile) {
1041
				return {
M
Martin Aeschlimann 已提交
1042
					workspace: getWorkspaceIdentifier(uri),
1043 1044 1045
					remoteAuthority
				};
			}
1046
			return {
M
Martin Aeschlimann 已提交
1047 1048
				fileUri: uri,
				remoteAuthority
1049 1050
			};
		}
1051
		return {
M
Martin Aeschlimann 已提交
1052 1053
			folderUri: uri,
			remoteAuthority
1054 1055 1056
		};
	}

1057
	private parsePath(anyPath: string, options: IPathParseOptions): IPathToOpen | undefined {
E
Erich Gamma 已提交
1058
		if (!anyPath) {
1059
			return undefined;
E
Erich Gamma 已提交
1060 1061
		}

1062
		let lineNumber, columnNumber: number | undefined;
1063

1064
		if (options.gotoLineMode) {
1065 1066 1067
			const parsedPath = parseLineAndColumnAware(anyPath);
			lineNumber = parsedPath.line;
			columnNumber = parsedPath.column;
1068

E
Erich Gamma 已提交
1069 1070 1071
			anyPath = parsedPath.path;
		}

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

1075
		const candidate = normalize(anyPath);
E
Erich Gamma 已提交
1076
		try {
B
Benjamin Pasero 已提交
1077
			const candidateStat = fs.statSync(candidate);
E
Erich Gamma 已提交
1078
			if (candidateStat) {
1079
				if (candidateStat.isFile()) {
1080

1081
					// Workspace (unless disabled via flag)
1082
					if (!options.forceOpenWorkspaceAsFile) {
1083
						const workspace = this.workspacesMainService.resolveLocalWorkspaceSync(URI.file(candidate));
1084
						if (workspace) {
1085
							return { workspace: { id: workspace.id, configPath: workspace.configPath }, remoteAuthority: workspace.remoteAuthority };
1086
						}
1087 1088 1089
					}

					// File
1090
					return {
1091
						fileUri: URI.file(candidate),
1092 1093
						lineNumber,
						columnNumber,
M
Martin Aeschlimann 已提交
1094
						remoteAuthority
1095 1096 1097
					};
				}

1098 1099 1100 1101 1102
				// 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 已提交
1103 1104
						folderUri: URI.file(candidate),
						remoteAuthority
1105 1106
					};
				}
E
Erich Gamma 已提交
1107 1108
			}
		} catch (error) {
S
Sandeep Somavarapu 已提交
1109
			const fileUri = URI.file(candidate);
M
Martin Aeschlimann 已提交
1110
			this.historyMainService.removeFromRecentlyOpened([fileUri]); // since file does not seem to exist anymore, remove from recent
1111
			if (options && options.ignoreFileNotFound) {
M
Martin Aeschlimann 已提交
1112
				return { fileUri, createFilePath: true, remoteAuthority }; // assume this is a file that does not yet exist
E
Erich Gamma 已提交
1113 1114 1115
			}
		}

1116
		return undefined;
E
Erich Gamma 已提交
1117 1118
	}

B
Benjamin Pasero 已提交
1119 1120 1121
	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
1122
		const windowConfig = this.configurationService.getValue<IWindowSettings>('window');
1123 1124 1125
		const openFolderInNewWindowConfig = (windowConfig && windowConfig.openFoldersInNewWindow) || 'default' /* default */;
		const openFilesInNewWindowConfig = (windowConfig && windowConfig.openFilesInNewWindow) || 'off' /* default */;

B
Benjamin Pasero 已提交
1126
		let openFolderInNewWindow = (openConfig.preferNewWindow || openConfig.forceNewWindow) && !openConfig.forceReuseWindow;
1127 1128
		if (!openConfig.forceNewWindow && !openConfig.forceReuseWindow && (openFolderInNewWindowConfig === 'on' || openFolderInNewWindowConfig === 'off')) {
			openFolderInNewWindow = (openFolderInNewWindowConfig === 'on');
B
Benjamin Pasero 已提交
1129 1130 1131
		}

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

			// 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 已提交
1149 1150
			}

1151
			// finally check for overrides of default
1152 1153
			if (!openConfig.cli.extensionDevelopmentPath && (openFilesInNewWindowConfig === 'on' || openFilesInNewWindowConfig === 'off')) {
				openFilesInNewWindow = (openFilesInNewWindowConfig === 'on');
B
Benjamin Pasero 已提交
1154 1155 1156
			}
		}

M
Matt Bierner 已提交
1157
		return { openFolderInNewWindow: !!openFolderInNewWindow, openFilesInNewWindow };
B
Benjamin Pasero 已提交
1158 1159
	}

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

B
Benjamin Pasero 已提交
1162 1163 1164
		// 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.
1165
		const existingWindow = findWindowOnExtensionDevelopmentPath(WindowsManager.WINDOWS, extensionDevelopmentPath);
1166 1167 1168
		if (existingWindow) {
			this.reload(existingWindow, openConfig.cli);
			existingWindow.focus(); // make sure it gets focus and is restored
E
Erich Gamma 已提交
1169

B
Benjamin Pasero 已提交
1170 1171
			return;
		}
1172 1173 1174
		let folderUris = asArray(openConfig.cli['folder-uri']);
		let fileUris = asArray(openConfig.cli['file-uri']);
		let cliArgs = openConfig.cli._;
E
Erich Gamma 已提交
1175

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

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

M
Martin Aeschlimann 已提交
1202
		if (folderUris.length && folderUris.some(uri => !!findWindowOnWorkspaceOrFolderUri(WindowsManager.WINDOWS, this.argToUri(uri)))) {
1203 1204 1205
			folderUris = [];
		}

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

1210 1211 1212 1213
		openConfig.cli._ = cliArgs;
		openConfig.cli['folder-uri'] = folderUris;
		openConfig.cli['file-uri'] = fileUris;

1214 1215
		const match = extensionDevelopmentPath.match(/^vscode-remote:\/\/([^\/]+)/);
		if (match) {
1216
			openConfig.cli['remote'] = URI.parse(extensionDevelopmentPath).authority;
1217 1218
		}

B
Benjamin Pasero 已提交
1219
		// Open it
M
Martin Aeschlimann 已提交
1220 1221 1222 1223 1224 1225 1226 1227 1228 1229
		const openArgs: IOpenConfiguration = {
			context: openConfig.context,
			cli: openConfig.cli,
			forceNewWindow: true,
			forceEmpty: !cliArgs.length && !folderUris.length && !fileUris.length,
			userEnv: openConfig.userEnv,
			noRecentEntry: true,
			waitMarkerFileURI: openConfig.waitMarkerFileURI
		};
		this.open(openArgs);
E
Erich Gamma 已提交
1230 1231
	}

1232
	private openInBrowserWindow(options: IOpenBrowserWindowOptions): ICodeWindow {
1233

B
Benjamin Pasero 已提交
1234 1235 1236
		// Build IWindowConfiguration from config and options
		const configuration: IWindowConfiguration = mixin({}, options.cli); // inherit all properties from CLI
		configuration.appRoot = this.environmentService.appRoot;
1237
		configuration.machineId = this.machineId;
1238
		configuration.nodeCachedDataDir = this.environmentService.nodeCachedDataDir;
1239
		configuration.mainPid = process.pid;
B
Benjamin Pasero 已提交
1240 1241 1242
		configuration.execPath = process.execPath;
		configuration.userEnv = assign({}, this.initialUserEnv, options.userEnv || {});
		configuration.isInitialStartup = options.initialStartup;
1243
		configuration.workspace = options.workspace;
1244
		configuration.folderUri = options.folderUri;
M
Martin Aeschlimann 已提交
1245
		configuration.remoteAuthority = options.remoteAuthority;
1246 1247 1248 1249 1250 1251 1252 1253

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

1255
		// if we know the backup folder upfront (for empty windows to restore), we can set it
1256
		// directly here which helps for restoring UI state associated with that window.
B
Benjamin Pasero 已提交
1257
		// For all other cases we first call into registerEmptyWindowBackupSync() to set it before
1258
		// loading the window.
1259
		if (options.emptyWindowBackupInfo) {
1260
			configuration.backupPath = join(this.environmentService.backupHome, options.emptyWindowBackupInfo.backupFolder);
1261 1262
		}

1263
		let window: ICodeWindow | undefined;
1264
		if (!options.forceNewWindow && !options.forceNewTabbedWindow) {
1265 1266 1267
			window = options.windowToUse || this.getLastActiveWindow();
			if (window) {
				window.focus();
E
Erich Gamma 已提交
1268 1269 1270 1271
			}
		}

		// New window
1272
		if (!window) {
1273
			const windowConfig = this.configurationService.getValue<IWindowSettings>('window');
1274 1275 1276 1277 1278 1279 1280 1281 1282 1283
			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 {
1284
				allowFullscreen = this.lifecycleService.wasRestarted || (windowConfig && windowConfig.restoreFullscreen);
1285 1286 1287 1288 1289
			}

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

1291
			// Create the window
1292
			window = this.instantiationService.createInstance(CodeWindow, {
1293
				state,
1294
				extensionDevelopmentPath: configuration.extensionDevelopmentPath,
1295
				isExtensionTestHost: !!configuration.extensionTestsPath
1296
			});
1297

1298 1299 1300 1301 1302 1303 1304 1305
			// Add as window tab if configured (macOS only)
			if (options.forceNewTabbedWindow) {
				const activeWindow = this.getLastActiveWindow();
				if (activeWindow) {
					activeWindow.addTabbedWindow(window);
				}
			}

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

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

E
Erich Gamma 已提交
1312
			// Window Events
1313
			window.win.webContents.removeAllListeners('devtools-reload-page'); // remove built in listener so we can handle this on our own
M
Matt Bierner 已提交
1314 1315 1316 1317
			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 已提交
1318 1319

			// Lifecycle
B
Benjamin Pasero 已提交
1320
			(this.lifecycleService as LifecycleService).registerWindow(window);
E
Erich Gamma 已提交
1321 1322 1323 1324 1325 1326
		}

		// Existing window
		else {

			// Some configuration things get inherited if the window is being reused and we are
B
Benjamin Pasero 已提交
1327
			// in extension development host mode. These options are all development related.
1328
			const currentWindowConfig = window.config;
A
Alex Dima 已提交
1329 1330
			if (!configuration.extensionDevelopmentPath && currentWindowConfig && !!currentWindowConfig.extensionDevelopmentPath) {
				configuration.extensionDevelopmentPath = currentWindowConfig.extensionDevelopmentPath;
B
Benjamin Pasero 已提交
1331
				configuration.verbose = currentWindowConfig.verbose;
1332
				configuration['inspect-brk-extensions'] = currentWindowConfig['inspect-brk-extensions'];
1333
				configuration.debugId = currentWindowConfig.debugId;
1334
				configuration['inspect-extensions'] = currentWindowConfig['inspect-extensions'];
1335
				configuration['extensions-dir'] = currentWindowConfig['extensions-dir'];
E
Erich Gamma 已提交
1336 1337 1338
			}
		}

1339 1340 1341 1342 1343 1344
		// 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 已提交
1345
					this.doOpenInBrowserWindow(window!, configuration, options);
B
Benjamin Pasero 已提交
1346
				}
1347 1348 1349 1350 1351 1352 1353
			});
		} else {
			this.doOpenInBrowserWindow(window, configuration, options);
		}

		return window;
	}
B
Benjamin Pasero 已提交
1354

1355
	private doOpenInBrowserWindow(window: ICodeWindow, configuration: IWindowConfiguration, options: IOpenBrowserWindowOptions): void {
1356

1357 1358 1359
		// Register window for backups
		if (!configuration.extensionDevelopmentPath) {
			if (configuration.workspace) {
1360
				configuration.backupPath = this.backupMainService.registerWorkspaceBackupSync({ workspace: configuration.workspace, remoteAuthority: configuration.remoteAuthority });
1361 1362 1363 1364
			} else if (configuration.folderUri) {
				configuration.backupPath = this.backupMainService.registerFolderBackupSync(configuration.folderUri);
			} else {
				const backupFolder = options.emptyWindowBackupInfo && options.emptyWindowBackupInfo.backupFolder;
1365
				configuration.backupPath = this.backupMainService.registerEmptyWindowBackupSync(backupFolder, configuration.remoteAuthority);
E
Erich Gamma 已提交
1366
			}
1367
		}
1368

1369 1370 1371 1372 1373
		// Load it
		window.load(configuration);

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

1376
	private getNewWindowState(configuration: IWindowConfiguration): INewWindowState {
1377
		const lastActive = this.getLastActiveWindow();
E
Erich Gamma 已提交
1378

1379 1380
		// Restore state unless we are running extension tests
		if (!configuration.extensionTestsPath) {
E
Erich Gamma 已提交
1381

1382 1383 1384
			// extension development host Window - load from stored settings if any
			if (!!configuration.extensionDevelopmentPath && this.windowsState.lastPluginDevelopmentHostWindow) {
				return this.windowsState.lastPluginDevelopmentHostWindow.uiState;
1385 1386
			}

1387
			// Known Workspace - load from stored settings
M
Matt Bierner 已提交
1388 1389 1390
			const workspace = configuration.workspace;
			if (workspace) {
				const stateForWorkspace = this.windowsState.openedWindows.filter(o => o.workspace && o.workspace.id === workspace.id).map(o => o.uiState);
1391 1392 1393 1394 1395 1396
				if (stateForWorkspace.length) {
					return stateForWorkspace[0];
				}
			}

			// Known Folder - load from stored settings
1397
			if (configuration.folderUri) {
1398
				const stateForFolder = this.windowsState.openedWindows.filter(o => o.folderUri && isEqual(o.folderUri, configuration.folderUri)).map(o => o.uiState);
1399 1400 1401
				if (stateForFolder.length) {
					return stateForFolder[0];
				}
1402 1403
			}

1404 1405 1406 1407 1408 1409
			// 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 已提交
1410 1411
			}

1412 1413 1414 1415 1416
			// First Window
			const lastActiveState = this.lastClosedWindowState || this.windowsState.lastActiveWindow;
			if (!lastActive && lastActiveState) {
				return lastActiveState.uiState;
			}
E
Erich Gamma 已提交
1417 1418 1419 1420 1421 1422 1423
		}

		//
		// 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
1424
		let displayToUse: Electron.Display | undefined;
B
Benjamin Pasero 已提交
1425
		const displays = screen.getAllDisplays();
E
Erich Gamma 已提交
1426 1427 1428 1429 1430 1431 1432 1433 1434 1435

		// 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 已提交
1436
			if (isMacintosh) {
B
Benjamin Pasero 已提交
1437
				const cursorPoint = screen.getCursorScreenPoint();
E
Erich Gamma 已提交
1438 1439 1440 1441 1442 1443 1444 1445
				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());
			}

1446
			// fallback to primary display or first display
E
Erich Gamma 已提交
1447
			if (!displayToUse) {
1448
				displayToUse = screen.getPrimaryDisplay() || displays[0];
E
Erich Gamma 已提交
1449 1450 1451
			}
		}

1452 1453 1454
		// 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.
1455
		let state = defaultWindowState() as INewWindowState;
M
Matt Bierner 已提交
1456 1457
		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 已提交
1458

1459
		// Check for newWindowDimensions setting and adjust accordingly
1460
		const windowConfig = this.configurationService.getValue<IWindowSettings>('window');
1461 1462 1463 1464 1465 1466 1467 1468 1469
		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 已提交
1470 1471 1472 1473 1474 1475 1476
				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;
				}

1477 1478 1479 1480 1481 1482 1483 1484
				ensureNoOverlap = false;
			}
		}

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

1485 1486
		state.hasDefaultState = true; // flag as default state

1487
		return state;
E
Erich Gamma 已提交
1488 1489
	}

J
Joao Moreno 已提交
1490
	private ensureNoOverlap(state: ISingleWindowState): ISingleWindowState {
E
Erich Gamma 已提交
1491 1492 1493 1494
		if (WindowsManager.WINDOWS.length === 0) {
			return state;
		}

M
Matt Bierner 已提交
1495 1496 1497
		state.x = typeof state.x === 'number' ? state.x : 0;
		state.y = typeof state.y === 'number' ? state.y : 0;

1498 1499
		const existingWindowBounds = WindowsManager.WINDOWS.map(win => win.getBounds());
		while (existingWindowBounds.some(b => b.x === state.x || b.y === state.y)) {
E
Erich Gamma 已提交
1500 1501 1502 1503 1504 1505 1506
			state.x += 30;
			state.y += 30;
		}

		return state;
	}

B
Benjamin Pasero 已提交
1507
	reload(win: ICodeWindow, cli?: ParsedArgs): void {
B
Benjamin Pasero 已提交
1508 1509

		// Only reload when the window has not vetoed this
1510
		this.lifecycleService.unload(win, UnloadReason.RELOAD).then(veto => {
B
Benjamin Pasero 已提交
1511
			if (!veto) {
R
Rob Lourens 已提交
1512
				win.reload(undefined, cli);
B
Benjamin Pasero 已提交
1513 1514 1515 1516
			}
		});
	}

B
Benjamin Pasero 已提交
1517
	closeWorkspace(win: ICodeWindow): void {
1518 1519
		this.openInBrowserWindow({
			cli: this.environmentService.args,
M
Martin Aeschlimann 已提交
1520 1521
			windowToUse: win,
			remoteAuthority: win.remoteAuthority
1522 1523 1524
		});
	}

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

1529
	private doEnterWorkspace(win: ICodeWindow, result: IEnterWorkspaceResult): IEnterWorkspaceResult {
1530

1531
		// Mark as recently opened
M
Martin Aeschlimann 已提交
1532
		this.historyMainService.addRecentlyOpened([{ workspace: result.workspace }]);
1533

1534 1535 1536
		// Trigger Eevent to indicate load of workspace into window
		this._onWindowReady.fire(win);

1537
		return result;
1538 1539
	}

B
Benjamin Pasero 已提交
1540
	pickWorkspaceAndOpen(options: INativeOpenDialogOptions): void {
1541
		this.workspacesManager.pickWorkspaceAndOpen(options);
1542 1543
	}

B
Benjamin Pasero 已提交
1544
	focusLastActive(cli: ParsedArgs, context: OpenContext): ICodeWindow {
B
Benjamin Pasero 已提交
1545
		const lastActive = this.getLastActiveWindow();
E
Erich Gamma 已提交
1546
		if (lastActive) {
B
Benjamin Pasero 已提交
1547
			lastActive.focus();
1548 1549

			return lastActive;
E
Erich Gamma 已提交
1550 1551
		}

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

M
Matt Bierner 已提交
1556
	getLastActiveWindow(): ICodeWindow | undefined {
1557
		return getLastActiveWindow(WindowsManager.WINDOWS);
E
Erich Gamma 已提交
1558 1559
	}

1560
	getLastActiveWindowForAuthority(remoteAuthority: string | undefined): ICodeWindow | undefined {
M
Martin Aeschlimann 已提交
1561 1562 1563
		return getLastActiveWindow(WindowsManager.WINDOWS.filter(w => w.remoteAuthority === remoteAuthority));
	}

1564 1565
	openNewWindow(context: OpenContext, options?: INewWindowOptions): ICodeWindow[] {
		let cli = this.environmentService.args;
1566
		const remote = options && options.remoteAuthority || undefined;
M
Martin Aeschlimann 已提交
1567 1568 1569
		if (cli && (cli.remote !== remote)) {
			cli = { ...cli, remote };
		}
1570
		return this.open({ context, cli, forceNewWindow: true, forceEmpty: true });
E
Erich Gamma 已提交
1571 1572
	}

1573 1574 1575 1576
	openNewTabbedWindow(context: OpenContext): ICodeWindow[] {
		return this.open({ context, cli: this.environmentService.args, forceNewTabbedWindow: true, forceEmpty: true });
	}

J
Johannes Rieken 已提交
1577
	waitForWindowCloseOrLoad(windowId: number): Promise<void> {
B
Benjamin Pasero 已提交
1578
		return new Promise<void>(resolve => {
1579
			function handler(id: number) {
1580
				if (id === windowId) {
1581 1582 1583
					closeListener.dispose();
					loadListener.dispose();

1584
					resolve();
1585
				}
1586 1587 1588 1589
			}

			const closeListener = this.onWindowClose(id => handler(id));
			const loadListener = this.onWindowLoad(id => handler(id));
1590 1591 1592
		});
	}

B
Benjamin Pasero 已提交
1593
	sendToFocused(channel: string, ...args: any[]): void {
E
Erich Gamma 已提交
1594 1595 1596
		const focusedWindow = this.getFocusedWindow() || this.getLastActiveWindow();

		if (focusedWindow) {
1597
			focusedWindow.sendWhenReady(channel, ...args);
E
Erich Gamma 已提交
1598 1599 1600
		}
	}

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

1607
			w.sendWhenReady(channel, payload);
E
Erich Gamma 已提交
1608 1609 1610
		});
	}

M
Matt Bierner 已提交
1611
	getFocusedWindow(): ICodeWindow | undefined {
B
Benjamin Pasero 已提交
1612
		const win = BrowserWindow.getFocusedWindow();
E
Erich Gamma 已提交
1613 1614 1615 1616
		if (win) {
			return this.getWindowById(win.id);
		}

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

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

M
Matt Bierner 已提交
1626
		return undefined;
E
Erich Gamma 已提交
1627 1628
	}

B
Benjamin Pasero 已提交
1629
	getWindows(): ICodeWindow[] {
E
Erich Gamma 已提交
1630 1631 1632
		return WindowsManager.WINDOWS;
	}

B
Benjamin Pasero 已提交
1633
	getWindowCount(): number {
E
Erich Gamma 已提交
1634 1635 1636
		return WindowsManager.WINDOWS.length;
	}

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

1640 1641
		/* __GDPR__
			"windowerror" : {
K
kieferrm 已提交
1642
				"type" : { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth", "isMeasurement": true }
1643 1644 1645 1646
			}
		*/
		this.telemetryService.publicLog('windowerror', { type: error });

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

1672 1673 1674 1675 1676 1677 1678
				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 已提交
1679 1680 1681 1682
		}

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

1695 1696 1697 1698 1699 1700 1701
				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 已提交
1702 1703 1704
		}
	}

1705
	private onWindowClosed(win: ICodeWindow): void {
E
Erich Gamma 已提交
1706 1707 1708 1709 1710

		// Tell window
		win.dispose();

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

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

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

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

B
Benjamin Pasero 已提交
1727
	pickFileAndOpen(options: INativeOpenDialogOptions): void {
B
Benjamin Pasero 已提交
1728 1729 1730 1731 1732 1733 1734 1735 1736
		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;

1737 1738
		const dialogOptions: OpenDialogOptions = internalOptions.dialogOptions || Object.create(null);
		internalOptions.dialogOptions = dialogOptions;
B
Benjamin Pasero 已提交
1739 1740 1741 1742 1743 1744 1745 1746 1747 1748 1749 1750 1751

		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 已提交
1752
				// __GDPR__TODO__ classify event
B
Benjamin Pasero 已提交
1753 1754 1755 1756 1757 1758 1759 1760
				internalOptions.telemetryEventName = 'openFileFolder';
			} else if (pickFolders) {
				internalOptions.telemetryEventName = 'openFolder';
			} else {
				internalOptions.telemetryEventName = 'openFile';
			}
		}

1761 1762 1763
		this.dialogs.pickAndOpen(internalOptions);
	}

J
Johannes Rieken 已提交
1764
	showMessageBox(options: Electron.MessageBoxOptions, win?: ICodeWindow): Promise<IMessageBoxResult> {
1765 1766 1767
		return this.dialogs.showMessageBox(options, win);
	}

J
Johannes Rieken 已提交
1768
	showSaveDialog(options: Electron.SaveDialogOptions, win?: ICodeWindow): Promise<string> {
1769 1770 1771
		return this.dialogs.showSaveDialog(options, win);
	}

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

B
Benjamin Pasero 已提交
1776
	quit(): void {
B
Benjamin Pasero 已提交
1777 1778 1779

		// 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.
1780 1781 1782
		const window = this.getFocusedWindow();
		if (window && window.isExtensionDevelopmentHost && this.getWindowCount() > 1) {
			window.win.close();
B
Benjamin Pasero 已提交
1783 1784 1785 1786 1787 1788 1789 1790
		}

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

B
Benjamin Pasero 已提交
1794
interface IInternalNativeOpenDialogOptions extends INativeOpenDialogOptions {
B
Benjamin Pasero 已提交
1795 1796 1797 1798
	pickFolders?: boolean;
	pickFiles?: boolean;
}

1799
class Dialogs {
B
Benjamin Pasero 已提交
1800

1801
	private static readonly workingDirPickerStorageKey = 'pickerWorkingDir';
1802

1803 1804
	private readonly mapWindowToDialogQueue: Map<number, Queue<any>>;
	private readonly noWindowDialogQueue: Queue<any>;
1805

B
Benjamin Pasero 已提交
1806
	constructor(
1807 1808 1809 1810
		private readonly environmentService: IEnvironmentService,
		private readonly telemetryService: ITelemetryService,
		private readonly stateService: IStateService,
		private readonly windowsMainService: IWindowsMainService,
B
Benjamin Pasero 已提交
1811
	) {
1812 1813
		this.mapWindowToDialogQueue = new Map<number, Queue<any>>();
		this.noWindowDialogQueue = new Queue<any>();
B
Benjamin Pasero 已提交
1814 1815
	}

B
Benjamin Pasero 已提交
1816
	pickAndOpen(options: INativeOpenDialogOptions): void {
1817
		this.getFileOrFolderUris(options).then(paths => {
B
Benjamin Pasero 已提交
1818 1819 1820 1821
			const numberOfPaths = paths ? paths.length : 0;

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

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

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

B
Benjamin Pasero 已提交
1846
		// Ensure dialog options
M
Matt Bierner 已提交
1847 1848
		const dialogOptions = options.dialogOptions || Object.create(null);
		options.dialogOptions = dialogOptions;
B
Benjamin Pasero 已提交
1849 1850

		// Ensure defaultPath
M
Matt Bierner 已提交
1851 1852
		if (!dialogOptions.defaultPath) {
			dialogOptions.defaultPath = this.stateService.getItem<string>(Dialogs.workingDirPickerStorageKey);
1853 1854
		}

B
Benjamin Pasero 已提交
1855 1856
		// Ensure properties
		if (typeof options.pickFiles === 'boolean' || typeof options.pickFolders === 'boolean') {
M
Matt Bierner 已提交
1857
			dialogOptions.properties = undefined; // let it override based on the booleans
B
Benjamin Pasero 已提交
1858 1859

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

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

1868
		if (isMacintosh) {
M
Matt Bierner 已提交
1869
			dialogOptions.properties.push('treatPackageAsDirectory'); // always drill into .app files
1870 1871
		}

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

M
Matt Bierner 已提交
1875
		return this.showOpenDialog(dialogOptions, focusedWindow).then(paths => {
1876 1877 1878 1879 1880
			if (paths && paths.length > 0) {

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

1881 1882 1883 1884 1885 1886
				const result: IURIToOpen[] = [];
				for (const path of paths) {
					result.push({ uri: URI.file(path) });
				}

				return result;
1887 1888
			}

R
Rob Lourens 已提交
1889
			return undefined;
1890 1891 1892 1893 1894 1895 1896 1897 1898 1899 1900 1901 1902 1903 1904 1905 1906
		});
	}

	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 已提交
1907
	showMessageBox(options: Electron.MessageBoxOptions, window?: ICodeWindow): Promise<IMessageBoxResult> {
1908
		return this.getDialogQueue(window).queue(() => {
B
Benjamin Pasero 已提交
1909
			return new Promise(resolve => {
1910
				const callback = (response: number, checkboxChecked: boolean) => {
B
Benjamin Pasero 已提交
1911
					resolve({ button: response, checkboxChecked });
1912 1913 1914 1915 1916 1917 1918
				};

				if (window) {
					dialog.showMessageBox(window.win, options, callback);
				} else {
					dialog.showMessageBox(options, callback);
				}
1919 1920 1921 1922
			});
		});
	}

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

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

1930 1931
			return path;
		}
1932

1933
		return this.getDialogQueue(window).queue(() => {
B
Benjamin Pasero 已提交
1934
			return new Promise(resolve => {
1935
				const callback = (path: string) => {
B
Benjamin Pasero 已提交
1936
					resolve(normalizePath(path));
1937 1938 1939 1940 1941 1942 1943
				};

				if (window) {
					dialog.showSaveDialog(window.win, options, callback);
				} else {
					dialog.showSaveDialog(options, callback);
				}
1944 1945 1946 1947
			});
		});
	}

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

1950 1951 1952 1953 1954 1955
		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;
1956
		}
B
Benjamin Pasero 已提交
1957

1958
		return this.getDialogQueue(window).queue(() => {
B
Benjamin Pasero 已提交
1959
			return new Promise(resolve => {
B
Benjamin Pasero 已提交
1960 1961

				// Ensure the path exists (if provided)
B
Benjamin Pasero 已提交
1962
				let validatePathPromise: Promise<void> = Promise.resolve();
B
Benjamin Pasero 已提交
1963 1964 1965
				if (options.defaultPath) {
					validatePathPromise = exists(options.defaultPath).then(exists => {
						if (!exists) {
R
Rob Lourens 已提交
1966
							options.defaultPath = undefined;
B
Benjamin Pasero 已提交
1967 1968 1969 1970 1971 1972
						}
					});
				}

				// Show dialog and wrap as promise
				validatePathPromise.then(() => {
1973
					const callback = (paths: string[]) => {
B
Benjamin Pasero 已提交
1974
						resolve(normalizePaths(paths));
1975 1976 1977 1978 1979 1980 1981
					};

					if (window) {
						dialog.showOpenDialog(window.win, options, callback);
					} else {
						dialog.showOpenDialog(options, callback);
					}
B
Benjamin Pasero 已提交
1982
				});
1983 1984
			});
		});
1985
	}
1986 1987 1988 1989 1990
}

class WorkspacesManager {

	constructor(
1991 1992 1993 1994
		private readonly workspacesMainService: IWorkspacesMainService,
		private readonly backupMainService: IBackupMainService,
		private readonly windowsMainService: IWindowsMainService,
	) { }
1995

M
Martin Aeschlimann 已提交
1996
	enterWorkspace(window: ICodeWindow, path: URI): Promise<IEnterWorkspaceResult | null> {
B
Benjamin Pasero 已提交
1997
		if (!window || !window.win || !window.isReady) {
B
Benjamin Pasero 已提交
1998
			return Promise.resolve(null); // return early if the window is not ready or disposed
1999 2000 2001 2002
		}

		return this.isValidTargetWorkspacePath(window, path).then(isValid => {
			if (!isValid) {
B
Benjamin Pasero 已提交
2003
				return null; // return early if the workspace is not valid
2004
			}
M
Martin Aeschlimann 已提交
2005
			const workspaceIdentifier = getWorkspaceIdentifier(path);
M
Martin Aeschlimann 已提交
2006
			return this.doOpenWorkspace(window, workspaceIdentifier);
2007 2008 2009 2010
		});

	}

M
Martin Aeschlimann 已提交
2011
	private isValidTargetWorkspacePath(window: ICodeWindow, path?: URI): Promise<boolean> {
2012
		if (!path) {
B
Benjamin Pasero 已提交
2013
			return Promise.resolve(true);
2014 2015
		}

M
Martin Aeschlimann 已提交
2016
		if (window.openedWorkspace && isEqual(window.openedWorkspace.configPath, path)) {
B
Benjamin Pasero 已提交
2017
			return Promise.resolve(false); // window is already opened on a workspace with that path
2018 2019 2020
		}

		// Prevent overwriting a workspace that is currently opened in another window
M
Martin Aeschlimann 已提交
2021
		if (findWindowOnWorkspace(this.windowsMainService.getWindows(), getWorkspaceIdentifier(path))) {
2022 2023 2024 2025
			const options: Electron.MessageBoxOptions = {
				title: product.nameLong,
				type: 'info',
				buttons: [localize('ok', "OK")],
M
Martin Aeschlimann 已提交
2026
				message: localize('workspaceOpenedMessage', "Unable to save workspace '{0}'", resourcesBasename(path)),
2027
				detail: localize('workspaceOpenedDetail', "The workspace is already opened in another window. Please close that window first and then try again."),
2028 2029 2030
				noLink: true
			};

2031
			return this.windowsMainService.showMessageBox(options, this.windowsMainService.getFocusedWindow()).then(() => false);
2032 2033
		}

B
Benjamin Pasero 已提交
2034
		return Promise.resolve(true); // OK
2035 2036
	}

2037 2038
	private doOpenWorkspace(window: ICodeWindow, workspace: IWorkspaceIdentifier): IEnterWorkspaceResult {
		window.focus();
2039

2040
		// Register window for backups and migrate current backups over
2041
		let backupPath: string | undefined;
2042
		if (!window.config.extensionDevelopmentPath) {
2043
			backupPath = this.backupMainService.registerWorkspaceBackupSync({ workspace, remoteAuthority: window.remoteAuthority }, window.config.backupPath);
2044
		}
2045

2046 2047 2048 2049 2050
		// if the window was opened on an untitled workspace, delete it.
		if (window.openedWorkspace && this.workspacesMainService.isUntitledWorkspace(window.openedWorkspace)) {
			this.workspacesMainService.deleteUntitledWorkspaceSync(window.openedWorkspace);
		}

2051
		// Update window configuration properly based on transition to workspace
R
Rob Lourens 已提交
2052
		window.config.folderUri = undefined;
2053 2054 2055 2056
		window.config.workspace = workspace;
		window.config.backupPath = backupPath;

		return { workspace, backupPath };
2057 2058
	}

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

		this.windowsMainService.pickFileAndOpen({
R
Rob Lourens 已提交
2063
			windowId: window ? window.id : undefined,
2064 2065 2066 2067 2068
			dialogOptions: {
				buttonLabel: mnemonicButtonLabel(localize({ key: 'openWorkspace', comment: ['&& denotes a mnemonic'] }, "&&Open")),
				title: localize('openWorkspaceTitle', "Open Workspace"),
				filters: WORKSPACE_FILTER,
				properties: ['openFile'],
2069
				defaultPath: options.dialogOptions && options.dialogOptions.defaultPath
2070
			},
2071 2072 2073
			forceNewWindow: options.forceNewWindow,
			telemetryEventName: options.telemetryEventName,
			telemetryExtraData: options.telemetryExtraData
2074 2075
		});
	}
J
Johannes Rieken 已提交
2076
}