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

6
import { basename, normalize, join, dirname } from 'path';
7
import * as fs from 'fs';
B
Benjamin Pasero 已提交
8
import { localize } from 'vs/nls';
J
Joao Moreno 已提交
9
import * as arrays from 'vs/base/common/arrays';
10
import { assign, mixin, equals } from 'vs/base/common/objects';
11
import { IBackupMainService, IEmptyWindowBackupInfo } from 'vs/platform/backup/common/backup';
J
Joao Moreno 已提交
12
import { IEnvironmentService, ParsedArgs } from 'vs/platform/environment/common/environment';
B
Benjamin Pasero 已提交
13
import { IStateService } from 'vs/platform/state/common/state';
14
import { CodeWindow, defaultWindowState } from 'vs/code/electron-main/window';
M
Martin Aeschlimann 已提交
15
import { hasArgs, asArray } from 'vs/platform/environment/node/argv';
16
import { ipcMain as ipc, screen, BrowserWindow, dialog, systemPreferences, app } from 'electron';
B
Benjamin Pasero 已提交
17
import { IPathWithLineAndColumn, parseLineAndColumnAware } from 'vs/code/node/paths';
18
import { ILifecycleService, UnloadReason, IWindowUnloadEvent } 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, ReadyState, IPathsToWaitFor, IEnterWorkspaceResult, IMessageBoxResult, INewWindowOptions } from 'vs/platform/windows/common/windows';
22
import { getLastActiveWindow, findBestWindowOrFolderForFile, findWindowOnWorkspace, findWindowOnExtensionDevelopmentPath, findWindowOnWorkspaceOrFolderUri } from 'vs/code/node/windowsFinder';
M
Matt Bierner 已提交
23
import { Event as CommonEvent, Emitter } from 'vs/base/common/event';
24
import product from 'vs/platform/node/product';
B
Benjamin Pasero 已提交
25
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
26
import { IWindowsMainService, IOpenConfiguration, IWindowsCountChangedEvent, ICodeWindow, IWindowState as ISingleWindowState, WindowMode } from 'vs/platform/windows/electron-main/windows';
B
Benjamin Pasero 已提交
27
import { IHistoryMainService } from 'vs/platform/history/common/history';
B
Benjamin Pasero 已提交
28
import { IProcessEnvironment, isLinux, isMacintosh, isWindows } from 'vs/base/common/platform';
29
import { TPromise } from 'vs/base/common/winjs.base';
30
import { IWorkspacesMainService, IWorkspaceIdentifier, WORKSPACE_FILTER, IWorkspaceFolderCreationData, ISingleFolderWorkspaceIdentifier, isSingleFolderWorkspaceIdentifier } from 'vs/platform/workspaces/common/workspaces';
B
Benjamin Pasero 已提交
31
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
32
import { mnemonicButtonLabel } from 'vs/base/common/labels';
J
Johannes Rieken 已提交
33
import { Schemas } from 'vs/base/common/network';
34
import { normalizeNFC } from 'vs/base/common/normalization';
35
import { URI } from 'vs/base/common/uri';
36
import { Queue, timeout } from 'vs/base/common/async';
B
Benjamin Pasero 已提交
37
import { exists } from 'vs/base/node/pfs';
38
import { getComparisonKey, isEqual, normalizePath } from 'vs/base/common/resources';
39
import { endsWith } from 'vs/base/common/strings';
E
Erich Gamma 已提交
40 41 42 43 44 45

enum WindowError {
	UNRESPONSIVE,
	CRASHED
}

46 47 48 49
interface INewWindowState extends ISingleWindowState {
	hasDefaultState?: boolean;
}

50
interface IWindowState {
51
	workspace?: IWorkspaceIdentifier;
52
	folderUri?: URI;
53
	backupPath: string;
J
Joao Moreno 已提交
54
	uiState: ISingleWindowState;
E
Erich Gamma 已提交
55 56
}

57 58 59 60
interface IBackwardCompatibleWindowState extends IWindowState {
	folderPath?: string;
}

E
Erich Gamma 已提交
61 62 63
interface IWindowsState {
	lastActiveWindow?: IWindowState;
	lastPluginDevelopmentHostWindow?: IWindowState;
64
	openedWindows: IWindowState[];
E
Erich Gamma 已提交
65 66
}

67
type RestoreWindowsSetting = 'all' | 'folders' | 'one' | 'none';
68

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

73
	workspace?: IWorkspaceIdentifier;
74
	folderUri?: URI;
B
Benjamin Pasero 已提交
75 76 77

	initialStartup?: boolean;

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

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

84 85 86 87 88 89 90 91 92 93 94 95 96 97
	emptyWindowBackupInfo?: IEmptyWindowBackupInfo;
}

interface IPathParseOptions {
	ignoreFileNotFound?: boolean;
	gotoLineMode?: boolean;
	forceOpenWorkspaceAsFile?: boolean;
}

interface IFileInputs {
	filesToOpen: IPath[];
	filesToCreate: IPath[];
	filesToDiff: IPath[];
	filesToWait?: IPathsToWaitFor;
B
Benjamin Pasero 已提交
98 99
}

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

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

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

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

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

J
Joao Moreno 已提交
115
export class WindowsManager implements IWindowsMainService {
J
Joao Moreno 已提交
116

117
	_serviceBrand: any;
E
Erich Gamma 已提交
118

119
	private static readonly windowsStateStorageKey = 'windowsState';
E
Erich Gamma 已提交
120

121
	private static WINDOWS: ICodeWindow[] = [];
E
Erich Gamma 已提交
122

B
Benjamin Pasero 已提交
123
	private initialUserEnv: IProcessEnvironment;
124

E
Erich Gamma 已提交
125
	private windowsState: IWindowsState;
126
	private lastClosedWindowState: IWindowState;
E
Erich Gamma 已提交
127

128
	private dialogs: Dialogs;
129
	private workspacesManager: WorkspacesManager;
B
Benjamin Pasero 已提交
130

131 132
	private _onWindowReady = new Emitter<ICodeWindow>();
	onWindowReady: CommonEvent<ICodeWindow> = this._onWindowReady.event;
133 134 135 136

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

137 138 139
	private _onWindowLoad = new Emitter<number>();
	onWindowLoad: CommonEvent<number> = this._onWindowLoad.event;

140 141
	private _onActiveWindowChanged = new Emitter<ICodeWindow>();
	onActiveWindowChanged: CommonEvent<ICodeWindow> = this._onActiveWindowChanged.event;
142

143 144 145
	private _onWindowReload = new Emitter<number>();
	onWindowReload: CommonEvent<number> = this._onWindowReload.event;

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

J
Joao Moreno 已提交
149
	constructor(
B
Benjamin Pasero 已提交
150
		private readonly machineId: string,
J
Joao Moreno 已提交
151
		@ILogService private logService: ILogService,
B
Benjamin Pasero 已提交
152
		@IStateService private stateService: IStateService,
153
		@IEnvironmentService private environmentService: IEnvironmentService,
B
Benjamin Pasero 已提交
154
		@ILifecycleService private lifecycleService: ILifecycleService,
B
Benjamin Pasero 已提交
155
		@IBackupMainService private backupMainService: IBackupMainService,
B
Benjamin Pasero 已提交
156
		@ITelemetryService telemetryService: ITelemetryService,
157
		@IConfigurationService private configurationService: IConfigurationService,
B
Benjamin Pasero 已提交
158 159
		@IHistoryMainService private historyMainService: IHistoryMainService,
		@IWorkspacesMainService private workspacesMainService: IWorkspacesMainService,
160
		@IInstantiationService private instantiationService: IInstantiationService
161
	) {
162
		this.windowsState = this.getWindowsState();
163 164 165
		if (!Array.isArray(this.windowsState.openedWindows)) {
			this.windowsState.openedWindows = [];
		}
166

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

171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194
	private getWindowsState(): IWindowsState {
		const windowsState = this.stateService.getItem<IWindowsState>(WindowsManager.windowsStateStorageKey) || { openedWindows: [] };
		if (windowsState.lastActiveWindow) {
			windowsState.lastActiveWindow = this.revive(windowsState.lastActiveWindow);
		}
		if (windowsState.lastPluginDevelopmentHostWindow) {
			windowsState.lastPluginDevelopmentHostWindow = this.revive(windowsState.lastPluginDevelopmentHostWindow);
		}
		if (windowsState.openedWindows) {
			windowsState.openedWindows = windowsState.openedWindows.map(windowState => this.revive(windowState));
		}
		return windowsState;
	}

	private revive(windowState: IWindowState): IWindowState {
		if (windowState.folderUri) {
			windowState.folderUri = URI.revive(windowState.folderUri);
		}
		if ((<IBackwardCompatibleWindowState>windowState).folderPath) {
			windowState.folderUri = URI.file((<IBackwardCompatibleWindowState>windowState).folderPath);
		}
		return windowState;
	}

B
Benjamin Pasero 已提交
195
	ready(initialUserEnv: IProcessEnvironment): void {
196
		this.initialUserEnv = initialUserEnv;
197 198

		this.registerListeners();
E
Erich Gamma 已提交
199 200 201
	}

	private registerListeners(): void {
202

203 204 205 206 207 208 209
		// React to windows focus changes
		app.on('browser-window-focus', () => {
			setTimeout(() => {
				this._onActiveWindowChanged.fire(this.getLastActiveWindow());
			});
		});

210
		// React to workbench loaded events from windows
211
		ipc.on('vscode:workbenchLoaded', (event: any, windowId: number) => {
J
Joao Moreno 已提交
212
			this.logService.trace('IPC#vscode-workbenchLoaded');
E
Erich Gamma 已提交
213

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

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

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

234 235
		// Handle various lifecycle events around windows
		this.lifecycleService.onBeforeWindowUnload(e => this.onBeforeWindowUnload(e));
236
		this.lifecycleService.onBeforeWindowClose(win => this.onBeforeWindowClose(win as ICodeWindow));
237
		this.lifecycleService.onBeforeShutdown(() => this.onBeforeShutdown());
238 239 240 241 242 243 244 245
		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.
				this.lastClosedWindowState = void 0;
			}
		});
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
B
Benjamin Pasero 已提交
320
		this.stateService.setItem(WindowsManager.windowsStateStorageKey, 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 361 362 363 364
			backupPath: win.backupPath,
			uiState: win.serializeWindowState()
		};
	}

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

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

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

379 380 381 382 383 384 385 386 387 388 389 390 391 392
		// collect all file inputs
		let fileInputs: IFileInputs = void 0;
		for (const path of pathsToOpen) {
			if (path.fileUri) {
				if (!fileInputs) {
					fileInputs = { filesToCreate: [], filesToOpen: [], filesToDiff: [] };
				}
				if (!path.createFilePath) {
					fileInputs.filesToOpen.push(path);
				} else {
					fileInputs.filesToCreate.push(path);
				}
			}
		}
393 394 395

