windows.ts 74.9 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';
B
Benjamin Pasero 已提交
27
import { IHistoryMainService } from 'vs/platform/history/common/history';
28
import { IProcessEnvironment, isMacintosh, isWindows } from 'vs/base/common/platform';
29
import { IWorkspacesMainService, IWorkspaceIdentifier, WORKSPACE_FILTER, ISingleFolderWorkspaceIdentifier, isSingleFolderWorkspaceIdentifier, hasWorkspaceFileExtension } from 'vs/platform/workspaces/common/workspaces';
B
Benjamin Pasero 已提交
30
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
31
import { mnemonicButtonLabel } from 'vs/base/common/labels';
J
Johannes Rieken 已提交
32
import { Schemas } from 'vs/base/common/network';
33
import { normalizeNFC } from 'vs/base/common/normalization';
34
import { URI } from 'vs/base/common/uri';
35
import { Queue } from 'vs/base/common/async';
B
Benjamin Pasero 已提交
36
import { exists } from 'vs/base/node/pfs';
37
import { getComparisonKey, isEqual, normalizePath, basename as resourcesBasename, originalFSPath, hasTrailingPathSeparator, removeTrailingPathSeparator } from 'vs/base/common/resources';
M
Martin Aeschlimann 已提交
38
import { getRemoteAuthority } from 'vs/platform/remote/common/remoteHosts';
M
Martin Aeschlimann 已提交
39
import { restoreWindowsState, WindowsStateStorageData, getWindowsStateStoreData } from 'vs/code/electron-main/windowsStateStorage';
E
Erich Gamma 已提交
40

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

interface IFolderPathToOpen {

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

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

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

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

interface IWorkspacePathToOpen {

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

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

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

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

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

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

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

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

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

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

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

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

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

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

J
Joao Moreno 已提交
179
	constructor(
B
Benjamin Pasero 已提交
180
		private readonly machineId: string,
181 182 183 184 185 186 187 188 189 190
		@ILogService private readonly logService: ILogService,
		@IStateService private readonly stateService: IStateService,
		@IEnvironmentService private readonly environmentService: IEnvironmentService,
		@ILifecycleService private readonly lifecycleService: ILifecycleService,
		@IBackupMainService private readonly backupMainService: IBackupMainService,
		@ITelemetryService private readonly telemetryService: ITelemetryService,
		@IConfigurationService private readonly configurationService: IConfigurationService,
		@IHistoryMainService private readonly historyMainService: IHistoryMainService,
		@IWorkspacesMainService private readonly workspacesMainService: IWorkspacesMainService,
		@IInstantiationService private readonly instantiationService: IInstantiationService
191
	) {
M
Martin Aeschlimann 已提交
192
		const windowsStateStoreData = this.stateService.getItem<WindowsStateStorageData>(WindowsManager.windowsStateStorageKey);
193 194

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

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

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

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

	private registerListeners(): void {
210

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

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

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

224 225 226 227 228 229 230 231 232 233 234
		// React to HC color scheme changes (Windows)
		if (isWindows) {
			systemPreferences.on('inverted-color-scheme-changed', () => {
				if (systemPreferences.isInvertedColorScheme()) {
					this.sendToAll('vscode:enterHighContrast');
				} else {
					this.sendToAll('vscode:leaveHighContrast');
				}
			});
		}

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

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

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

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

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

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

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

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

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

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

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

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

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

372 373 374 375 376 377
		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
378
		for (const path of pathsToOpen) {
379 380 381 382 383 384 385 386 387 388 389
			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) {
390
				if (!fileInputs) {
M
Martin Aeschlimann 已提交
391
					fileInputs = { filesToCreate: [], filesToOpen: [], filesToDiff: [], remoteAuthority: path.remoteAuthority };
392 393 394 395 396 397
				}
				if (!path.createFilePath) {
					fileInputs.filesToOpen.push(path);
				} else {
					fileInputs.filesToCreate.push(path);
				}
398 399 400 401
			} else if (path.backupPath) {
				emptyToRestore.push({ backupFolder: basename(path.backupPath), remoteAuthority: path.remoteAuthority });
			} else {
				emptyToOpen++;
402 403
			}
		}
404 405 406

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

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

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

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

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

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

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

442
			let focusLastActive = this.windowsState.lastActiveWindow && !openConfig.forceEmpty && !hasArgs(openConfig.cli._) && !hasArgs(openConfig.cli['file-uri']) && !hasArgs(openConfig.cli['folder-uri']) && !(openConfig.urisToOpen && openConfig.urisToOpen.length);
443 444
			let focusLastOpened = true;
			let focusLastWindow = true;
445

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

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

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

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

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

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

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

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

508 509 510
		return usedWindows;
	}

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

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

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

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

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

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

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

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

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

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