		// When run with --diff, take the files to open as files to diff
		// if there are exactly two files provided.
396 397 398 399
		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 已提交
400 401
		}

402
		// When run with --wait, make sure we keep the paths to wait for
403 404
		if (fileInputs && openConfig.cli.wait && openConfig.cli.waitMarkerFilePath) {
			fileInputs.filesToWait = { paths: [...fileInputs.filesToDiff, ...fileInputs.filesToOpen, ...fileInputs.filesToCreate], waitMarkerFilePath: openConfig.cli.waitMarkerFilePath };
405 406
		}

407 408 409
		//
		// These are windows to open to show workspaces
		//
B
Benjamin Pasero 已提交
410
		const workspacesToOpen = arrays.distinct(pathsToOpen.filter(win => !!win.workspace).map(win => win.workspace), workspace => workspace.id); // prevent duplicates
411 412 413 414

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

417
		//
418
		// These are windows to restore because of hot-exit or from previous session (only performed once on startup!)
419
		//
420
		let foldersToRestore: URI[] = [];
421
		let workspacesToRestore: IWorkspaceIdentifier[] = [];
422
		let emptyToRestore: IEmptyWindowBackupInfo[] = [];
B
Benjamin Pasero 已提交
423
		if (openConfig.initialStartup && !openConfig.cli.extensionDevelopmentPath && !openConfig.cli['disable-restore-windows']) {
424
			foldersToRestore = this.backupMainService.getFolderBackupPaths();
425

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

B
Benjamin Pasero 已提交
429
			emptyToRestore = this.backupMainService.getEmptyWindowBackupPaths();
430
			emptyToRestore.push(...pathsToOpen.filter(w => !w.workspace && !w.folderUri && w.backupPath).map(w => ({ backupFolder: basename(w.backupPath) }))); // add empty windows with backupPath
431
			emptyToRestore = arrays.distinct(emptyToRestore, info => info.backupFolder); // prevent duplicates
432
		}
433

434 435 436
		//
		// These are empty windows to open
		//
437
		const emptyToOpen = pathsToOpen.filter(win => !win.workspace && !win.folderUri && !win.fileUri && !win.backupPath).length;
438

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

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

M
Martin Aeschlimann 已提交
445
			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);
446 447
			let focusLastOpened = true;
			let focusLastWindow = true;
448

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

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

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

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

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

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

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

502
		// If we got started with --wait from the CLI, we need to signal to the outside when the window
503 504
		// used for the edit operation is closed or loaded to a different folder so that the waiting
		// process can continue. We do this by deleting the waitMarkerFilePath.
505
		if (openConfig.context === OpenContext.CLI && openConfig.cli.wait && openConfig.cli.waitMarkerFilePath && usedWindows.length === 1 && usedWindows[0]) {
506
			this.waitForWindowCloseOrLoad(usedWindows[0].id).then(() => fs.unlink(openConfig.cli.waitMarkerFilePath, error => void 0));
507 508
		}

509 510 511
		return usedWindows;
	}

512 513 514 515 516 517 518 519 520 521
	private validateOpenConfig(config: IOpenConfiguration): IOpenConfiguration {

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

		return config;
	}

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

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

538 539 540 541
		// Handle folders to add by looking for the last active workspace (not on initial startup)
		if (!openConfig.initialStartup && foldersToAdd.length > 0) {
			const lastActiveWindow = this.getLastActiveWindow();
			if (lastActiveWindow) {
542
				usedWindows.push(this.doAddFoldersToExistingWindow(lastActiveWindow, foldersToAdd));
543 544 545 546 547 548
			}

			// Reset because we handled them
			foldersToAdd = [];
		}

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

553
			// Find suitable window or folder path to open files in
554 555 556
			const fileToCheck = fileInputs.filesToOpen[0] || fileInputs.filesToCreate[0] || fileInputs.filesToDiff[0];
			const windows = WindowsManager.WINDOWS;

557
			let bestWindowOrFolder = findBestWindowOrFolderForFile({
558
				windows,
559 560 561
				newWindow: openFilesInNewWindow,
				reuseWindow: openConfig.forceReuseWindow,
				context: openConfig.context,
562
				fileUri: fileToCheck && fileToCheck.fileUri,
B
Benjamin Pasero 已提交
563
				workspaceResolver: workspace => this.workspacesMainService.resolveWorkspaceSync(workspace.configPath)
564
			});
B
Benjamin Pasero 已提交
565

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

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

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

				// Window is empty
				else {

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

					// Reset these because we handled them
586
					fileInputs = void 0;
587
				}
588 589 590
			}

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

601
				// Reset these because we handled them
602
				fileInputs = void 0;