				// Window is empty
				else {

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

B
Benjamin Pasero 已提交
743 744 745 746 747 748 749 750 751 752 753 754 755
		const params: { filesToOpen?, filesToCreate?, filesToDiff?, filesToWait?, termProgram?} = {};
		if (fileInputs) {
			params.filesToOpen = fileInputs.filesToOpen;
			params.filesToCreate = fileInputs.filesToCreate;
			params.filesToDiff = fileInputs.filesToDiff;
			params.filesToWait = fileInputs.filesToWait;
		}

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

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

		return window;
758 759
	}

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

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

		return window;
	}

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

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

		return browserWindow;
	}

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

		return restoreWindows;
	}

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

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

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

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


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

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

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

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

1054
		let lineNumber, columnNumber: number | undefined;
1055

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

		// Existing window
		else {

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

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

		return window;
	}
B
Benjamin Pasero 已提交
1338

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

1341 1342 1343
		// Register window for backups
		if (!configuration.extensionDevelopmentPath) {
			if (configuration.workspace) {
1344
				configuration.backupPath = this.backupMainService.registerWorkspaceBackupSync({ workspace: configuration.workspace, remoteAuthority: configuration.remoteAuthority });
1345 1346 1347 1348 1349
			} else if (configuration.folderUri) {
				configuration.backupPath = this.backupMainService.registerFolderBackupSync(configuration.folderUri);
			} else {
				const backupFolder = options.emptyWindowBackupInfo && options.emptyWindowBackupInfo.backupFolder;
				configuration.backupPath = this.backupMainService.registerEmptyWindowBackupSync({ backupFolder, remoteAuthority: configuration.remoteAuthority });
E
Erich Gamma 已提交
1350
			}
1351
		}
1352

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

		return state;
	}

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

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

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

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

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

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

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

1521
		return result;
1522 1523
	}

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