E
Erich Gamma 已提交
603 604 605
			}
		}

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

			// Check for existing instances
			const windowsOnWorkspace = arrays.coalesce(allWorkspacesToOpen.map(workspaceToOpen => findWindowOnWorkspace(WindowsManager.WINDOWS, workspaceToOpen)));
			if (windowsOnWorkspace.length > 0) {
				const windowOnWorkspace = windowsOnWorkspace[0];
614
				const fileInputsForWindow = fileInputs;
615 616

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

				// Reset these because we handled them
620 621 622
				if (fileInputsForWindow) {
					fileInputs = void 0;
				}
623 624 625 626 627 628

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

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

633 634
				const fileInputsForWindow = fileInputs;

635
				// Do open folder
636
				usedWindows.push(this.doOpenFolderOrWorkspace(openConfig, { workspace: workspaceToOpen }, openFolderInNewWindow, fileInputsForWindow));
637 638

				// Reset these because we handled them
639 640 641
				if (fileInputsForWindow) {
					fileInputs = void 0;
				}
642 643 644 645 646

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

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

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

			// Check for existing instances
653
			const windowsOnFolderPath = arrays.coalesce(allFoldersToOpen.map(folderToOpen => findWindowOnWorkspace(WindowsManager.WINDOWS, folderToOpen)));
654
			if (windowsOnFolderPath.length > 0) {
655
				const windowOnFolderPath = windowsOnFolderPath[0];
656
				const fileInputsForWindow = fileInputs;
E
Erich Gamma 已提交
657

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

E
Erich Gamma 已提交
661
				// Reset these because we handled them
662 663 664
				if (fileInputsForWindow) {
					fileInputs = void 0;
				}
E
Erich Gamma 已提交
665

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

			// Open remaining ones
670
			allFoldersToOpen.forEach(folderToOpen => {
671

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

676 677
				const fileInputsForWindow = fileInputs;

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

				// Reset these because we handled them
682 683 684
				if (fileInputsForWindow) {
					fileInputs = void 0;
				}
E
Erich Gamma 已提交
685

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

690
		// Handle empty to restore
691
		if (emptyToRestore.length > 0) {
692 693 694
			emptyToRestore.forEach(emptyWindowBackupInfo => {
				const fileInputsForWindow = fileInputs;

B
Benjamin Pasero 已提交
695
				usedWindows.push(this.openInBrowserWindow({
B
Benjamin Pasero 已提交
696 697 698
					userEnv: openConfig.userEnv,
					cli: openConfig.cli,
					initialStartup: openConfig.initialStartup,
699
					fileInputs: fileInputsForWindow,
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 707 708
				if (fileInputsForWindow) {
					fileInputs = void 0;
				}
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++;
			}
719
			for (let i = 0; i < emptyToOpen; i++) {
B
Benjamin Pasero 已提交
720
				usedWindows.push(this.openInBrowserWindow({
B
Benjamin Pasero 已提交
721 722 723
					userEnv: openConfig.userEnv,
					cli: openConfig.cli,
					initialStartup: openConfig.initialStartup,
724
					forceNewWindow: openFolderInNewWindow,
725 726
					forceNewTabbedWindow: openConfig.forceNewTabbedWindow,
					fileInputs
B
Benjamin Pasero 已提交
727
				}));
E
Erich Gamma 已提交
728

729 730
				// Reset these because we handled them
				fileInputs = void 0;
731
				openFolderInNewWindow = true; // any other window to open must open in new window then
732 733
			}
		}
E
Erich Gamma 已提交
734

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

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

		window.ready().then(readyWindow => {
742 743 744 745 746 747 748 749 750 751 752
			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'];
			}
			readyWindow.send('vscode:openFiles', params);
753
		});
B
Benjamin Pasero 已提交
754 755

		return window;
756 757
	}

758
	private doAddFoldersToExistingWindow(window: ICodeWindow, foldersToAdd: URI[]): ICodeWindow {
759 760 761 762 763 764 765 766 767
		window.focus(); // make sure window has focus

		window.ready().then(readyWindow => {
			readyWindow.send('vscode:addFolders', { foldersToAdd });
		});

		return window;
	}

768
	private doOpenFolderOrWorkspace(openConfig: IOpenConfiguration, folderOrWorkspace: IPathToOpen, forceNewWindow: boolean, fileInputs: IFileInputs, 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,
B
Benjamin Pasero 已提交
780
			forceNewWindow,
781
			forceNewTabbedWindow: openConfig.forceNewTabbedWindow,
782
			windowToUse
783 784 785 786 787
		});

		return browserWindow;
	}

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

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

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

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

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

814 815
		// Convert multiple folders into workspace (if opened via API or CLI)
		// This will ensure to open these folders in one window instead of multiple
816 817
		// If we are in addMode, we should not do this because in that case all
		// folders should be added to the existing window.
818
		if (!openConfig.addMode && isCommandLineOrAPICall) {
819
			const foldersToOpen = windowsToOpen.filter(path => !!path.folderUri);
820
			if (foldersToOpen.length > 1) {
821
				const workspace = this.workspacesMainService.createWorkspaceSync(foldersToOpen.map(folder => ({ uri: folder.folderUri })));
822 823 824

				// Add workspace and remove folders thereby
				windowsToOpen.push({ workspace });
825
				windowsToOpen = windowsToOpen.filter(path => !path.folderUri);
826 827 828
			}
		}

829
		return windowsToOpen;
E
Erich Gamma 已提交
830 831
	}

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

			const path = this.parseUri(pathToOpen, openConfig.forceOpenWorkspaceAsFile, parseOptions);
			if (path) {
				pathsToOpen.push(path);
			} else {
				// Warn about the invalid URI or path
846

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

864
				this.dialogs.showMessageBox(options, this.getFocusedWindow());
865
			}
M
Martin Aeschlimann 已提交
866
		}
867 868 869 870
		return pathsToOpen;
	}

	private doExtractPathsFromCLI(cli: ParsedArgs): IPath[] {
871
		const pathsToOpen = [];
872
		const parseOptions: IPathParseOptions = { ignoreFileNotFound: true, gotoLineMode: cli.goto };
873 874

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

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

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

M
Martin Aeschlimann 已提交
901
		if (pathsToOpen.length) {
902
			return pathsToOpen;
B
Benjamin Pasero 已提交
903 904 905 906 907 908
		}

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

B
Benjamin Pasero 已提交
909
	private doGetWindowsFromLastSession(): IPathToOpen[] {
910
		const restoreWindows = this.getRestoreWindowsSetting();
B
Benjamin Pasero 已提交
911

912
		switch (restoreWindows) {
B
Benjamin Pasero 已提交
913

914
			// none: we always open an empty window
915 916
			case 'none':
				return [Object.create(null)];
B
Benjamin Pasero 已提交
917

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

932 933 934 935 936 937 938 939 940 941 942 943 944 945 946
				const windowsToOpen: IPathToOpen[] = [];
				for (const openedWindow of openedWindows) {
					if (openedWindow.workspace) { // Workspaces
						const pathToOpen = this.parsePath(openedWindow.workspace.configPath);
						if (pathToOpen && pathToOpen.workspace) {
							windowsToOpen.push(pathToOpen);
						}
					} else if (openedWindow.folderUri) { // Folders
						const pathToOpen = this.parseUri(openedWindow.folderUri, false);
						if (pathToOpen && pathToOpen.folderUri) {
							windowsToOpen.push(pathToOpen);
						}
					} else if (restoreWindows !== 'folders' && openedWindow.backupPath) { // Windows that were Empty
						windowsToOpen.push({ backupPath: openedWindow.backupPath });
					}
947 948 949 950 951 952 953
				}

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

				break;
B
Benjamin Pasero 已提交
954
		}
E
Erich Gamma 已提交
955

956
		// Always fallback to empty window
B
Benjamin Pasero 已提交
957
		return [Object.create(null)];
E
Erich Gamma 已提交
958 959
	}

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

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

		return restoreWindows;
	}

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

990
	private parseUri(uri: URI, isFile: boolean, options?: IPathParseOptions): IPathToOpen {
M
Martin Aeschlimann 已提交
991
		if (!uri || !uri.scheme) {
992 993
			return null;
		}
M
Martin Aeschlimann 已提交
994 995
		if (uri.scheme === Schemas.file) {
			return this.parsePath(uri.fsPath, options);
996
		}
997 998 999 1000 1001
		// normalize URI
		uri = normalizePath(uri);
		if (endsWith(uri.path, '/')) {
			uri = uri.with({ path: uri.path.substr(0, uri.path.length - 1) });
		}
1002
		if (isFile) {
1003 1004 1005 1006 1007 1008 1009 1010
			if (options && options.gotoLineMode) {
				const parsedPath = parseLineAndColumnAware(uri.path);
				return {
					fileUri: uri.with({ path: parsedPath.path }),
					lineNumber: parsedPath.line,
					columnNumber: parsedPath.column
				};
			}
1011
			return {
M
Martin Aeschlimann 已提交
1012
				fileUri: uri
1013 1014
			};
		}
1015
		return {
M
Martin Aeschlimann 已提交
1016
			folderUri: uri
1017 1018 1019
		};
	}

1020
	private parsePath(anyPath: string, options?: IPathParseOptions): IPathToOpen {
E
Erich Gamma 已提交
1021 1022 1023 1024
		if (!anyPath) {
			return null;
		}

1025
		let parsedPath: IPathWithLineAndColumn;
1026 1027 1028

		const gotoLineMode = options && options.gotoLineMode;
		if (options && options.gotoLineMode) {
J
Joao Moreno 已提交
1029
			parsedPath = parseLineAndColumnAware(anyPath);
E
Erich Gamma 已提交
1030 1031 1032
			anyPath = parsedPath.path;
		}

1033
		const candidate = normalize(anyPath);
E
Erich Gamma 已提交
1034
		try {
B
Benjamin Pasero 已提交
1035
			const candidateStat = fs.statSync(candidate);
E
Erich Gamma 已提交
1036
			if (candidateStat) {
1037
				if (candidateStat.isFile()) {
1038

1039 1040
					// Workspace (unless disabled via flag)
					if (!options || !options.forceOpenWorkspaceAsFile) {
B
Benjamin Pasero 已提交
1041
						const workspace = this.workspacesMainService.resolveWorkspaceSync(candidate);
1042
						if (workspace) {
1043
							return { workspace: { id: workspace.id, configPath: workspace.configPath } };
1044
						}
1045 1046 1047
					}

					// File
1048
					return {
1049
						fileUri: URI.file(candidate),
E
Erich Gamma 已提交
1050
						lineNumber: gotoLineMode ? parsedPath.line : void 0,
1051
						columnNumber: gotoLineMode ? parsedPath.column : void 0
1052 1053 1054
					};
				}

1055 1056 1057 1058 1059
				// 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 {
1060
						folderUri: URI.file(candidate)
1061 1062
					};
				}
E
Erich Gamma 已提交
1063 1064
			}
		} catch (error) {
1065 1066
			const fileUri = URI.file(candidate);
			this.historyMainService.removeFromRecentlyOpened([fileUri]); // since file does not seem to exist anymore, remove from recent
1067

1068
			if (options && options.ignoreFileNotFound) {
1069
				return { fileUri, createFilePath: true }; // assume this is a file that does not yet exist
E
Erich Gamma 已提交
1070 1071 1072 1073 1074 1075
			}
		}

		return null;
	}

B
Benjamin Pasero 已提交
1076 1077 1078
	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
1079
		const windowConfig = this.configurationService.getValue<IWindowSettings>('window');
1080 1081 1082
		const openFolderInNewWindowConfig = (windowConfig && windowConfig.openFoldersInNewWindow) || 'default' /* default */;
		const openFilesInNewWindowConfig = (windowConfig && windowConfig.openFilesInNewWindow) || 'off' /* default */;

B
Benjamin Pasero 已提交
1083
		let openFolderInNewWindow = (openConfig.preferNewWindow || openConfig.forceNewWindow) && !openConfig.forceReuseWindow;
1084 1085
		if (!openConfig.forceNewWindow && !openConfig.forceReuseWindow && (openFolderInNewWindowConfig === 'on' || openFolderInNewWindowConfig === 'off')) {
			openFolderInNewWindow = (openFolderInNewWindowConfig === 'on');
B
Benjamin Pasero 已提交
1086 1087 1088 1089 1090 1091 1092
		}

		// 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)
		let openFilesInNewWindow: boolean;
		if (openConfig.forceNewWindow || openConfig.forceReuseWindow) {
			openFilesInNewWindow = openConfig.forceNewWindow && !openConfig.forceReuseWindow;
		} else {
1093 1094 1095 1096 1097 1098 1099 1100 1101 1102 1103 1104 1105

			// 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 已提交
1106 1107
			}

1108
			// finally check for overrides of default
1109 1110
			if (!openConfig.cli.extensionDevelopmentPath && (openFilesInNewWindowConfig === 'on' || openFilesInNewWindowConfig === 'off')) {
				openFilesInNewWindow = (openFilesInNewWindowConfig === 'on');
B
Benjamin Pasero 已提交
1111 1112 1113 1114 1115 1116
			}
		}

		return { openFolderInNewWindow, openFilesInNewWindow };
	}

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

B
Benjamin Pasero 已提交
1119 1120 1121
		// 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.
1122 1123 1124 1125
		const existingWindow = findWindowOnExtensionDevelopmentPath(WindowsManager.WINDOWS, openConfig.cli.extensionDevelopmentPath);
		if (existingWindow) {
			this.reload(existingWindow, openConfig.cli);
			existingWindow.focus(); // make sure it gets focus and is restored
E
Erich Gamma 已提交
1126

B
Benjamin Pasero 已提交
1127 1128
			return;
		}
1129 1130 1131
		let folderUris = asArray(openConfig.cli['folder-uri']);
		let fileUris = asArray(openConfig.cli['file-uri']);
		let cliArgs = openConfig.cli._;
E
Erich Gamma 已提交
1132

1133
		// Fill in previously opened workspace unless an explicit path is provided and we are not unit testing
1134
		if (!cliArgs.length && !folderUris.length && !fileUris.length && !openConfig.cli.extensionTestsPath) {
1135
			const extensionDevelopmentWindowState = this.windowsState.lastPluginDevelopmentHostWindow;
1136
			const workspaceToOpen = extensionDevelopmentWindowState && (extensionDevelopmentWindowState.workspace || extensionDevelopmentWindowState.folderUri);
1137
			if (workspaceToOpen) {
1138
				if (isSingleFolderWorkspaceIdentifier(workspaceToOpen)) {
1139
					if (workspaceToOpen.scheme === Schemas.file) {
1140
						cliArgs = [workspaceToOpen.fsPath];
1141
					} else {
1142
						folderUris = [workspaceToOpen.toString()];
1143 1144
					}
				} else {
1145
					cliArgs = [workspaceToOpen.configPath];
1146
				}
E
Erich Gamma 已提交
1147 1148 1149
			}
		}

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

M
Martin Aeschlimann 已提交
1155
		if (folderUris.length && folderUris.some(uri => !!findWindowOnWorkspaceOrFolderUri(WindowsManager.WINDOWS, this.argToUri(uri)))) {
1156 1157 1158
			folderUris = [];
		}

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

1163 1164 1165 1166
		openConfig.cli._ = cliArgs;
		openConfig.cli['folder-uri'] = folderUris;
		openConfig.cli['file-uri'] = fileUris;

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