B
Benjamin Pasero 已提交
1528
	focusLastActive(cli: ParsedArgs, context: OpenContext): ICodeWindow {
B
Benjamin Pasero 已提交
1529
		const lastActive = this.getLastActiveWindow();
E
Erich Gamma 已提交
1530
		if (lastActive) {
B
Benjamin Pasero 已提交
1531
			lastActive.focus();
1532 1533

			return lastActive;
E
Erich Gamma 已提交
1534 1535
		}

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

M
Matt Bierner 已提交
1540
	getLastActiveWindow(): ICodeWindow | undefined {
1541
		return getLastActiveWindow(WindowsManager.WINDOWS);
E
Erich Gamma 已提交
1542 1543
	}

1544
	getLastActiveWindowForAuthority(remoteAuthority: string | undefined): ICodeWindow | undefined {
M
Martin Aeschlimann 已提交
1545 1546 1547
		return getLastActiveWindow(WindowsManager.WINDOWS.filter(w => w.remoteAuthority === remoteAuthority));
	}

1548 1549
	openNewWindow(context: OpenContext, options?: INewWindowOptions): ICodeWindow[] {
		let cli = this.environmentService.args;
R
Rob Lourens 已提交
1550
		let remote = options && options.remoteAuthority || undefined;
M
Martin Aeschlimann 已提交
1551 1552 1553
		if (cli && (cli.remote !== remote)) {
			cli = { ...cli, remote };
		}
1554
		return this.open({ context, cli, forceNewWindow: true, forceEmpty: true });
E
Erich Gamma 已提交
1555 1556
	}

1557 1558 1559 1560
	openNewTabbedWindow(context: OpenContext): ICodeWindow[] {
		return this.open({ context, cli: this.environmentService.args, forceNewTabbedWindow: true, forceEmpty: true });
	}

J
Johannes Rieken 已提交
1561
	waitForWindowCloseOrLoad(windowId: number): Promise<void> {
B
Benjamin Pasero 已提交
1562
		return new Promise<void>(resolve => {
1563
			function handler(id: number) {
1564
				if (id === windowId) {
1565 1566 1567
					closeListener.dispose();
					loadListener.dispose();

1568
					resolve();
1569
				}
1570 1571 1572 1573
			}

			const closeListener = this.onWindowClose(id => handler(id));
			const loadListener = this.onWindowLoad(id => handler(id));
1574 1575 1576
		});
	}

B
Benjamin Pasero 已提交
1577
	sendToFocused(channel: string, ...args: any[]): void {
E
Erich Gamma 已提交
1578 1579 1580
		const focusedWindow = this.getFocusedWindow() || this.getLastActiveWindow();

		if (focusedWindow) {
1581
			focusedWindow.sendWhenReady(channel, ...args);
E
Erich Gamma 已提交
1582 1583 1584
		}
	}

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

1591
			w.sendWhenReady(channel, payload);
E
Erich Gamma 已提交
1592 1593 1594
		});
	}

M
Matt Bierner 已提交
1595
	getFocusedWindow(): ICodeWindow | undefined {
B
Benjamin Pasero 已提交
1596
		const win = BrowserWindow.getFocusedWindow();
E
Erich Gamma 已提交
1597 1598 1599 1600
		if (win) {
			return this.getWindowById(win.id);
		}

M
Matt Bierner 已提交
1601
		return undefined;
E
Erich Gamma 已提交
1602 1603
	}

M
Matt Bierner 已提交
1604
	getWindowById(windowId: number): ICodeWindow | undefined {
1605
		const res = WindowsManager.WINDOWS.filter(w => w.id === windowId);
E
Erich Gamma 已提交
1606 1607 1608 1609
		if (res && res.length === 1) {
			return res[0];
		}

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

B
Benjamin Pasero 已提交
1613
	getWindows(): ICodeWindow[] {
E
Erich Gamma 已提交
1614 1615 1616
		return WindowsManager.WINDOWS;
	}

B
Benjamin Pasero 已提交
1617
	getWindowCount(): number {
E
Erich Gamma 已提交
1618 1619 1620
		return WindowsManager.WINDOWS.length;
	}

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

1624 1625
		/* __GDPR__
			"windowerror" : {
K
kieferrm 已提交
1626
				"type" : { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth", "isMeasurement": true }
1627 1628 1629 1630
			}
		*/
		this.telemetryService.publicLog('windowerror', { type: error });

E
Erich Gamma 已提交
1631 1632
		// Unresponsive
		if (error === WindowError.UNRESPONSIVE) {
1633 1634 1635 1636 1637 1638 1639 1640 1641 1642 1643
			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
1644
			this.dialogs.showMessageBox({
B
Benjamin Pasero 已提交
1645
				title: product.nameLong,
E
Erich Gamma 已提交
1646
				type: 'warning',
1647
				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"))],
1648 1649
				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 已提交
1650
				noLink: true
1651 1652 1653 1654
			}, window).then(result => {
				if (!window.win) {
					return; // Return early if the window has been going down already
				}
1655

1656 1657 1658 1659 1660 1661 1662
				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 已提交
1663 1664 1665 1666
		}

		// Crashed
		else {
1667
			this.dialogs.showMessageBox({
B
Benjamin Pasero 已提交
1668
				title: product.nameLong,
E
Erich Gamma 已提交
1669
				type: 'warning',
1670
				buttons: [mnemonicButtonLabel(localize({ key: 'reopen', comment: ['&& denotes a mnemonic'] }, "&&Reopen")), mnemonicButtonLabel(localize({ key: 'close', comment: ['&& denotes a mnemonic'] }, "&&Close"))],
1671 1672
				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 已提交
1673
				noLink: true
1674 1675 1676 1677
			}, window).then(result => {
				if (!window.win) {
					return; // Return early if the window has been going down already
				}
1678

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

1689
	private onWindowClosed(win: ICodeWindow): void {
E
Erich Gamma 已提交
1690 1691 1692 1693 1694

		// Tell window
		win.dispose();

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

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

B
Benjamin Pasero 已提交
1703
	pickFileFolderAndOpen(options: INativeOpenDialogOptions): void {
B
Benjamin Pasero 已提交
1704
		this.doPickAndOpen(options, true /* pick folders */, true /* pick files */);
1705 1706
	}

B
Benjamin Pasero 已提交
1707
	pickFolderAndOpen(options: INativeOpenDialogOptions): void {
B
Benjamin Pasero 已提交
1708
		this.doPickAndOpen(options, true /* pick folders */, false /* pick files */);
1709 1710
	}

B
Benjamin Pasero 已提交
1711
	pickFileAndOpen(options: INativeOpenDialogOptions): void {
B
Benjamin Pasero 已提交
1712 1713 1714 1715 1716 1717 1718 1719 1720
		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;

1721 1722
		const dialogOptions: OpenDialogOptions = internalOptions.dialogOptions || Object.create(null);
		internalOptions.dialogOptions = dialogOptions;
B
Benjamin Pasero 已提交
1723 1724 1725 1726 1727 1728 1729 1730 1731 1732 1733 1734 1735

		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 已提交
1736
				// __GDPR__TODO__ classify event
B
Benjamin Pasero 已提交
1737 1738 1739 1740 1741 1742 1743 1744
				internalOptions.telemetryEventName = 'openFileFolder';
			} else if (pickFolders) {
				internalOptions.telemetryEventName = 'openFolder';
			} else {
				internalOptions.telemetryEventName = 'openFile';
			}
		}

1745 1746 1747
		this.dialogs.pickAndOpen(internalOptions);
	}

J
Johannes Rieken 已提交
1748
	showMessageBox(options: Electron.MessageBoxOptions, win?: ICodeWindow): Promise<IMessageBoxResult> {
1749 1750 1751
		return this.dialogs.showMessageBox(options, win);
	}

J
Johannes Rieken 已提交
1752
	showSaveDialog(options: Electron.SaveDialogOptions, win?: ICodeWindow): Promise<string> {
1753 1754 1755
		return this.dialogs.showSaveDialog(options, win);
	}

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

B
Benjamin Pasero 已提交
1760
	quit(): void {
B
Benjamin Pasero 已提交
1761 1762 1763

		// 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.
1764 1765 1766
		const window = this.getFocusedWindow();
		if (window && window.isExtensionDevelopmentHost && this.getWindowCount() > 1) {
			window.win.close();
B
Benjamin Pasero 已提交
1767 1768 1769 1770 1771 1772 1773 1774
		}

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

B
Benjamin Pasero 已提交
1778
interface IInternalNativeOpenDialogOptions extends INativeOpenDialogOptions {
B
Benjamin Pasero 已提交
1779 1780 1781 1782
	pickFolders?: boolean;
	pickFiles?: boolean;
}

1783
class Dialogs {
B
Benjamin Pasero 已提交
1784

1785
	private static readonly workingDirPickerStorageKey = 'pickerWorkingDir';
1786

1787 1788 1789
	private mapWindowToDialogQueue: Map<number, Queue<any>>;
	private noWindowDialogQueue: Queue<any>;

B
Benjamin Pasero 已提交
1790 1791 1792
	constructor(
		private environmentService: IEnvironmentService,
		private telemetryService: ITelemetryService,
B
Benjamin Pasero 已提交
1793
		private stateService: IStateService,
B
Benjamin Pasero 已提交
1794
		private windowsMainService: IWindowsMainService,
B
Benjamin Pasero 已提交
1795
	) {
1796 1797
		this.mapWindowToDialogQueue = new Map<number, Queue<any>>();
		this.noWindowDialogQueue = new Queue<any>();
B
Benjamin Pasero 已提交
1798 1799
	}

B
Benjamin Pasero 已提交
1800
	pickAndOpen(options: INativeOpenDialogOptions): void {
1801
		this.getFileOrFolderUris(options).then(paths => {
B
Benjamin Pasero 已提交
1802 1803 1804 1805
			const numberOfPaths = paths ? paths.length : 0;

			// Telemetry
			if (options.telemetryEventName) {
K
kieferrm 已提交
1806
				// __GDPR__TODO__ Dynamic event names and dynamic properties. Can not be registered statically.
B
Benjamin Pasero 已提交
1807 1808 1809 1810 1811 1812 1813 1814 1815
				this.telemetryService.publicLog(options.telemetryEventName, {
					...options.telemetryExtraData,
					outcome: numberOfPaths ? 'success' : 'canceled',
					numberOfPaths
				});
			}

			// Open
			if (numberOfPaths) {
1816 1817
				this.windowsMainService.open({
					context: OpenContext.DIALOG,
B
Benjamin Pasero 已提交
1818
					contextWindowId: options.windowId,
1819
					cli: this.environmentService.args,
S
Sandeep Somavarapu 已提交
1820
					urisToOpen: paths,
1821 1822 1823
					forceNewWindow: options.forceNewWindow,
					forceOpenWorkspaceAsFile: options.dialogOptions && !equals(options.dialogOptions.filters, WORKSPACE_FILTER)
				});
1824 1825 1826 1827
			}
		});
	}

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

B
Benjamin Pasero 已提交
1830
		// Ensure dialog options
M
Matt Bierner 已提交
1831 1832
		const dialogOptions = options.dialogOptions || Object.create(null);
		options.dialogOptions = dialogOptions;
B
Benjamin Pasero 已提交
1833 1834

		// Ensure defaultPath
M
Matt Bierner 已提交
1835 1836
		if (!dialogOptions.defaultPath) {
			dialogOptions.defaultPath = this.stateService.getItem<string>(Dialogs.workingDirPickerStorageKey);
1837 1838
		}

B
Benjamin Pasero 已提交
1839 1840
		// Ensure properties
		if (typeof options.pickFiles === 'boolean' || typeof options.pickFolders === 'boolean') {
M
Matt Bierner 已提交
1841
			dialogOptions.properties = undefined; // let it override based on the booleans
B
Benjamin Pasero 已提交
1842 1843

			if (options.pickFiles && options.pickFolders) {
M
Matt Bierner 已提交
1844
				dialogOptions.properties = ['multiSelections', 'openDirectory', 'openFile', 'createDirectory'];
B
Benjamin Pasero 已提交
1845 1846 1847
			}
		}

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

1852
		if (isMacintosh) {
M
Matt Bierner 已提交
1853
			dialogOptions.properties.push('treatPackageAsDirectory'); // always drill into .app files
1854 1855
		}

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

M
Matt Bierner 已提交
1859
		return this.showOpenDialog(dialogOptions, focusedWindow).then(paths => {
1860 1861 1862 1863 1864
			if (paths && paths.length > 0) {

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

1865 1866 1867 1868 1869 1870
				const result: IURIToOpen[] = [];
				for (const path of paths) {
					result.push({ uri: URI.file(path) });
				}

				return result;
1871 1872
			}

R
Rob Lourens 已提交
1873
			return undefined;
1874 1875 1876 1877 1878 1879 1880 1881 1882 1883 1884 1885 1886 1887 1888 1889 1890
		});
	}

	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 已提交
1891
	showMessageBox(options: Electron.MessageBoxOptions, window?: ICodeWindow): Promise<IMessageBoxResult> {
1892
		return this.getDialogQueue(window).queue(() => {
B
Benjamin Pasero 已提交
1893
			return new Promise(resolve => {
M
Matt Bierner 已提交
1894
				dialog.showMessageBox(window ? window.win : undefined!, options, (response: number, checkboxChecked: boolean) => {
B
Benjamin Pasero 已提交
1895
					resolve({ button: response, checkboxChecked });
B
Benjamin Pasero 已提交
1896
				});
1897 1898 1899 1900
			});
		});
	}

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

1903 1904 1905
		function normalizePath(path: string): string {
			if (path && isMacintosh) {
				path = normalizeNFC(path); // normalize paths returned from the OS
1906
			}
1907

1908 1909
			return path;
		}
1910

1911
		return this.getDialogQueue(window).queue(() => {
B
Benjamin Pasero 已提交
1912
			return new Promise(resolve => {
M
Matt Bierner 已提交
1913
				dialog.showSaveDialog(window ? window.win : undefined!, options, path => {
B
Benjamin Pasero 已提交
1914
					resolve(normalizePath(path));
B
Benjamin Pasero 已提交
1915
				});
1916 1917 1918 1919
			});
		});
	}

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

1922 1923 1924 1925 1926 1927
		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;
1928
		}
B
Benjamin Pasero 已提交
1929

1930
		return this.getDialogQueue(window).queue(() => {
B
Benjamin Pasero 已提交
1931
			return new Promise(resolve => {
B
Benjamin Pasero 已提交
1932 1933

				// Ensure the path exists (if provided)
B
Benjamin Pasero 已提交
1934
				let validatePathPromise: Promise<void> = Promise.resolve();
B
Benjamin Pasero 已提交
1935 1936 1937
				if (options.defaultPath) {
					validatePathPromise = exists(options.defaultPath).then(exists => {
						if (!exists) {
R
Rob Lourens 已提交
1938
							options.defaultPath = undefined;
B
Benjamin Pasero 已提交
1939 1940 1941 1942 1943 1944
						}
					});
				}

				// Show dialog and wrap as promise
				validatePathPromise.then(() => {
M
Matt Bierner 已提交
1945
					dialog.showOpenDialog(window ? window.win : undefined!, options, paths => {
B
Benjamin Pasero 已提交
1946
						resolve(normalizePaths(paths));
B
Benjamin Pasero 已提交
1947
					});
B
Benjamin Pasero 已提交
1948
				});
1949 1950
			});
		});
1951
	}
1952 1953 1954 1955 1956
}

class WorkspacesManager {

	constructor(
1957 1958 1959 1960
		private readonly workspacesMainService: IWorkspacesMainService,
		private readonly backupMainService: IBackupMainService,
		private readonly windowsMainService: IWindowsMainService,
	) { }
1961

M
Martin Aeschlimann 已提交
1962
	enterWorkspace(window: ICodeWindow, path: URI): Promise<IEnterWorkspaceResult | null> {
B
Benjamin Pasero 已提交
1963
		if (!window || !window.win || !window.isReady) {
B
Benjamin Pasero 已提交
1964
			return Promise.resolve(null); // return early if the window is not ready or disposed
1965 1966 1967 1968
		}

		return this.isValidTargetWorkspacePath(window, path).then(isValid => {
			if (!isValid) {
B
Benjamin Pasero 已提交
1969
				return null; // return early if the workspace is not valid
1970
			}
M
Martin Aeschlimann 已提交
1971 1972
			const workspaceIdentifier = this.workspacesMainService.getWorkspaceIdentifier(path);
			return this.doOpenWorkspace(window, workspaceIdentifier);
1973 1974 1975 1976
		});

	}

M
Martin Aeschlimann 已提交
1977
	private isValidTargetWorkspacePath(window: ICodeWindow, path?: URI): Promise<boolean> {
1978
		if (!path) {
B
Benjamin Pasero 已提交
1979
			return Promise.resolve(true);
1980 1981
		}

M
Martin Aeschlimann 已提交
1982
		if (window.openedWorkspace && isEqual(window.openedWorkspace.configPath, path)) {
B
Benjamin Pasero 已提交
1983
			return Promise.resolve(false); // window is already opened on a workspace with that path
1984 1985 1986
		}

		// Prevent overwriting a workspace that is currently opened in another window
M
Martin Aeschlimann 已提交
1987
		if (findWindowOnWorkspace(this.windowsMainService.getWindows(), this.workspacesMainService.getWorkspaceIdentifier(path))) {
1988 1989 1990 1991
			const options: Electron.MessageBoxOptions = {
				title: product.nameLong,
				type: 'info',
				buttons: [localize('ok', "OK")],
M
Martin Aeschlimann 已提交
1992
				message: localize('workspaceOpenedMessage', "Unable to save workspace '{0}'", resourcesBasename(path)),
1993
				detail: localize('workspaceOpenedDetail', "The workspace is already opened in another window. Please close that window first and then try again."),
1994 1995 1996
				noLink: true
			};

1997
			return this.windowsMainService.showMessageBox(options, this.windowsMainService.getFocusedWindow()).then(() => false);
1998 1999
		}

B
Benjamin Pasero 已提交
2000
		return Promise.resolve(true); // OK
2001 2002
	}

2003 2004
	private doOpenWorkspace(window: ICodeWindow, workspace: IWorkspaceIdentifier): IEnterWorkspaceResult {
		window.focus();
2005

2006
		// Register window for backups and migrate current backups over
2007
		let backupPath: string | undefined;
2008
		if (!window.config.extensionDevelopmentPath) {
2009
			backupPath = this.backupMainService.registerWorkspaceBackupSync({ workspace, remoteAuthority: window.remoteAuthority }, window.config.backupPath);
2010
		}
2011

2012 2013 2014 2015 2016
		// if the window was opened on an untitled workspace, delete it.
		if (window.openedWorkspace && this.workspacesMainService.isUntitledWorkspace(window.openedWorkspace)) {
			this.workspacesMainService.deleteUntitledWorkspaceSync(window.openedWorkspace);
		}

2017
		// Update window configuration properly based on transition to workspace
R
Rob Lourens 已提交
2018
		window.config.folderUri = undefined;
2019 2020 2021 2022
		window.config.workspace = workspace;
		window.config.backupPath = backupPath;

		return { workspace, backupPath };
2023 2024
	}

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

		this.windowsMainService.pickFileAndOpen({
R
Rob Lourens 已提交
2029
			windowId: window ? window.id : undefined,
2030 2031 2032 2033 2034
			dialogOptions: {
				buttonLabel: mnemonicButtonLabel(localize({ key: 'openWorkspace', comment: ['&& denotes a mnemonic'] }, "&&Open")),
				title: localize('openWorkspaceTitle', "Open Workspace"),
				filters: WORKSPACE_FILTER,
				properties: ['openFile'],
2035
				defaultPath: options.dialogOptions && options.dialogOptions.defaultPath
2036
			},
2037 2038 2039
			forceNewWindow: options.forceNewWindow,
			telemetryEventName: options.telemetryEventName,
			telemetryExtraData: options.telemetryExtraData
2040 2041
		});
	}
J
Johannes Rieken 已提交
2042
}