1171
	private openInBrowserWindow(options: IOpenBrowserWindowOptions): ICodeWindow {
1172

B
Benjamin Pasero 已提交
1173 1174 1175
		// Build IWindowConfiguration from config and options
		const configuration: IWindowConfiguration = mixin({}, options.cli); // inherit all properties from CLI
		configuration.appRoot = this.environmentService.appRoot;
1176
		configuration.machineId = this.machineId;
1177
		configuration.nodeCachedDataDir = this.environmentService.nodeCachedDataDir;
1178
		configuration.mainPid = process.pid;
B
Benjamin Pasero 已提交
1179 1180 1181
		configuration.execPath = process.execPath;
		configuration.userEnv = assign({}, this.initialUserEnv, options.userEnv || {});
		configuration.isInitialStartup = options.initialStartup;
1182
		configuration.workspace = options.workspace;
1183
		configuration.folderUri = options.folderUri;
1184 1185 1186 1187 1188 1189 1190 1191

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

1193
		// if we know the backup folder upfront (for empty windows to restore), we can set it
1194
		// directly here which helps for restoring UI state associated with that window.
B
Benjamin Pasero 已提交
1195
		// For all other cases we first call into registerEmptyWindowBackupSync() to set it before
1196
		// loading the window.
1197 1198
		if (options.emptyWindowBackupInfo) {
			configuration.backupPath = join(this.environmentService.backupHome, options.emptyWindowBackupInfo.backupFolder);
1199 1200
		}

1201
		let window: ICodeWindow;
1202
		if (!options.forceNewWindow && !options.forceNewTabbedWindow) {
1203 1204 1205
			window = options.windowToUse || this.getLastActiveWindow();
			if (window) {
				window.focus();
E
Erich Gamma 已提交
1206 1207 1208 1209
			}
		}

		// New window
1210
		if (!window) {
1211
			const windowConfig = this.configurationService.getValue<IWindowSettings>('window');
1212 1213 1214 1215 1216 1217 1218 1219 1220 1221
			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 {
1222
				allowFullscreen = this.lifecycleService.wasRestarted || (windowConfig && windowConfig.restoreFullscreen);
1223 1224 1225 1226 1227
			}

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

1229
			// Create the window
1230
			window = this.instantiationService.createInstance(CodeWindow, {
1231
				state,
1232
				extensionDevelopmentPath: configuration.extensionDevelopmentPath,
1233
				isExtensionTestHost: !!configuration.extensionTestsPath
1234
			});
1235

1236 1237 1238 1239 1240 1241 1242 1243
			// Add as window tab if configured (macOS only)
			if (options.forceNewTabbedWindow) {
				const activeWindow = this.getLastActiveWindow();
				if (activeWindow) {
					activeWindow.addTabbedWindow(window);
				}
			}

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

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

E
Erich Gamma 已提交
1250
			// Window Events
1251 1252 1253 1254 1255
			window.win.webContents.removeAllListeners('devtools-reload-page'); // remove built in listener so we can handle this on our own
			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 已提交
1256 1257

			// Lifecycle
1258
			this.lifecycleService.registerWindow(window);
E
Erich Gamma 已提交
1259 1260 1261 1262 1263 1264
		}

		// Existing window
		else {

			// Some configuration things get inherited if the window is being reused and we are
B
Benjamin Pasero 已提交
1265
			// in extension development host mode. These options are all development related.
1266
			const currentWindowConfig = window.config;
A
Alex Dima 已提交
1267 1268
			if (!configuration.extensionDevelopmentPath && currentWindowConfig && !!currentWindowConfig.extensionDevelopmentPath) {
				configuration.extensionDevelopmentPath = currentWindowConfig.extensionDevelopmentPath;
B
Benjamin Pasero 已提交
1269
				configuration.verbose = currentWindowConfig.verbose;
1270
				configuration.debugBrkPluginHost = currentWindowConfig.debugBrkPluginHost;
1271
				configuration.debugId = currentWindowConfig.debugId;
1272
				configuration.debugPluginHost = currentWindowConfig.debugPluginHost;
1273
				configuration['extensions-dir'] = currentWindowConfig['extensions-dir'];
E
Erich Gamma 已提交
1274 1275 1276 1277
			}
		}

		// Only load when the window has not vetoed this
1278
		this.lifecycleService.unload(window, UnloadReason.LOAD).then(veto => {
E
Erich Gamma 已提交
1279 1280
			if (!veto) {

B
Benjamin Pasero 已提交
1281 1282
				// Register window for backups
				if (!configuration.extensionDevelopmentPath) {
1283
					if (configuration.workspace) {
B
Benjamin Pasero 已提交
1284
						configuration.backupPath = this.backupMainService.registerWorkspaceBackupSync(configuration.workspace);
1285
					} else if (configuration.folderUri) {
1286
						configuration.backupPath = this.backupMainService.registerFolderBackupSync(configuration.folderUri);
B
Benjamin Pasero 已提交
1287
					} else {
1288 1289
						const backupFolder = options.emptyWindowBackupInfo && options.emptyWindowBackupInfo.backupFolder;
						configuration.backupPath = this.backupMainService.registerEmptyWindowBackupSync({ backupFolder });
B
Benjamin Pasero 已提交
1290
					}
B
Benjamin Pasero 已提交
1291 1292
				}

E
Erich Gamma 已提交
1293
				// Load it
1294
				window.load(configuration);
1295 1296 1297

				// Signal event
				this._onWindowLoad.fire(window.id);
E
Erich Gamma 已提交
1298 1299
			}
		});
1300

1301
		return window;
E
Erich Gamma 已提交
1302 1303
	}

1304
	private getNewWindowState(configuration: IWindowConfiguration): INewWindowState {
1305
		const lastActive = this.getLastActiveWindow();
E
Erich Gamma 已提交
1306

1307 1308
		// Restore state unless we are running extension tests
		if (!configuration.extensionTestsPath) {
E
Erich Gamma 已提交
1309

1310 1311 1312
			// extension development host Window - load from stored settings if any
			if (!!configuration.extensionDevelopmentPath && this.windowsState.lastPluginDevelopmentHostWindow) {
				return this.windowsState.lastPluginDevelopmentHostWindow.uiState;
1313 1314
			}

1315 1316 1317 1318 1319 1320 1321 1322 1323
			// Known Workspace - load from stored settings
			if (configuration.workspace) {
				const stateForWorkspace = this.windowsState.openedWindows.filter(o => o.workspace && o.workspace.id === configuration.workspace.id).map(o => o.uiState);
				if (stateForWorkspace.length) {
					return stateForWorkspace[0];
				}
			}

			// Known Folder - load from stored settings
1324
			if (configuration.folderUri) {
1325
				const stateForFolder = this.windowsState.openedWindows.filter(o => o.folderUri && isEqual(o.folderUri, configuration.folderUri)).map(o => o.uiState);
1326 1327 1328
				if (stateForFolder.length) {
					return stateForFolder[0];
				}
1329 1330
			}

1331 1332 1333 1334 1335 1336
			// 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 已提交
1337 1338
			}

1339 1340 1341 1342 1343
			// First Window
			const lastActiveState = this.lastClosedWindowState || this.windowsState.lastActiveWindow;
			if (!lastActive && lastActiveState) {
				return lastActiveState.uiState;
			}
E
Erich Gamma 已提交
1344 1345 1346 1347 1348 1349 1350
		}

		//
		// 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
1351
		let displayToUse: Electron.Display;
B
Benjamin Pasero 已提交
1352
		const displays = screen.getAllDisplays();
E
Erich Gamma 已提交
1353 1354 1355 1356 1357 1358 1359 1360 1361 1362

		// 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 已提交
1363
			if (isMacintosh) {
B
Benjamin Pasero 已提交
1364
				const cursorPoint = screen.getCursorScreenPoint();
E
Erich Gamma 已提交
1365 1366 1367 1368 1369 1370 1371 1372
				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());
			}

1373
			// fallback to primary display or first display
E
Erich Gamma 已提交
1374
			if (!displayToUse) {
1375
				displayToUse = screen.getPrimaryDisplay() || displays[0];
E
Erich Gamma 已提交
1376 1377 1378
			}
		}

1379 1380 1381
		// 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.
1382
		let state = defaultWindowState() as INewWindowState;
1383 1384
		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 已提交
1385

1386
		// Check for newWindowDimensions setting and adjust accordingly
1387
		const windowConfig = this.configurationService.getValue<IWindowSettings>('window');
1388 1389 1390 1391 1392 1393 1394 1395 1396
		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 已提交
1397 1398 1399 1400 1401 1402 1403
				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;
				}

1404 1405 1406 1407 1408 1409 1410 1411
				ensureNoOverlap = false;
			}
		}

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

1412 1413
		state.hasDefaultState = true; // flag as default state

1414
		return state;
E
Erich Gamma 已提交
1415 1416
	}

J
Joao Moreno 已提交
1417
	private ensureNoOverlap(state: ISingleWindowState): ISingleWindowState {
E
Erich Gamma 已提交
1418 1419 1420 1421
		if (WindowsManager.WINDOWS.length === 0) {
			return state;
		}

1422 1423
		const existingWindowBounds = WindowsManager.WINDOWS.map(win => win.getBounds());
		while (existingWindowBounds.some(b => b.x === state.x || b.y === state.y)) {
E
Erich Gamma 已提交
1424 1425 1426 1427 1428 1429 1430
			state.x += 30;
			state.y += 30;
		}

		return state;
	}

B
Benjamin Pasero 已提交
1431
	reload(win: ICodeWindow, cli?: ParsedArgs): void {
B
Benjamin Pasero 已提交
1432 1433

		// Only reload when the window has not vetoed this
1434
		this.lifecycleService.unload(win, UnloadReason.RELOAD).then(veto => {
B
Benjamin Pasero 已提交
1435
			if (!veto) {
1436
				win.reload(void 0, cli);
B
Benjamin Pasero 已提交
1437 1438 1439 1440 1441 1442 1443

				// Emit
				this._onWindowReload.fire(win.id);
			}
		});
	}

B
Benjamin Pasero 已提交
1444
	closeWorkspace(win: ICodeWindow): void {
1445 1446 1447 1448 1449 1450
		this.openInBrowserWindow({
			cli: this.environmentService.args,
			windowToUse: win
		});
	}

B
Benjamin Pasero 已提交
1451
	saveAndEnterWorkspace(win: ICodeWindow, path: string): TPromise<IEnterWorkspaceResult> {
1452
		return this.workspacesManager.saveAndEnterWorkspace(win, path).then(result => this.doEnterWorkspace(win, result));
1453
	}
1454

1455 1456 1457 1458
	enterWorkspace(win: ICodeWindow, path: string): TPromise<IEnterWorkspaceResult> {
		return this.workspacesManager.enterWorkspace(win, path).then(result => this.doEnterWorkspace(win, result));
	}

B
Benjamin Pasero 已提交
1459
	createAndEnterWorkspace(win: ICodeWindow, folders?: IWorkspaceFolderCreationData[], path?: string): TPromise<IEnterWorkspaceResult> {
1460
		return this.workspacesManager.createAndEnterWorkspace(win, folders, path).then(result => this.doEnterWorkspace(win, result));
1461
	}
1462

1463
	private doEnterWorkspace(win: ICodeWindow, result: IEnterWorkspaceResult): IEnterWorkspaceResult {
1464

1465
		// Mark as recently opened
B
Benjamin Pasero 已提交
1466
		this.historyMainService.addRecentlyOpened([result.workspace], []);
1467

1468 1469 1470
		// Trigger Eevent to indicate load of workspace into window
		this._onWindowReady.fire(win);

1471
		return result;
1472 1473
	}

B
Benjamin Pasero 已提交
1474
	pickWorkspaceAndOpen(options: INativeOpenDialogOptions): void {
1475
		this.workspacesManager.pickWorkspaceAndOpen(options);
1476 1477 1478
	}

	private onBeforeWindowUnload(e: IWindowUnloadEvent): void {
1479 1480
		const windowClosing = (e.reason === UnloadReason.CLOSE);
		const windowLoading = (e.reason === UnloadReason.LOAD);
1481 1482 1483 1484 1485
		if (!windowClosing && !windowLoading) {
			return; // only interested when window is closing or loading
		}

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

1490
		if (e.window.config && !!e.window.config.extensionDevelopmentPath) {
1491 1492 1493 1494
			// do not ask to save workspace when doing extension development
			// but still delete it.
			this.workspacesMainService.deleteUntitledWorkspaceSync(workspace);
			return;
1495 1496
		}

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

1501
		// Handle untitled workspaces with prompt as needed
B
Benjamin Pasero 已提交
1502 1503 1504 1505 1506 1507 1508 1509 1510
		e.veto(this.workspacesManager.promptToSaveUntitledWorkspace(this.getWindowById(e.window.id), workspace).then(veto => {
			if (veto) {
				return veto;
			}

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

B
Benjamin Pasero 已提交
1515
	focusLastActive(cli: ParsedArgs, context: OpenContext): ICodeWindow {
B
Benjamin Pasero 已提交
1516
		const lastActive = this.getLastActiveWindow();
E
Erich Gamma 已提交
1517
		if (lastActive) {
B
Benjamin Pasero 已提交
1518
			lastActive.focus();
1519 1520

			return lastActive;
E
Erich Gamma 已提交
1521 1522
		}

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

B
Benjamin Pasero 已提交
1527
	getLastActiveWindow(): ICodeWindow {
1528
		return getLastActiveWindow(WindowsManager.WINDOWS);
E
Erich Gamma 已提交
1529 1530
	}

1531 1532 1533
	openNewWindow(context: OpenContext, options?: INewWindowOptions): ICodeWindow[] {
		let cli = this.environmentService.args;
		return this.open({ context, cli, forceNewWindow: true, forceEmpty: true });
E
Erich Gamma 已提交
1534 1535
	}

1536 1537 1538 1539
	openNewTabbedWindow(context: OpenContext): ICodeWindow[] {
		return this.open({ context, cli: this.environmentService.args, forceNewTabbedWindow: true, forceEmpty: true });
	}

B
Benjamin Pasero 已提交
1540
	waitForWindowCloseOrLoad(windowId: number): TPromise<void> {
1541
		return new TPromise<void>(c => {
1542
			function handler(id: number) {
1543
				if (id === windowId) {
1544 1545 1546
					closeListener.dispose();
					loadListener.dispose();

1547 1548
					c(null);
				}
1549 1550 1551 1552
			}

			const closeListener = this.onWindowClose(id => handler(id));
			const loadListener = this.onWindowLoad(id => handler(id));
1553 1554 1555
		});
	}

B
Benjamin Pasero 已提交
1556
	sendToFocused(channel: string, ...args: any[]): void {
E
Erich Gamma 已提交
1557 1558 1559
		const focusedWindow = this.getFocusedWindow() || this.getLastActiveWindow();

		if (focusedWindow) {
1560
			focusedWindow.sendWhenReady(channel, ...args);
E
Erich Gamma 已提交
1561 1562 1563
		}
	}

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

1570
			w.sendWhenReady(channel, payload);
E
Erich Gamma 已提交
1571 1572 1573
		});
	}

B
Benjamin Pasero 已提交
1574
	getFocusedWindow(): ICodeWindow {
B
Benjamin Pasero 已提交
1575
		const win = BrowserWindow.getFocusedWindow();
E
Erich Gamma 已提交
1576 1577 1578 1579 1580 1581 1582
		if (win) {
			return this.getWindowById(win.id);
		}

		return null;
	}

B
Benjamin Pasero 已提交
1583
	getWindowById(windowId: number): ICodeWindow {
1584
		const res = WindowsManager.WINDOWS.filter(w => w.id === windowId);
E
Erich Gamma 已提交
1585 1586 1587 1588 1589 1590 1591
		if (res && res.length === 1) {
			return res[0];
		}

		return null;
	}

B
Benjamin Pasero 已提交
1592
	getWindows(): ICodeWindow[] {
E
Erich Gamma 已提交
1593 1594 1595
		return WindowsManager.WINDOWS;
	}

B
Benjamin Pasero 已提交
1596
	getWindowCount(): number {
E
Erich Gamma 已提交
1597 1598 1599
		return WindowsManager.WINDOWS.length;
	}

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

		// Unresponsive
		if (error === WindowError.UNRESPONSIVE) {
1605
			this.dialogs.showMessageBox({
B
Benjamin Pasero 已提交
1606
				title: product.nameLong,
E
Erich Gamma 已提交
1607
				type: 'warning',
1608
				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"))],
1609 1610
				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 已提交
1611
				noLink: true
1612 1613 1614 1615
			}, window).then(result => {
				if (!window.win) {
					return; // Return early if the window has been going down already
				}
1616

1617 1618 1619 1620 1621 1622 1623
				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 已提交
1624 1625 1626 1627
		}

		// Crashed
		else {
1628
			this.dialogs.showMessageBox({
B
Benjamin Pasero 已提交
1629
				title: product.nameLong,
E
Erich Gamma 已提交
1630
				type: 'warning',
1631
				buttons: [mnemonicButtonLabel(localize({ key: 'reopen', comment: ['&& denotes a mnemonic'] }, "&&Reopen")), mnemonicButtonLabel(localize({ key: 'close', comment: ['&& denotes a mnemonic'] }, "&&Close"))],
1632 1633
				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 已提交
1634
				noLink: true
1635 1636 1637 1638
			}, window).then(result => {
				if (!window.win) {
					return; // Return early if the window has been going down already
				}
1639

1640 1641 1642 1643 1644 1645 1646
				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 已提交
1647 1648 1649
		}
	}

1650
	private onWindowClosed(win: ICodeWindow): void {
E
Erich Gamma 已提交
1651 1652 1653 1654 1655

		// Tell window
		win.dispose();

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

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

B
Benjamin Pasero 已提交
1664
	pickFileFolderAndOpen(options: INativeOpenDialogOptions): void {
B
Benjamin Pasero 已提交
1665
		this.doPickAndOpen(options, true /* pick folders */, true /* pick files */);
1666 1667
	}

B
Benjamin Pasero 已提交
1668
	pickFolderAndOpen(options: INativeOpenDialogOptions): void {
B
Benjamin Pasero 已提交
1669
		this.doPickAndOpen(options, true /* pick folders */, false /* pick files */);
1670 1671
	}

B
Benjamin Pasero 已提交
1672
	pickFileAndOpen(options: INativeOpenDialogOptions): void {
B
Benjamin Pasero 已提交
1673 1674 1675 1676 1677 1678 1679 1680 1681 1682 1683 1684 1685 1686 1687 1688 1689 1690 1691 1692 1693 1694 1695 1696 1697
		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;

		if (!internalOptions.dialogOptions) {
			internalOptions.dialogOptions = Object.create(null);
		}

		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 已提交
1698
				// __GDPR__TODO__ classify event
B
Benjamin Pasero 已提交
1699 1700 1701 1702 1703 1704 1705 1706
				internalOptions.telemetryEventName = 'openFileFolder';
			} else if (pickFolders) {
				internalOptions.telemetryEventName = 'openFolder';
			} else {
				internalOptions.telemetryEventName = 'openFile';
			}
		}

1707 1708 1709
		this.dialogs.pickAndOpen(internalOptions);
	}

B
Benjamin Pasero 已提交
1710
	showMessageBox(options: Electron.MessageBoxOptions, win?: ICodeWindow): TPromise<IMessageBoxResult> {
1711 1712 1713
		return this.dialogs.showMessageBox(options, win);
	}

B
Benjamin Pasero 已提交
1714
	showSaveDialog(options: Electron.SaveDialogOptions, win?: ICodeWindow): TPromise<string> {
1715 1716 1717
		return this.dialogs.showSaveDialog(options, win);
	}

B
Benjamin Pasero 已提交
1718
	showOpenDialog(options: Electron.OpenDialogOptions, win?: ICodeWindow): TPromise<string[]> {
1719
		return this.dialogs.showOpenDialog(options, win);
B
Benjamin Pasero 已提交
1720 1721
	}

B
Benjamin Pasero 已提交
1722
	quit(): void {
B
Benjamin Pasero 已提交
1723 1724 1725

		// 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.
1726 1727 1728
		const window = this.getFocusedWindow();
		if (window && window.isExtensionDevelopmentHost && this.getWindowCount() > 1) {
			window.win.close();
B
Benjamin Pasero 已提交
1729 1730 1731 1732 1733 1734 1735 1736
		}

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

B
Benjamin Pasero 已提交
1740
interface IInternalNativeOpenDialogOptions extends INativeOpenDialogOptions {
B
Benjamin Pasero 已提交
1741 1742 1743 1744
	pickFolders?: boolean;
	pickFiles?: boolean;
}

1745
class Dialogs {
B
Benjamin Pasero 已提交
1746

1747
	private static readonly workingDirPickerStorageKey = 'pickerWorkingDir';
1748

1749 1750 1751
	private mapWindowToDialogQueue: Map<number, Queue<any>>;
	private noWindowDialogQueue: Queue<any>;

B
Benjamin Pasero 已提交
1752 1753 1754
	constructor(
		private environmentService: IEnvironmentService,
		private telemetryService: ITelemetryService,
B
Benjamin Pasero 已提交
1755
		private stateService: IStateService,
B
Benjamin Pasero 已提交
1756
		private windowsMainService: IWindowsMainService,
B
Benjamin Pasero 已提交
1757
	) {
1758 1759
		this.mapWindowToDialogQueue = new Map<number, Queue<any>>();
		this.noWindowDialogQueue = new Queue<any>();
B
Benjamin Pasero 已提交
1760 1761
	}

B
Benjamin Pasero 已提交
1762
	pickAndOpen(options: INativeOpenDialogOptions): void {
1763
		this.getFileOrFolderUris(options).then(paths => {
B
Benjamin Pasero 已提交
1764 1765 1766 1767
			const numberOfPaths = paths ? paths.length : 0;

			// Telemetry
			if (options.telemetryEventName) {
K
kieferrm 已提交
1768
				// __GDPR__TODO__ Dynamic event names and dynamic properties. Can not be registered statically.
B
Benjamin Pasero 已提交
1769 1770 1771 1772 1773 1774 1775 1776 1777
				this.telemetryService.publicLog(options.telemetryEventName, {
					...options.telemetryExtraData,
					outcome: numberOfPaths ? 'success' : 'canceled',
					numberOfPaths
				});
			}

			// Open
			if (numberOfPaths) {
1778 1779 1780
				this.windowsMainService.open({
					context: OpenContext.DIALOG,
					cli: this.environmentService.args,
S
Sandeep Somavarapu 已提交
1781
					urisToOpen: paths,
1782 1783 1784
					forceNewWindow: options.forceNewWindow,
					forceOpenWorkspaceAsFile: options.dialogOptions && !equals(options.dialogOptions.filters, WORKSPACE_FILTER)
				});
1785 1786 1787 1788
			}
		});
	}

1789
	private getFileOrFolderUris(options: IInternalNativeOpenDialogOptions): TPromise<URI[]> {
1790

B
Benjamin Pasero 已提交
1791 1792 1793 1794 1795 1796 1797
		// Ensure dialog options
		if (!options.dialogOptions) {
			options.dialogOptions = Object.create(null);
		}

		// Ensure defaultPath
		if (!options.dialogOptions.defaultPath) {
1798
			options.dialogOptions.defaultPath = this.stateService.getItem<string>(Dialogs.workingDirPickerStorageKey);
1799 1800
		}

B
Benjamin Pasero 已提交
1801 1802 1803 1804 1805 1806 1807 1808 1809 1810 1811 1812 1813
		// Ensure properties
		if (typeof options.pickFiles === 'boolean' || typeof options.pickFolders === 'boolean') {
			options.dialogOptions.properties = void 0; // let it override based on the booleans

			if (options.pickFiles && options.pickFolders) {
				options.dialogOptions.properties = ['multiSelections', 'openDirectory', 'openFile', 'createDirectory'];
			}
		}

		if (!options.dialogOptions.properties) {
			options.dialogOptions.properties = ['multiSelections', options.pickFolders ? 'openDirectory' : 'openFile', 'createDirectory'];
		}

1814 1815 1816 1817
		if (isMacintosh) {
			options.dialogOptions.properties.push('treatPackageAsDirectory'); // always drill into .app files
		}

B
Benjamin Pasero 已提交
1818
		// Show Dialog
1819
		const focusedWindow = this.windowsMainService.getWindowById(options.windowId) || this.windowsMainService.getFocusedWindow();
1820 1821 1822 1823 1824 1825 1826

		return this.showOpenDialog(options.dialogOptions, focusedWindow).then(paths => {
			if (paths && paths.length > 0) {

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

1827
				return paths.map(path => URI.file(path));
1828 1829 1830 1831 1832 1833 1834 1835 1836 1837 1838 1839 1840 1841 1842 1843 1844 1845 1846 1847
			}

			return void 0;
		});
	}

	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;
	}

B
Benjamin Pasero 已提交
1848
	showMessageBox(options: Electron.MessageBoxOptions, window?: ICodeWindow): TPromise<IMessageBoxResult> {
1849 1850
		return this.getDialogQueue(window).queue(() => {
			return new TPromise((c, e) => {
B
Benjamin Pasero 已提交
1851 1852 1853
				dialog.showMessageBox(window ? window.win : void 0, options, (response: number, checkboxChecked: boolean) => {
					c({ button: response, checkboxChecked });
				});
1854 1855 1856 1857
			});
		});
	}

B
Benjamin Pasero 已提交
1858
	showSaveDialog(options: Electron.SaveDialogOptions, window?: ICodeWindow): TPromise<string> {
B
Benjamin Pasero 已提交
1859

1860 1861 1862
		function normalizePath(path: string): string {
			if (path && isMacintosh) {
				path = normalizeNFC(path); // normalize paths returned from the OS
1863
			}
1864

1865 1866
			return path;
		}
1867

1868 1869
		return this.getDialogQueue(window).queue(() => {
			return new TPromise((c, e) => {
B
Benjamin Pasero 已提交
1870 1871 1872
				dialog.showSaveDialog(window ? window.win : void 0, options, path => {
					c(normalizePath(path));
				});
1873 1874 1875 1876
			});
		});
	}

B
Benjamin Pasero 已提交
1877
	showOpenDialog(options: Electron.OpenDialogOptions, window?: ICodeWindow): TPromise<string[]> {
B
Benjamin Pasero 已提交
1878

1879 1880 1881 1882 1883 1884
		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;
1885
		}
B
Benjamin Pasero 已提交
1886

1887 1888
		return this.getDialogQueue(window).queue(() => {
			return new TPromise((c, e) => {
B
Benjamin Pasero 已提交
1889 1890 1891 1892 1893 1894 1895 1896 1897 1898 1899 1900 1901 1902 1903 1904

				// Ensure the path exists (if provided)
				let validatePathPromise: TPromise<void> = TPromise.as(void 0);
				if (options.defaultPath) {
					validatePathPromise = exists(options.defaultPath).then(exists => {
						if (!exists) {
							options.defaultPath = void 0;
						}
					});
				}

				// Show dialog and wrap as promise
				validatePathPromise.then(() => {
					dialog.showOpenDialog(window ? window.win : void 0, options, paths => {
						c(normalizePaths(paths));
					});
B
Benjamin Pasero 已提交
1905
				});
1906 1907
			});
		});
1908
	}
1909 1910 1911 1912 1913
}

class WorkspacesManager {

	constructor(
1914 1915
		private workspacesMainService: IWorkspacesMainService,
		private backupMainService: IBackupMainService,
1916 1917 1918 1919 1920
		private environmentService: IEnvironmentService,
		private windowsMainService: IWindowsMainService
	) {
	}

B
Benjamin Pasero 已提交
1921
	saveAndEnterWorkspace(window: ICodeWindow, path: string): TPromise<IEnterWorkspaceResult> {
1922 1923 1924 1925 1926 1927 1928
		if (!window || !window.win || window.readyState !== ReadyState.READY || !window.openedWorkspace || !path || !this.isValidTargetWorkspacePath(window, path)) {
			return TPromise.as(null); // return early if the window is not ready or disposed or does not have a workspace
		}

		return this.doSaveAndOpenWorkspace(window, window.openedWorkspace, path);
	}

1929 1930 1931 1932 1933 1934 1935 1936 1937 1938 1939 1940 1941 1942 1943 1944 1945
	enterWorkspace(window: ICodeWindow, path: string): TPromise<IEnterWorkspaceResult> {
		if (!window || !window.win || window.readyState !== ReadyState.READY) {
			return TPromise.as(null); // return early if the window is not ready or disposed
		}

		return this.isValidTargetWorkspacePath(window, path).then(isValid => {
			if (!isValid) {
				return TPromise.as<IEnterWorkspaceResult>(null); // return early if the workspace is not valid
			}

			return this.workspacesMainService.resolveWorkspace(path).then(workspace => {
				return this.doOpenWorkspace(window, workspace);
			});
		});

	}

B
Benjamin Pasero 已提交
1946
	createAndEnterWorkspace(window: ICodeWindow, folders?: IWorkspaceFolderCreationData[], path?: string): TPromise<IEnterWorkspaceResult> {
1947
		if (!window || !window.win || window.readyState !== ReadyState.READY) {
1948 1949 1950
			return TPromise.as(null); // return early if the window is not ready or disposed
		}

1951 1952 1953 1954 1955
		return this.isValidTargetWorkspacePath(window, path).then(isValid => {
			if (!isValid) {
				return TPromise.as(null); // return early if the workspace is not valid
			}

1956
			return this.workspacesMainService.createWorkspace(folders).then(workspace => {
1957 1958
				return this.doSaveAndOpenWorkspace(window, workspace, path);
			});
1959
		});
1960

1961 1962
	}

1963
	private isValidTargetWorkspacePath(window: ICodeWindow, path?: string): TPromise<boolean> {
1964
		if (!path) {
1965
			return TPromise.wrap(true);
1966 1967 1968
		}

		if (window.openedWorkspace && window.openedWorkspace.configPath === path) {
1969
			return TPromise.wrap(false); // window is already opened on a workspace with that path
1970 1971 1972
		}

		// Prevent overwriting a workspace that is currently opened in another window
1973
		if (findWindowOnWorkspace(this.windowsMainService.getWindows(), { id: this.workspacesMainService.getWorkspaceId(path), configPath: path })) {
1974 1975 1976 1977 1978
			const options: Electron.MessageBoxOptions = {
				title: product.nameLong,
				type: 'info',
				buttons: [localize('ok', "OK")],
				message: localize('workspaceOpenedMessage', "Unable to save workspace '{0}'", basename(path)),
1979
				detail: localize('workspaceOpenedDetail', "The workspace is already opened in another window. Please close that window first and then try again."),
1980 1981 1982
				noLink: true
			};

1983
			return this.windowsMainService.showMessageBox(options, this.windowsMainService.getFocusedWindow()).then(() => false);
1984 1985
		}

1986
		return TPromise.wrap(true); // OK
1987 1988
	}

1989
	private doSaveAndOpenWorkspace(window: ICodeWindow, workspace: IWorkspaceIdentifier, path?: string): TPromise<IEnterWorkspaceResult> {
1990 1991
		let savePromise: TPromise<IWorkspaceIdentifier>;
		if (path) {
1992
			savePromise = this.workspacesMainService.saveWorkspace(workspace, path);
1993 1994 1995 1996
		} else {
			savePromise = TPromise.as(workspace);
		}

1997 1998
		return savePromise.then(workspace => this.doOpenWorkspace(window, workspace));
	}
1999

2000 2001
	private doOpenWorkspace(window: ICodeWindow, workspace: IWorkspaceIdentifier): IEnterWorkspaceResult {
		window.focus();
2002

2003 2004 2005 2006 2007
		// Register window for backups and migrate current backups over
		let backupPath: string;
		if (!window.config.extensionDevelopmentPath) {
			backupPath = this.backupMainService.registerWorkspaceBackupSync(workspace, window.config.backupPath);
		}
2008

2009
		// Update window configuration properly based on transition to workspace
2010
		window.config.folderUri = void 0;
2011 2012 2013 2014
		window.config.workspace = workspace;
		window.config.backupPath = backupPath;

		return { workspace, backupPath };
2015 2016
	}

B
Benjamin Pasero 已提交
2017
	pickWorkspaceAndOpen(options: INativeOpenDialogOptions): void {
2018
		const window = this.windowsMainService.getWindowById(options.windowId) || this.windowsMainService.getFocusedWindow() || this.windowsMainService.getLastActiveWindow();
2019 2020 2021 2022 2023 2024 2025 2026

		this.windowsMainService.pickFileAndOpen({
			windowId: window ? window.id : void 0,
			dialogOptions: {
				buttonLabel: mnemonicButtonLabel(localize({ key: 'openWorkspace', comment: ['&& denotes a mnemonic'] }, "&&Open")),
				title: localize('openWorkspaceTitle', "Open Workspace"),
				filters: WORKSPACE_FILTER,
				properties: ['openFile'],
2027
				defaultPath: options.dialogOptions && options.dialogOptions.defaultPath
2028
			},
2029 2030 2031
			forceNewWindow: options.forceNewWindow,
			telemetryEventName: options.telemetryEventName,
			telemetryExtraData: options.telemetryExtraData
2032 2033 2034
		});
	}

B
Benjamin Pasero 已提交
2035
	promptToSaveUntitledWorkspace(window: ICodeWindow, workspace: IWorkspaceIdentifier): TPromise<boolean> {
2036 2037 2038 2039 2040 2041 2042 2043 2044 2045 2046 2047 2048 2049 2050 2051 2052 2053 2054 2055 2056 2057 2058 2059 2060 2061 2062 2063 2064 2065 2066 2067 2068
		enum ConfirmResult {
			SAVE,
			DONT_SAVE,
			CANCEL
		}

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

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

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

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

2069 2070 2071 2072 2073 2074 2075 2076 2077
		return this.windowsMainService.showMessageBox(options, window).then(res => {
			switch (buttons[res.button].result) {

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

				// Don't Save: delete workspace
				case ConfirmResult.DONT_SAVE:
2078
					this.workspacesMainService.deleteUntitledWorkspaceSync(workspace);
2079 2080 2081 2082 2083 2084 2085 2086 2087 2088 2089
					return false;

				// Save: save workspace, but do not veto unload
				case ConfirmResult.SAVE: {
					return this.windowsMainService.showSaveDialog({
						buttonLabel: mnemonicButtonLabel(localize({ key: 'save', comment: ['&& denotes a mnemonic'] }, "&&Save")),
						title: localize('saveWorkspace', "Save Workspace"),
						filters: WORKSPACE_FILTER,
						defaultPath: this.getUntitledWorkspaceSaveDialogDefaultPath(workspace)
					}, window).then(target => {
						if (target) {
2090
							return this.workspacesMainService.saveWorkspace(workspace, target).then(() => false, () => false);
2091
						}
2092

2093 2094
						return true; // keep veto if no target was provided
					});
2095 2096
				}
			}
2097
		});
2098 2099
	}

2100
	private getUntitledWorkspaceSaveDialogDefaultPath(workspace?: IWorkspaceIdentifier | ISingleFolderWorkspaceIdentifier): string {
2101
		if (workspace) {
2102
			if (isSingleFolderWorkspaceIdentifier(workspace)) {
2103
				return workspace.scheme === Schemas.file ? dirname(workspace.fsPath) : void 0;
J
Johannes Rieken 已提交
2104 2105
			}

2106
			const resolvedWorkspace = this.workspacesMainService.resolveWorkspaceSync(workspace.configPath);
J
Johannes Rieken 已提交
2107 2108 2109 2110 2111
			if (resolvedWorkspace && resolvedWorkspace.folders.length > 0) {
				for (const folder of resolvedWorkspace.folders) {
					if (folder.uri.scheme === Schemas.file) {
						return dirname(folder.uri.fsPath);
					}
2112 2113 2114
				}
			}
		}
2115

J
Johannes Rieken 已提交
2116
		return void 0;
2117
	}
J
Johannes Rieken 已提交
2118
}