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

'use strict';

8
import { basename, normalize, join, dirname } from 'path';
B
Benjamin Pasero 已提交
9
import * as fs from 'original-fs';
B
Benjamin Pasero 已提交
10
import { localize } from 'vs/nls';
J
Joao Moreno 已提交
11
import * as arrays from 'vs/base/common/arrays';
12
import { assign, mixin, equals } from 'vs/base/common/objects';
D
Daniel Imms 已提交
13
import { IBackupMainService } from 'vs/platform/backup/common/backup';
J
Joao Moreno 已提交
14
import { IEnvironmentService, ParsedArgs } from 'vs/platform/environment/common/environment';
B
Benjamin Pasero 已提交
15
import { IStateService } from 'vs/platform/state/common/state';
16
import { CodeWindow, defaultWindowState } from 'vs/code/electron-main/window';
17
import { ipcMain as ipc, screen, BrowserWindow, dialog, systemPreferences, app } from 'electron';
B
Benjamin Pasero 已提交
18
import { IPathWithLineAndColumn, parseLineAndColumnAware } from 'vs/code/node/paths';
19
import { ILifecycleService, UnloadReason, IWindowUnloadEvent } from 'vs/platform/lifecycle/electron-main/lifecycleMain';
20
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
21
import { ILogService } from 'vs/platform/log/common/log';
22
import { IWindowSettings, OpenContext, IPath, IWindowConfiguration, INativeOpenDialogOptions, ReadyState, IPathsToWaitFor, IEnterWorkspaceResult, IMessageBoxResult } from 'vs/platform/windows/common/windows';
23
import { getLastActiveWindow, findBestWindowOrFolderForFile, findWindowOnWorkspace, findWindowOnExtensionDevelopmentPath, findWindowOnWorkspaceOrFolderPath } from 'vs/code/node/windowsFinder';
M
Matt Bierner 已提交
24
import { Event as CommonEvent, Emitter } from 'vs/base/common/event';
25
import product from 'vs/platform/node/product';
B
Benjamin Pasero 已提交
26
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
27
import { isEqual } from 'vs/base/common/paths';
28
import { IWindowsMainService, IOpenConfiguration, IWindowsCountChangedEvent, ICodeWindow, IWindowState as ISingleWindowState, WindowMode } from 'vs/platform/windows/electron-main/windows';
B
Benjamin Pasero 已提交
29
import { IHistoryMainService } from 'vs/platform/history/common/history';
B
Benjamin Pasero 已提交
30
import { IProcessEnvironment, isLinux, isMacintosh, isWindows } from 'vs/base/common/platform';
31
import { TPromise } from 'vs/base/common/winjs.base';
32
import { IWorkspacesMainService, IWorkspaceIdentifier, ISingleFolderWorkspaceIdentifier, WORKSPACE_FILTER, isSingleFolderWorkspaceIdentifier, IWorkspaceFolderCreationData } from 'vs/platform/workspaces/common/workspaces';
B
Benjamin Pasero 已提交
33
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
34
import { mnemonicButtonLabel } from 'vs/base/common/labels';
J
Johannes Rieken 已提交
35
import { Schemas } from 'vs/base/common/network';
36
import { normalizeNFC } from 'vs/base/common/normalization';
37
import URI from 'vs/base/common/uri';
38
import { Queue } from 'vs/base/common/async';
B
Benjamin Pasero 已提交
39
import { exists } from 'vs/base/node/pfs';
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
	folderPath?: string;
53
	backupPath: string;
J
Joao Moreno 已提交
54
	uiState: ISingleWindowState;
E
Erich Gamma 已提交
55 56 57 58 59
}

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

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

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

69
	workspace?: IWorkspaceIdentifier;
70
	folderPath?: string;
B
Benjamin Pasero 已提交
71 72 73 74 75 76

	initialStartup?: boolean;

	filesToOpen?: IPath[];
	filesToCreate?: IPath[];
	filesToDiff?: IPath[];
77
	filesToWait?: IPathsToWaitFor;
B
Benjamin Pasero 已提交
78 79

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

82
	emptyWindowBackupFolder?: string;
B
Benjamin Pasero 已提交
83 84
}

B
Benjamin Pasero 已提交
85
interface IPathToOpen extends IPath {
86

87
	// the workspace for a Code instance to open
88
	workspace?: IWorkspaceIdentifier;
89

90 91
	// the folder path for a Code instance to open
	folderPath?: string;
92 93 94 95 96 97 98 99

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

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

J
Joao Moreno 已提交
100
export class WindowsManager implements IWindowsMainService {
J
Joao Moreno 已提交
101

102
	_serviceBrand: any;
E
Erich Gamma 已提交
103

104
	private static readonly windowsStateStorageKey = 'windowsState';
E
Erich Gamma 已提交
105

106
	private static WINDOWS: ICodeWindow[] = [];
E
Erich Gamma 已提交
107

B
Benjamin Pasero 已提交
108
	private initialUserEnv: IProcessEnvironment;
109

E
Erich Gamma 已提交
110
	private windowsState: IWindowsState;
111
	private lastClosedWindowState: IWindowState;
E
Erich Gamma 已提交
112

113
	private dialogs: Dialogs;
114
	private workspacesManager: WorkspacesManager;
B
Benjamin Pasero 已提交
115

116 117
	private _onWindowReady = new Emitter<ICodeWindow>();
	onWindowReady: CommonEvent<ICodeWindow> = this._onWindowReady.event;
118 119 120 121

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

122 123 124
	private _onWindowLoad = new Emitter<number>();
	onWindowLoad: CommonEvent<number> = this._onWindowLoad.event;

125 126
	private _onActiveWindowChanged = new Emitter<ICodeWindow>();
	onActiveWindowChanged: CommonEvent<ICodeWindow> = this._onActiveWindowChanged.event;
127

128 129 130
	private _onWindowReload = new Emitter<number>();
	onWindowReload: CommonEvent<number> = this._onWindowReload.event;

B
Benjamin Pasero 已提交
131 132 133
	private _onWindowsCountChanged = new Emitter<IWindowsCountChangedEvent>();
	onWindowsCountChanged: CommonEvent<IWindowsCountChangedEvent> = this._onWindowsCountChanged.event;

J
Joao Moreno 已提交
134
	constructor(
B
Benjamin Pasero 已提交
135
		private readonly machineId: string,
J
Joao Moreno 已提交
136
		@ILogService private logService: ILogService,
B
Benjamin Pasero 已提交
137
		@IStateService private stateService: IStateService,
138
		@IEnvironmentService private environmentService: IEnvironmentService,
B
Benjamin Pasero 已提交
139
		@ILifecycleService private lifecycleService: ILifecycleService,
B
Benjamin Pasero 已提交
140
		@IBackupMainService private backupMainService: IBackupMainService,
B
Benjamin Pasero 已提交
141
		@ITelemetryService telemetryService: ITelemetryService,
142
		@IConfigurationService private configurationService: IConfigurationService,
B
Benjamin Pasero 已提交
143 144
		@IHistoryMainService private historyMainService: IHistoryMainService,
		@IWorkspacesMainService private workspacesMainService: IWorkspacesMainService,
145
		@IInstantiationService private instantiationService: IInstantiationService
146
	) {
B
Benjamin Pasero 已提交
147
		this.windowsState = this.stateService.getItem<IWindowsState>(WindowsManager.windowsStateStorageKey) || { openedWindows: [] };
148 149 150
		if (!Array.isArray(this.windowsState.openedWindows)) {
			this.windowsState.openedWindows = [];
		}
151

B
Benjamin Pasero 已提交
152
		this.dialogs = new Dialogs(environmentService, telemetryService, stateService, this);
B
Benjamin Pasero 已提交
153
		this.workspacesManager = new WorkspacesManager(workspacesMainService, backupMainService, environmentService, this);
154
	}
J
Joao Moreno 已提交
155

B
Benjamin Pasero 已提交
156
	public ready(initialUserEnv: IProcessEnvironment): void {
157
		this.initialUserEnv = initialUserEnv;
158 159

		this.registerListeners();
E
Erich Gamma 已提交
160 161 162
	}

	private registerListeners(): void {
163

164 165 166 167 168 169 170
		// React to windows focus changes
		app.on('browser-window-focus', () => {
			setTimeout(() => {
				this._onActiveWindowChanged.fire(this.getLastActiveWindow());
			});
		});

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

B
Benjamin Pasero 已提交
175
			const win = this.getWindowById(windowId);
E
Erich Gamma 已提交
176 177 178 179
			if (win) {
				win.setReady();

				// Event
180
				this._onWindowReady.fire(win);
E
Erich Gamma 已提交
181 182 183
			}
		});

184 185 186 187 188 189 190 191 192 193 194
		// 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');
				}
			});
		}

195 196
		// Handle various lifecycle events around windows
		this.lifecycleService.onBeforeWindowUnload(e => this.onBeforeWindowUnload(e));
197
		this.lifecycleService.onBeforeWindowClose(win => this.onBeforeWindowClose(win as ICodeWindow));
198
		this.lifecycleService.onBeforeShutdown(() => this.onBeforeShutdown());
199 200 201 202 203 204 205 206
		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;
			}
		});
207 208
	}

209
	// Note that onBeforeShutdown() and onBeforeWindowClose() are fired in different order depending on the OS:
210
	// - macOS: since the app will not quit when closing the last window, you will always first get
211
	//          the onBeforeShutdown() event followed by N onbeforeWindowClose() events for each window
212 213
	// - 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()
214
	//          and then onBeforeShutdown(). Using the quit action however will first issue onBeforeShutdown()
215
	//          and then onBeforeWindowClose().
216 217 218 219 220 221 222
	//
	// 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
223
	// - onBeforeShutdown(N): number of windows reported in this event handler
224 225 226
	// - onBeforeWindowClose(N, M): number of windows reported and quitRequested boolean in this event handler
	//
	// macOS
227 228 229
	// 	-     quit(1): onBeforeShutdown(1), onBeforeWindowClose(1, true)
	// 	-     quit(2): onBeforeShutdown(2), onBeforeWindowClose(2, true), onBeforeWindowClose(2, true)
	// 	-     quit(0): onBeforeShutdown(0)
230 231 232
	// 	-    close(1): onBeforeWindowClose(1, false)
	//
	// Windows
233 234
	// 	-     quit(1): onBeforeShutdown(1), onBeforeWindowClose(1, true)
	// 	-     quit(2): onBeforeShutdown(2), onBeforeWindowClose(2, true), onBeforeWindowClose(2, true)
235
	// 	-    close(1): onBeforeWindowClose(2, false)[not last window]
236 237
	// 	-    close(1): onBeforeWindowClose(1, false), onBeforeShutdown(0)[last window]
	// 	- closeAll(2): onBeforeWindowClose(2, false), onBeforeWindowClose(2, false), onBeforeShutdown(0)
238 239
	//
	// Linux
240 241
	// 	-     quit(1): onBeforeShutdown(1), onBeforeWindowClose(1, true)
	// 	-     quit(2): onBeforeShutdown(2), onBeforeWindowClose(2, true), onBeforeWindowClose(2, true)
242
	// 	-    close(1): onBeforeWindowClose(2, false)[not last window]
243 244
	// 	-    close(1): onBeforeWindowClose(1, false), onBeforeShutdown(0)[last window]
	// 	- closeAll(2): onBeforeWindowClose(2, false), onBeforeWindowClose(2, false), onBeforeShutdown(0)
245
	//
246
	private onBeforeShutdown(): void {
247
		const currentWindowsState: IWindowsState = {
248
			openedWindows: [],
249
			lastPluginDevelopmentHostWindow: this.windowsState.lastPluginDevelopmentHostWindow,
250
			lastActiveWindow: this.lastClosedWindowState
251 252 253 254 255 256 257 258
		};

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

260
			if (activeWindow) {
261
				currentWindowsState.lastActiveWindow = this.toWindowState(activeWindow);
E
Erich Gamma 已提交
262
			}
263 264 265 266 267
		}

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

271
		// 3.) All windows (except extension host) for N >= 2 to support restoreWindows: all or for auto update
272 273 274 275 276
		//
		// 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) {
277
			currentWindowsState.openedWindows = WindowsManager.WINDOWS.filter(w => !w.isExtensionDevelopmentHost).map(w => this.toWindowState(w));
278
		}
E
Erich Gamma 已提交
279

280
		// Persist
B
Benjamin Pasero 已提交
281
		this.stateService.setItem(WindowsManager.windowsStateStorageKey, currentWindowsState);
282
	}
283

284
	// See note on #onBeforeShutdown() for details how these events are flowing
285
	private onBeforeWindowClose(win: ICodeWindow): void {
286
		if (this.lifecycleService.isQuitRequested) {
287 288 289 290
			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
291
		const state: IWindowState = this.toWindowState(win);
292 293 294 295
		if (win.isExtensionDevelopmentHost && !win.isExtensionTestHost) {
			this.windowsState.lastPluginDevelopmentHostWindow = state; // do not let test run window state overwrite our extension development state
		}

296
		// Any non extension host window with same workspace or folder
297
		else if (!win.isExtensionDevelopmentHost && (!!win.openedWorkspace || !!win.openedFolderPath)) {
298
			this.windowsState.openedWindows.forEach(o => {
B
fix npe  
Benjamin Pasero 已提交
299
				const sameWorkspace = win.openedWorkspace && o.workspace && o.workspace.id === win.openedWorkspace.id;
300 301 302
				const sameFolder = win.openedFolderPath && isEqual(o.folderPath, win.openedFolderPath, !isLinux /* ignorecase */);

				if (sameWorkspace || sameFolder) {
303 304 305 306 307 308 309
					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.
310 311 312
		// 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) {
313 314
			this.lastClosedWindowState = state;
		}
E
Erich Gamma 已提交
315 316
	}

317
	private toWindowState(win: ICodeWindow): IWindowState {
318
		return {
319
			workspace: win.openedWorkspace,
320 321 322 323 324 325
			folderPath: win.openedFolderPath,
			backupPath: win.backupPath,
			uiState: win.serializeWindowState()
		};
	}

326
	public open(openConfig: IOpenConfiguration): ICodeWindow[] {
327
		openConfig = this.validateOpenConfig(openConfig);
328

329
		let pathsToOpen = this.getPathsToOpen(openConfig);
330 331 332

		// When run with --add, take the folders that are to be opened as
		// folders that should be added to the currently active window.
333
		let foldersToAdd: IPath[] = [];
334
		if (openConfig.addMode) {
335 336 337
			foldersToAdd = pathsToOpen.filter(path => !!path.folderPath).map(path => ({ filePath: path.folderPath }));
			pathsToOpen = pathsToOpen.filter(path => !path.folderPath);
		}
E
Erich Gamma 已提交
338

B
Benjamin Pasero 已提交
339 340
		let filesToOpen = pathsToOpen.filter(path => !!path.filePath && !path.createFilePath);
		let filesToCreate = pathsToOpen.filter(path => !!path.filePath && path.createFilePath);
341 342 343 344

		// When run with --diff, take the files to open as files to diff
		// if there are exactly two files provided.
		let filesToDiff: IPath[] = [];
345 346 347 348
		if (openConfig.diffMode && filesToOpen.length === 2) {
			filesToDiff = filesToOpen;
			filesToOpen = [];
			filesToCreate = []; // diff ignores other files that do not exist
E
Erich Gamma 已提交
349 350
		}

351 352 353 354 355 356
		// When run with --wait, make sure we keep the paths to wait for
		let filesToWait: IPathsToWaitFor;
		if (openConfig.cli.wait && openConfig.cli.waitMarkerFilePath) {
			filesToWait = { paths: [...filesToDiff, ...filesToOpen, ...filesToCreate], waitMarkerFilePath: openConfig.cli.waitMarkerFilePath };
		}

357 358 359
		//
		// These are windows to open to show workspaces
		//
B
Benjamin Pasero 已提交
360
		const workspacesToOpen = arrays.distinct(pathsToOpen.filter(win => !!win.workspace).map(win => win.workspace), workspace => workspace.id); // prevent duplicates
361 362 363 364

		//
		// These are windows to open to show either folders or files (including diffing files or creating them)
		//
B
Benjamin Pasero 已提交
365
		const foldersToOpen = arrays.distinct(pathsToOpen.filter(win => win.folderPath && !win.filePath).map(win => win.folderPath), folder => isLinux ? folder : folder.toLowerCase()); // prevent duplicates
366

367
		//
368
		// These are windows to restore because of hot-exit or from previous session (only performed once on startup!)
369
		//
370 371 372
		let foldersToRestore: string[] = [];
		let workspacesToRestore: IWorkspaceIdentifier[] = [];
		let emptyToRestore: string[] = [];
B
Benjamin Pasero 已提交
373
		if (openConfig.initialStartup && !openConfig.cli.extensionDevelopmentPath && !openConfig.cli['disable-restore-windows']) {
B
Benjamin Pasero 已提交
374
			foldersToRestore = this.backupMainService.getFolderBackupPaths();
375

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

B
Benjamin Pasero 已提交
379
			emptyToRestore = this.backupMainService.getEmptyWindowBackupPaths();
380
			emptyToRestore.push(...pathsToOpen.filter(w => !w.workspace && !w.folderPath && w.backupPath).map(w => basename(w.backupPath))); // add empty windows with backupPath
381 382
			emptyToRestore = arrays.distinct(emptyToRestore); // prevent duplicates
		}
383

384 385 386
		//
		// These are empty windows to open
		//
B
Benjamin Pasero 已提交
387
		const emptyToOpen = pathsToOpen.filter(win => !win.workspace && !win.folderPath && !win.filePath && !win.backupPath).length;
388

389
		// Open based on config
390
		const usedWindows = this.doOpen(openConfig, workspacesToOpen, workspacesToRestore, foldersToOpen, foldersToRestore, emptyToRestore, emptyToOpen, filesToOpen, filesToCreate, filesToDiff, filesToWait, foldersToAdd);
391

392
		// Make sure to pass focus to the most relevant of the windows if we open multiple
393
		if (usedWindows.length > 1) {
394 395 396
			let focusLastActive = this.windowsState.lastActiveWindow && !openConfig.forceEmpty && !openConfig.cli._.length && (!openConfig.pathsToOpen || !openConfig.pathsToOpen.length);
			let focusLastOpened = true;
			let focusLastWindow = true;
397

398 399
			// 1.) focus last active window if we are not instructed to open any paths
			if (focusLastActive) {
400 401 402
				const lastActiveWindw = usedWindows.filter(w => w.backupPath === this.windowsState.lastActiveWindow.backupPath);
				if (lastActiveWindw.length) {
					lastActiveWindw[0].focus();
403 404
					focusLastOpened = false;
					focusLastWindow = false;
405 406 407
				}
			}

408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427
			// 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 (
						(usedWindow.openedWorkspace && workspacesToRestore.some(workspace => workspace.id === usedWindow.openedWorkspace.id)) || 	// skip over restored workspace
						(usedWindow.openedFolderPath && foldersToRestore.some(folder => folder === usedWindow.openedFolderPath)) ||					// skip over restored folder
						(usedWindow.backupPath && emptyToRestore.some(empty => empty === basename(usedWindow.backupPath)))							// skip over restored empty window
					) {
						continue;
					}

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

			// 3.) finally, always ensure to have at least last used window focused
			if (focusLastWindow) {
428
				usedWindows[usedWindows.length - 1].focus();
429 430
			}
		}
431

432 433 434
		// 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) {
435
			const recentlyOpenedWorkspaces: (IWorkspaceIdentifier | ISingleFolderWorkspaceIdentifier)[] = [];
436
			const recentlyOpenedFiles: string[] = [];
437

B
Benjamin Pasero 已提交
438
			pathsToOpen.forEach(win => {
439 440 441 442
				if (win.workspace || win.folderPath) {
					recentlyOpenedWorkspaces.push(win.workspace || win.folderPath);
				} else if (win.filePath) {
					recentlyOpenedFiles.push(win.filePath);
443 444 445
				}
			});

446 447 448
			if (!this.environmentService.skipAddToRecentlyOpened) {
				this.historyMainService.addRecentlyOpened(recentlyOpenedWorkspaces, recentlyOpenedFiles);
			}
449
		}
450

451
		// If we got started with --wait from the CLI, we need to signal to the outside when the window
452 453
		// 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.
454
		if (openConfig.context === OpenContext.CLI && openConfig.cli.wait && openConfig.cli.waitMarkerFilePath && usedWindows.length === 1 && usedWindows[0]) {
455
			this.waitForWindowCloseOrLoad(usedWindows[0].id).done(() => fs.unlink(openConfig.cli.waitMarkerFilePath, error => void 0));
456 457
		}

458 459 460
		return usedWindows;
	}

461 462 463 464 465 466 467 468 469 470
	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;
	}

471 472
	private doOpen(
		openConfig: IOpenConfiguration,
473 474
		workspacesToOpen: IWorkspaceIdentifier[],
		workspacesToRestore: IWorkspaceIdentifier[],
475 476 477 478 479 480
		foldersToOpen: string[],
		foldersToRestore: string[],
		emptyToRestore: string[],
		emptyToOpen: number,
		filesToOpen: IPath[],
		filesToCreate: IPath[],
481
		filesToDiff: IPath[],
482
		filesToWait: IPathsToWaitFor,
483
		foldersToAdd: IPath[]
484
	) {
485
		const usedWindows: ICodeWindow[] = [];
486

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

490 491 492 493 494 495 496 497 498 499 500
		// 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) {
				usedWindows.push(this.doAddFoldersToExistingWidow(lastActiveWindow, foldersToAdd));
			}

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

B
Benjamin Pasero 已提交
501
		// 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
502 503
		const potentialWindowsCount = foldersToOpen.length + foldersToRestore.length + workspacesToOpen.length + workspacesToRestore.length + emptyToRestore.length;
		if (potentialWindowsCount === 0 && (filesToOpen.length > 0 || filesToCreate.length > 0 || filesToDiff.length > 0)) {
E
Erich Gamma 已提交
504

505
			// Find suitable window or folder path to open files in
506
			const fileToCheck = filesToOpen[0] || filesToCreate[0] || filesToDiff[0];
507
			let bestWindowOrFolder = findBestWindowOrFolderForFile({
508 509 510 511 512
				windows: WindowsManager.WINDOWS,
				newWindow: openFilesInNewWindow,
				reuseWindow: openConfig.forceReuseWindow,
				context: openConfig.context,
				filePath: fileToCheck && fileToCheck.filePath,
B
Benjamin Pasero 已提交
513
				workspaceResolver: workspace => this.workspacesMainService.resolveWorkspaceSync(workspace.configPath)
514
			});
B
Benjamin Pasero 已提交
515

516 517 518 519 520 521
			// Special case: we started with --wait and we got back a folder to open. In this case
			// we actually prefer to not open the folder but operate purely on the file.
			if (typeof bestWindowOrFolder === 'string' && filesToWait) {
				bestWindowOrFolder = !openFilesInNewWindow ? this.getLastActiveWindow() : null;
			}

522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538
			// 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
				else if (bestWindowOrFolder.openedFolderPath) {
					foldersToOpen.push(bestWindowOrFolder.openedFolderPath);
				}

				// Window is empty
				else {

					// Do open files
539
					usedWindows.push(this.doOpenFilesInExistingWindow(openConfig, bestWindowOrFolder, filesToOpen, filesToCreate, filesToDiff, filesToWait));
540 541 542 543 544

					// Reset these because we handled them
					filesToOpen = [];
					filesToCreate = [];
					filesToDiff = [];
545
					filesToWait = void 0;
546
				}
547 548 549 550 551
			}

			// We found a suitable folder to open: add it to foldersToOpen
			else if (typeof bestWindowOrFolder === 'string') {
				foldersToOpen.push(bestWindowOrFolder);
E
Erich Gamma 已提交
552 553
			}

554
			// Finally, if no window or folder is found, just open the files in an empty window
E
Erich Gamma 已提交
555
			else {
B
Benjamin Pasero 已提交
556
				usedWindows.push(this.openInBrowserWindow({
B
Benjamin Pasero 已提交
557 558 559 560 561 562
					userEnv: openConfig.userEnv,
					cli: openConfig.cli,
					initialStartup: openConfig.initialStartup,
					filesToOpen,
					filesToCreate,
					filesToDiff,
563
					filesToWait,
B
Benjamin Pasero 已提交
564
					forceNewWindow: true
B
Benjamin Pasero 已提交
565
				}));
E
Erich Gamma 已提交
566

567 568 569 570
				// Reset these because we handled them
				filesToOpen = [];
				filesToCreate = [];
				filesToDiff = [];
571
				filesToWait = void 0;
E
Erich Gamma 已提交
572 573 574
			}
		}

575
		// Handle workspaces to open (instructed and to restore)
576
		const allWorkspacesToOpen = arrays.distinct([...workspacesToRestore, ...workspacesToOpen], workspace => workspace.id); // prevent duplicates
577 578 579 580 581 582 583 584
		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];

				// Do open files
585
				usedWindows.push(this.doOpenFilesInExistingWindow(openConfig, windowOnWorkspace, filesToOpen, filesToCreate, filesToDiff, filesToWait));
586 587 588 589 590

				// Reset these because we handled them
				filesToOpen = [];
				filesToCreate = [];
				filesToDiff = [];
591
				filesToWait = void 0;
592 593 594 595 596 597

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

			// Open remaining ones
			allWorkspacesToOpen.forEach(workspaceToOpen => {
598
				if (windowsOnWorkspace.some(win => win.openedWorkspace.id === workspaceToOpen.id)) {
599 600 601 602
					return; // ignore folders that are already open
				}

				// Do open folder
603
				usedWindows.push(this.doOpenFolderOrWorkspace(openConfig, { workspace: workspaceToOpen }, openFolderInNewWindow, filesToOpen, filesToCreate, filesToDiff, filesToWait));
604 605 606 607 608

				// Reset these because we handled them
				filesToOpen = [];
				filesToCreate = [];
				filesToDiff = [];
609
				filesToWait = void 0;
610 611 612 613 614

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

615
		// Handle folders to open (instructed and to restore)
616
		const allFoldersToOpen = arrays.distinct([...foldersToRestore, ...foldersToOpen], folder => isLinux ? folder : folder.toLowerCase()); // prevent duplicates
617
		if (allFoldersToOpen.length > 0) {
E
Erich Gamma 已提交
618 619

			// Check for existing instances
620
			const windowsOnFolderPath = arrays.coalesce(allFoldersToOpen.map(folderToOpen => findWindowOnWorkspace(WindowsManager.WINDOWS, folderToOpen)));
621
			if (windowsOnFolderPath.length > 0) {
622
				const windowOnFolderPath = windowsOnFolderPath[0];
E
Erich Gamma 已提交
623

624
				// Do open files
625
				usedWindows.push(this.doOpenFilesInExistingWindow(openConfig, windowOnFolderPath, filesToOpen, filesToCreate, filesToDiff, filesToWait));
626

E
Erich Gamma 已提交
627 628 629
				// Reset these because we handled them
				filesToOpen = [];
				filesToCreate = [];
630
				filesToDiff = [];
631
				filesToWait = void 0;
E
Erich Gamma 已提交
632

B
Benjamin Pasero 已提交
633
				openFolderInNewWindow = true; // any other folders to open must open in new window then
E
Erich Gamma 已提交
634 635 636
			}

			// Open remaining ones
637
			allFoldersToOpen.forEach(folderToOpen => {
638
				if (windowsOnFolderPath.some(win => isEqual(win.openedFolderPath, folderToOpen, !isLinux /* ignorecase */))) {
E
Erich Gamma 已提交
639 640 641
					return; // ignore folders that are already open
				}

642
				// Do open folder
643
				usedWindows.push(this.doOpenFolderOrWorkspace(openConfig, { folderPath: folderToOpen }, openFolderInNewWindow, filesToOpen, filesToCreate, filesToDiff, filesToWait));
E
Erich Gamma 已提交
644 645 646 647

				// Reset these because we handled them
				filesToOpen = [];
				filesToCreate = [];
648
				filesToDiff = [];
649
				filesToWait = void 0;
E
Erich Gamma 已提交
650

B
Benjamin Pasero 已提交
651
				openFolderInNewWindow = true; // any other folders to open must open in new window then
E
Erich Gamma 已提交
652 653 654
			});
		}

655
		// Handle empty to restore
656
		if (emptyToRestore.length > 0) {
657
			emptyToRestore.forEach(emptyWindowBackupFolder => {
B
Benjamin Pasero 已提交
658
				usedWindows.push(this.openInBrowserWindow({
B
Benjamin Pasero 已提交
659 660 661 662 663 664
					userEnv: openConfig.userEnv,
					cli: openConfig.cli,
					initialStartup: openConfig.initialStartup,
					filesToOpen,
					filesToCreate,
					filesToDiff,
665
					filesToWait,
B
Benjamin Pasero 已提交
666
					forceNewWindow: true,
667
					emptyWindowBackupFolder
B
Benjamin Pasero 已提交
668
				}));
669

B
wip  
Benjamin Pasero 已提交
670 671 672 673
				// Reset these because we handled them
				filesToOpen = [];
				filesToCreate = [];
				filesToDiff = [];
674
				filesToWait = void 0;
B
wip  
Benjamin Pasero 已提交
675

B
Benjamin Pasero 已提交
676
				openFolderInNewWindow = true; // any other folders to open must open in new window then
677 678
			});
		}
B
Benjamin Pasero 已提交
679

680 681
		// Handle empty to open (only if no other window opened)
		if (usedWindows.length === 0) {
682
			for (let i = 0; i < emptyToOpen; i++) {
B
Benjamin Pasero 已提交
683
				usedWindows.push(this.openInBrowserWindow({
B
Benjamin Pasero 已提交
684 685 686
					userEnv: openConfig.userEnv,
					cli: openConfig.cli,
					initialStartup: openConfig.initialStartup,
687
					forceNewWindow: openFolderInNewWindow
B
Benjamin Pasero 已提交
688
				}));
E
Erich Gamma 已提交
689

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

694
		return arrays.distinct(usedWindows);
E
Erich Gamma 已提交
695 696
	}

697
	private doOpenFilesInExistingWindow(configuration: IOpenConfiguration, window: ICodeWindow, filesToOpen: IPath[], filesToCreate: IPath[], filesToDiff: IPath[], filesToWait: IPathsToWaitFor): ICodeWindow {
698 699 700
		window.focus(); // make sure window has focus

		window.ready().then(readyWindow => {
701 702
			const termProgram = configuration.userEnv ? configuration.userEnv['TERM_PROGRAM'] : void 0;
			readyWindow.send('vscode:openFiles', { filesToOpen, filesToCreate, filesToDiff, filesToWait, termProgram });
703
		});
B
Benjamin Pasero 已提交
704 705

		return window;
706 707
	}

708
	private doAddFoldersToExistingWidow(window: ICodeWindow, foldersToAdd: IPath[]): ICodeWindow {
709 710 711 712 713 714 715 716 717
		window.focus(); // make sure window has focus

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

		return window;
	}

B
Benjamin Pasero 已提交
718 719 720 721 722
	private doOpenFolderOrWorkspace(openConfig: IOpenConfiguration, folderOrWorkspace: IPathToOpen, forceNewWindow: boolean, filesToOpen: IPath[], filesToCreate: IPath[], filesToDiff: IPath[], filesToWait: IPathsToWaitFor, windowToUse?: ICodeWindow): ICodeWindow {
		if (!forceNewWindow && !windowToUse && typeof openConfig.contextWindowId === 'number') {
			windowToUse = this.getWindowById(openConfig.contextWindowId); // fix for https://github.com/Microsoft/vscode/issues/49587
		}

723 724 725 726
		const browserWindow = this.openInBrowserWindow({
			userEnv: openConfig.userEnv,
			cli: openConfig.cli,
			initialStartup: openConfig.initialStartup,
727
			workspace: folderOrWorkspace.workspace,
728 729 730 731
			folderPath: folderOrWorkspace.folderPath,
			filesToOpen,
			filesToCreate,
			filesToDiff,
732
			filesToWait,
B
Benjamin Pasero 已提交
733
			forceNewWindow,
734
			windowToUse
735 736 737 738 739
		});

		return browserWindow;
	}

B
Benjamin Pasero 已提交
740 741
	private getPathsToOpen(openConfig: IOpenConfiguration): IPathToOpen[] {
		let windowsToOpen: IPathToOpen[];
742
		let isCommandLineOrAPICall = false;
E
Erich Gamma 已提交
743

744
		// Extract paths: from API
B
Benjamin Pasero 已提交
745
		if (openConfig.pathsToOpen && openConfig.pathsToOpen.length > 0) {
746
			windowsToOpen = this.doExtractPathsFromAPI(openConfig);
747
			isCommandLineOrAPICall = true;
E
Erich Gamma 已提交
748 749
		}

B
Benjamin Pasero 已提交
750 751
		// Check for force empty
		else if (openConfig.forceEmpty) {
752
			windowsToOpen = [Object.create(null)];
E
Erich Gamma 已提交
753 754
		}

755
		// Extract paths: from CLI
B
Benjamin Pasero 已提交
756
		else if (openConfig.cli._.length > 0) {
757
			windowsToOpen = this.doExtractPathsFromCLI(openConfig.cli);
758
			isCommandLineOrAPICall = true;
B
Benjamin Pasero 已提交
759 760
		}

761
		// Extract windows: from previous session
B
Benjamin Pasero 已提交
762
		else {
763
			windowsToOpen = this.doGetWindowsFromLastSession();
B
Benjamin Pasero 已提交
764 765
		}

766 767
		// Convert multiple folders into workspace (if opened via API or CLI)
		// This will ensure to open these folders in one window instead of multiple
768 769
		// If we are in addMode, we should not do this because in that case all
		// folders should be added to the existing window.
770
		if (!openConfig.addMode && isCommandLineOrAPICall) {
771 772
			const foldersToOpen = windowsToOpen.filter(path => !!path.folderPath);
			if (foldersToOpen.length > 1) {
B
Benjamin Pasero 已提交
773
				const workspace = this.workspacesMainService.createWorkspaceSync(foldersToOpen.map(folder => ({ uri: URI.file(folder.folderPath) })));
774 775 776 777 778 779 780

				// Add workspace and remove folders thereby
				windowsToOpen.push({ workspace });
				windowsToOpen = windowsToOpen.filter(path => !path.folderPath);
			}
		}

781
		return windowsToOpen;
E
Erich Gamma 已提交
782 783
	}

784 785 786
	private doExtractPathsFromAPI(openConfig: IOpenConfiguration): IPath[] {
		let pathsToOpen = openConfig.pathsToOpen.map(pathToOpen => {
			const path = this.parsePath(pathToOpen, { gotoLineMode: openConfig.cli && openConfig.cli.goto, forceOpenWorkspaceAsFile: openConfig.forceOpenWorkspaceAsFile });
787 788 789

			// Warn if the requested path to open does not exist
			if (!path) {
790
				const options: Electron.MessageBoxOptions = {
791 792
					title: product.nameLong,
					type: 'info',
793 794 795
					buttons: [localize('ok', "OK")],
					message: localize('pathNotExistTitle', "Path does not exist"),
					detail: localize('pathNotExistDetail', "The path '{0}' does not seem to exist anymore on disk.", pathToOpen),
796 797 798
					noLink: true
				};

799
				this.dialogs.showMessageBox(options, this.getFocusedWindow());
800
			}
B
Benjamin Pasero 已提交
801

802 803 804 805 806 807 808 809 810 811
			return path;
		});

		// get rid of nulls
		pathsToOpen = arrays.coalesce(pathsToOpen);

		return pathsToOpen;
	}

	private doExtractPathsFromCLI(cli: ParsedArgs): IPath[] {
812
		const pathsToOpen = arrays.coalesce(cli._.map(candidate => this.parsePath(candidate, { ignoreFileNotFound: true, gotoLineMode: cli.goto })));
813 814
		if (pathsToOpen.length > 0) {
			return pathsToOpen;
B
Benjamin Pasero 已提交
815 816 817 818 819 820
		}

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

B
Benjamin Pasero 已提交
821
	private doGetWindowsFromLastSession(): IPathToOpen[] {
822 823
		const restoreWindows = this.getRestoreWindowsSetting();
		const lastActiveWindow = this.windowsState.lastActiveWindow;
B
Benjamin Pasero 已提交
824

825
		switch (restoreWindows) {
B
Benjamin Pasero 已提交
826

827
			// none: we always open an empty window
828 829
			case 'none':
				return [Object.create(null)];
B
Benjamin Pasero 已提交
830

831
			// one: restore last opened workspace/folder or empty window
832 833
			case 'one':
				if (lastActiveWindow) {
B
Benjamin Pasero 已提交
834

835
					// workspace
B
Benjamin Pasero 已提交
836 837 838 839 840 841
					const candidateWorkspace = lastActiveWindow.workspace;
					if (candidateWorkspace) {
						const validatedWorkspace = this.parsePath(candidateWorkspace.configPath);
						if (validatedWorkspace && validatedWorkspace.workspace) {
							return [validatedWorkspace];
						}
842 843 844 845 846
					}

					// folder (if path is valid)
					else if (lastActiveWindow.folderPath) {
						const validatedFolder = this.parsePath(lastActiveWindow.folderPath);
B
Benjamin Pasero 已提交
847
						if (validatedFolder && validatedFolder.folderPath) {
848
							return [validatedFolder];
849 850
						}
					}
B
Benjamin Pasero 已提交
851

852
					// otherwise use backup path to restore empty windows
853 854 855 856 857 858 859 860 861 862
					else if (lastActiveWindow.backupPath) {
						return [{ backupPath: lastActiveWindow.backupPath }];
					}
				}
				break;

			// all: restore all windows
			// folders: restore last opened folders only
			case 'all':
			case 'folders':
B
Benjamin Pasero 已提交
863
				const windowsToOpen: IPathToOpen[] = [];
864

865
				// Workspaces
B
Benjamin Pasero 已提交
866
				const workspaceCandidates = this.windowsState.openedWindows.filter(w => !!w.workspace).map(w => w.workspace);
867
				if (lastActiveWindow && lastActiveWindow.workspace) {
B
Benjamin Pasero 已提交
868
					workspaceCandidates.push(lastActiveWindow.workspace);
869
				}
B
Benjamin Pasero 已提交
870
				windowsToOpen.push(...workspaceCandidates.map(candidate => this.parsePath(candidate.configPath)).filter(window => window && window.workspace));
B
Benjamin Pasero 已提交
871

872
				// Folders
B
Benjamin Pasero 已提交
873
				const folderCandidates = this.windowsState.openedWindows.filter(w => !!w.folderPath).map(w => w.folderPath);
874
				if (lastActiveWindow && lastActiveWindow.folderPath) {
B
Benjamin Pasero 已提交
875
					folderCandidates.push(lastActiveWindow.folderPath);
876
				}
B
Benjamin Pasero 已提交
877
				windowsToOpen.push(...folderCandidates.map(candidate => this.parsePath(candidate)).filter(window => window && window.folderPath));
B
Benjamin Pasero 已提交
878

879 880
				// Windows that were Empty
				if (restoreWindows === 'all') {
881 882
					const lastOpenedEmpty = this.windowsState.openedWindows.filter(w => !w.workspace && !w.folderPath && w.backupPath).map(w => w.backupPath);
					const lastActiveEmpty = lastActiveWindow && !lastActiveWindow.workspace && !lastActiveWindow.folderPath && lastActiveWindow.backupPath;
883 884 885 886 887 888 889 890 891 892 893 894
					if (lastActiveEmpty) {
						lastOpenedEmpty.push(lastActiveEmpty);
					}

					windowsToOpen.push(...lastOpenedEmpty.map(backupPath => ({ backupPath })));
				}

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

				break;
B
Benjamin Pasero 已提交
895
		}
E
Erich Gamma 已提交
896

897
		// Always fallback to empty window
B
Benjamin Pasero 已提交
898
		return [Object.create(null)];
E
Erich Gamma 已提交
899 900
	}

901 902 903 904 905
	private getRestoreWindowsSetting(): RestoreWindowsSetting {
		let restoreWindows: RestoreWindowsSetting;
		if (this.lifecycleService.wasRestarted) {
			restoreWindows = 'all'; // always reopen all windows when an update was applied
		} else {
906
			const windowConfig = this.configurationService.getValue<IWindowSettings>('window');
907
			restoreWindows = ((windowConfig && windowConfig.restoreWindows) || 'one') as RestoreWindowsSetting;
908 909 910 911 912 913 914 915 916

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

		return restoreWindows;
	}

B
Benjamin Pasero 已提交
917
	private parsePath(anyPath: string, options?: { ignoreFileNotFound?: boolean, gotoLineMode?: boolean, forceOpenWorkspaceAsFile?: boolean; }): IPathToOpen {
E
Erich Gamma 已提交
918 919 920 921
		if (!anyPath) {
			return null;
		}

922
		let parsedPath: IPathWithLineAndColumn;
923 924 925

		const gotoLineMode = options && options.gotoLineMode;
		if (options && options.gotoLineMode) {
J
Joao Moreno 已提交
926
			parsedPath = parseLineAndColumnAware(anyPath);
E
Erich Gamma 已提交
927 928 929
			anyPath = parsedPath.path;
		}

930
		const candidate = normalize(anyPath);
E
Erich Gamma 已提交
931
		try {
B
Benjamin Pasero 已提交
932
			const candidateStat = fs.statSync(candidate);
E
Erich Gamma 已提交
933
			if (candidateStat) {
934
				if (candidateStat.isFile()) {
935

936 937
					// Workspace (unless disabled via flag)
					if (!options || !options.forceOpenWorkspaceAsFile) {
B
Benjamin Pasero 已提交
938
						const workspace = this.workspacesMainService.resolveWorkspaceSync(candidate);
939
						if (workspace) {
940
							return { workspace: { id: workspace.id, configPath: workspace.configPath } };
941
						}
942 943 944
					}

					// File
945
					return {
946
						filePath: candidate,
E
Erich Gamma 已提交
947
						lineNumber: gotoLineMode ? parsedPath.line : void 0,
948
						columnNumber: gotoLineMode ? parsedPath.column : void 0
949 950 951
					};
				}

952 953 954 955 956 957 958 959
				// 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 {
						folderPath: candidate
					};
				}
E
Erich Gamma 已提交
960 961
			}
		} catch (error) {
B
Benjamin Pasero 已提交
962
			this.historyMainService.removeFromRecentlyOpened([candidate]); // since file does not seem to exist anymore, remove from recent
963

964
			if (options && options.ignoreFileNotFound) {
E
Erich Gamma 已提交
965 966 967 968 969 970 971
				return { filePath: candidate, createFilePath: true }; // assume this is a file that does not yet exist
			}
		}

		return null;
	}

B
Benjamin Pasero 已提交
972 973 974
	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
975
		const windowConfig = this.configurationService.getValue<IWindowSettings>('window');
976 977 978
		const openFolderInNewWindowConfig = (windowConfig && windowConfig.openFoldersInNewWindow) || 'default' /* default */;
		const openFilesInNewWindowConfig = (windowConfig && windowConfig.openFilesInNewWindow) || 'off' /* default */;

B
Benjamin Pasero 已提交
979
		let openFolderInNewWindow = (openConfig.preferNewWindow || openConfig.forceNewWindow) && !openConfig.forceReuseWindow;
980 981
		if (!openConfig.forceNewWindow && !openConfig.forceReuseWindow && (openFolderInNewWindowConfig === 'on' || openFolderInNewWindowConfig === 'off')) {
			openFolderInNewWindow = (openFolderInNewWindowConfig === 'on');
B
Benjamin Pasero 已提交
982 983 984 985 986 987 988
		}

		// 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 {
989 990 991 992 993 994 995 996 997 998 999 1000 1001

			// 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 已提交
1002 1003
			}

1004
			// finally check for overrides of default
1005 1006
			if (!openConfig.cli.extensionDevelopmentPath && (openFilesInNewWindowConfig === 'on' || openFilesInNewWindowConfig === 'off')) {
				openFilesInNewWindow = (openFilesInNewWindowConfig === 'on');
B
Benjamin Pasero 已提交
1007 1008 1009 1010 1011 1012
			}
		}

		return { openFolderInNewWindow, openFilesInNewWindow };
	}

B
Benjamin Pasero 已提交
1013
	public openExtensionDevelopmentHostWindow(openConfig: IOpenConfiguration): void {
E
Erich Gamma 已提交
1014

B
Benjamin Pasero 已提交
1015 1016 1017
		// 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.
1018 1019 1020 1021
		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 已提交
1022

B
Benjamin Pasero 已提交
1023 1024
			return;
		}
E
Erich Gamma 已提交
1025

1026
		// Fill in previously opened workspace unless an explicit path is provided and we are not unit testing
B
Benjamin Pasero 已提交
1027
		if (openConfig.cli._.length === 0 && !openConfig.cli.extensionTestsPath) {
1028 1029 1030 1031
			const extensionDevelopmentWindowState = this.windowsState.lastPluginDevelopmentHostWindow;
			const workspaceToOpen = extensionDevelopmentWindowState && (extensionDevelopmentWindowState.workspace || extensionDevelopmentWindowState.folderPath);
			if (workspaceToOpen) {
				openConfig.cli._ = [isSingleFolderWorkspaceIdentifier(workspaceToOpen) ? workspaceToOpen : workspaceToOpen.configPath];
E
Erich Gamma 已提交
1032 1033 1034
			}
		}

1035 1036 1037
		// Make sure we are not asked to open a workspace or folder that is already opened
		if (openConfig.cli._.some(path => !!findWindowOnWorkspaceOrFolderPath(WindowsManager.WINDOWS, path))) {
			openConfig.cli._ = [];
E
Erich Gamma 已提交
1038 1039
		}

B
Benjamin Pasero 已提交
1040 1041
		// Open it
		this.open({ context: openConfig.context, cli: openConfig.cli, forceNewWindow: true, forceEmpty: openConfig.cli._.length === 0, userEnv: openConfig.userEnv });
E
Erich Gamma 已提交
1042 1043
	}

1044
	private openInBrowserWindow(options: IOpenBrowserWindowOptions): ICodeWindow {
B
Benjamin Pasero 已提交
1045 1046 1047 1048

		// Build IWindowConfiguration from config and options
		const configuration: IWindowConfiguration = mixin({}, options.cli); // inherit all properties from CLI
		configuration.appRoot = this.environmentService.appRoot;
1049
		configuration.machineId = this.machineId;
B
Benjamin Pasero 已提交
1050 1051 1052
		configuration.execPath = process.execPath;
		configuration.userEnv = assign({}, this.initialUserEnv, options.userEnv || {});
		configuration.isInitialStartup = options.initialStartup;
1053
		configuration.workspace = options.workspace;
1054
		configuration.folderPath = options.folderPath;
B
Benjamin Pasero 已提交
1055 1056 1057
		configuration.filesToOpen = options.filesToOpen;
		configuration.filesToCreate = options.filesToCreate;
		configuration.filesToDiff = options.filesToDiff;
1058
		configuration.filesToWait = options.filesToWait;
B
Benjamin Pasero 已提交
1059 1060
		configuration.nodeCachedDataDir = this.environmentService.nodeCachedDataDir;

1061
		// if we know the backup folder upfront (for empty windows to restore), we can set it
1062
		// directly here which helps for restoring UI state associated with that window.
B
Benjamin Pasero 已提交
1063
		// For all other cases we first call into registerEmptyWindowBackupSync() to set it before
1064
		// loading the window.
1065
		if (options.emptyWindowBackupFolder) {
1066
			configuration.backupPath = join(this.environmentService.backupHome, options.emptyWindowBackupFolder);
1067 1068
		}

1069
		let window: ICodeWindow;
B
Benjamin Pasero 已提交
1070
		if (!options.forceNewWindow) {
1071 1072 1073
			window = options.windowToUse || this.getLastActiveWindow();
			if (window) {
				window.focus();
E
Erich Gamma 已提交
1074 1075 1076 1077
			}
		}

		// New window
1078
		if (!window) {
1079
			const windowConfig = this.configurationService.getValue<IWindowSettings>('window');
1080 1081 1082 1083 1084 1085 1086 1087 1088 1089
			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 {
1090
				allowFullscreen = this.lifecycleService.wasRestarted || (windowConfig && windowConfig.restoreFullscreen);
1091 1092 1093 1094 1095
			}

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

1097
			window = this.instantiationService.createInstance(CodeWindow, {
1098
				state,
1099
				extensionDevelopmentPath: configuration.extensionDevelopmentPath,
1100
				isExtensionTestHost: !!configuration.extensionTestsPath
1101
			});
1102

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

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

E
Erich Gamma 已提交
1109
			// Window Events
1110 1111 1112 1113 1114
			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 已提交
1115 1116

			// Lifecycle
1117
			this.lifecycleService.registerWindow(window);
E
Erich Gamma 已提交
1118 1119 1120 1121 1122 1123
		}

		// Existing window
		else {

			// Some configuration things get inherited if the window is being reused and we are
B
Benjamin Pasero 已提交
1124
			// in extension development host mode. These options are all development related.
1125
			const currentWindowConfig = window.config;
A
Alex Dima 已提交
1126 1127
			if (!configuration.extensionDevelopmentPath && currentWindowConfig && !!currentWindowConfig.extensionDevelopmentPath) {
				configuration.extensionDevelopmentPath = currentWindowConfig.extensionDevelopmentPath;
B
Benjamin Pasero 已提交
1128
				configuration.verbose = currentWindowConfig.verbose;
1129
				configuration.debugBrkPluginHost = currentWindowConfig.debugBrkPluginHost;
1130
				configuration.debugId = currentWindowConfig.debugId;
1131
				configuration.debugPluginHost = currentWindowConfig.debugPluginHost;
1132
				configuration['extensions-dir'] = currentWindowConfig['extensions-dir'];
E
Erich Gamma 已提交
1133 1134 1135 1136
			}
		}

		// Only load when the window has not vetoed this
1137
		this.lifecycleService.unload(window, UnloadReason.LOAD).done(veto => {
E
Erich Gamma 已提交
1138 1139
			if (!veto) {

B
Benjamin Pasero 已提交
1140 1141
				// Register window for backups
				if (!configuration.extensionDevelopmentPath) {
1142
					if (configuration.workspace) {
B
Benjamin Pasero 已提交
1143
						configuration.backupPath = this.backupMainService.registerWorkspaceBackupSync(configuration.workspace);
1144
					} else if (configuration.folderPath) {
B
Benjamin Pasero 已提交
1145
						configuration.backupPath = this.backupMainService.registerFolderBackupSync(configuration.folderPath);
B
Benjamin Pasero 已提交
1146
					} else {
B
Benjamin Pasero 已提交
1147
						configuration.backupPath = this.backupMainService.registerEmptyWindowBackupSync(options.emptyWindowBackupFolder);
B
Benjamin Pasero 已提交
1148
					}
B
Benjamin Pasero 已提交
1149 1150
				}

E
Erich Gamma 已提交
1151
				// Load it
1152
				window.load(configuration);
1153 1154 1155

				// Signal event
				this._onWindowLoad.fire(window.id);
E
Erich Gamma 已提交
1156 1157
			}
		});
1158

1159
		return window;
E
Erich Gamma 已提交
1160 1161
	}

1162
	private getNewWindowState(configuration: IWindowConfiguration): INewWindowState {
1163
		const lastActive = this.getLastActiveWindow();
E
Erich Gamma 已提交
1164

1165 1166
		// Restore state unless we are running extension tests
		if (!configuration.extensionTestsPath) {
E
Erich Gamma 已提交
1167

1168 1169 1170
			// extension development host Window - load from stored settings if any
			if (!!configuration.extensionDevelopmentPath && this.windowsState.lastPluginDevelopmentHostWindow) {
				return this.windowsState.lastPluginDevelopmentHostWindow.uiState;
1171 1172
			}

1173 1174 1175 1176 1177 1178 1179 1180 1181 1182 1183 1184 1185 1186
			// 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
			if (configuration.folderPath) {
				const stateForFolder = this.windowsState.openedWindows.filter(o => isEqual(o.folderPath, configuration.folderPath, !isLinux /* ignorecase */)).map(o => o.uiState);
				if (stateForFolder.length) {
					return stateForFolder[0];
				}
1187 1188
			}

1189 1190 1191 1192 1193 1194
			// 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 已提交
1195 1196
			}

1197 1198 1199 1200 1201
			// First Window
			const lastActiveState = this.lastClosedWindowState || this.windowsState.lastActiveWindow;
			if (!lastActive && lastActiveState) {
				return lastActiveState.uiState;
			}
E
Erich Gamma 已提交
1202 1203 1204 1205 1206 1207 1208
		}

		//
		// 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
1209
		let displayToUse: Electron.Display;
B
Benjamin Pasero 已提交
1210
		const displays = screen.getAllDisplays();
E
Erich Gamma 已提交
1211 1212 1213 1214 1215 1216 1217 1218 1219 1220

		// 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 已提交
1221
			if (isMacintosh) {
B
Benjamin Pasero 已提交
1222
				const cursorPoint = screen.getCursorScreenPoint();
E
Erich Gamma 已提交
1223 1224 1225 1226 1227 1228 1229 1230
				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());
			}

1231
			// fallback to primary display or first display
E
Erich Gamma 已提交
1232
			if (!displayToUse) {
1233
				displayToUse = screen.getPrimaryDisplay() || displays[0];
E
Erich Gamma 已提交
1234 1235 1236
			}
		}

1237 1238 1239
		// 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.
1240
		let state = defaultWindowState() as INewWindowState;
1241 1242
		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 已提交
1243

1244
		// Check for newWindowDimensions setting and adjust accordingly
1245
		const windowConfig = this.configurationService.getValue<IWindowSettings>('window');
1246 1247 1248 1249 1250 1251 1252 1253 1254
		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 已提交
1255 1256 1257 1258 1259 1260 1261
				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;
				}

1262 1263 1264 1265 1266 1267 1268 1269
				ensureNoOverlap = false;
			}
		}

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

1270 1271
		state.hasDefaultState = true; // flag as default state

1272
		return state;
E
Erich Gamma 已提交
1273 1274
	}

J
Joao Moreno 已提交
1275
	private ensureNoOverlap(state: ISingleWindowState): ISingleWindowState {
E
Erich Gamma 已提交
1276 1277 1278 1279
		if (WindowsManager.WINDOWS.length === 0) {
			return state;
		}

1280 1281
		const existingWindowBounds = WindowsManager.WINDOWS.map(win => win.getBounds());
		while (existingWindowBounds.some(b => b.x === state.x || b.y === state.y)) {
E
Erich Gamma 已提交
1282 1283 1284 1285 1286 1287 1288
			state.x += 30;
			state.y += 30;
		}

		return state;
	}

1289
	public reload(win: ICodeWindow, cli?: ParsedArgs): void {
B
Benjamin Pasero 已提交
1290 1291 1292 1293

		// Only reload when the window has not vetoed this
		this.lifecycleService.unload(win, UnloadReason.RELOAD).done(veto => {
			if (!veto) {
1294
				win.reload(void 0, cli);
B
Benjamin Pasero 已提交
1295 1296 1297 1298 1299 1300 1301

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

1302
	public closeWorkspace(win: ICodeWindow): void {
1303 1304 1305 1306 1307 1308
		this.openInBrowserWindow({
			cli: this.environmentService.args,
			windowToUse: win
		});
	}

1309
	public saveAndEnterWorkspace(win: ICodeWindow, path: string): TPromise<IEnterWorkspaceResult> {
1310
		return this.workspacesManager.saveAndEnterWorkspace(win, path).then(result => this.doEnterWorkspace(win, result));
1311
	}
1312

1313
	public createAndEnterWorkspace(win: ICodeWindow, folders?: IWorkspaceFolderCreationData[], path?: string): TPromise<IEnterWorkspaceResult> {
1314
		return this.workspacesManager.createAndEnterWorkspace(win, folders, path).then(result => this.doEnterWorkspace(win, result));
1315
	}
1316

1317
	private doEnterWorkspace(win: ICodeWindow, result: IEnterWorkspaceResult): IEnterWorkspaceResult {
1318

1319
		// Mark as recently opened
B
Benjamin Pasero 已提交
1320
		this.historyMainService.addRecentlyOpened([result.workspace], []);
1321

1322 1323 1324
		// Trigger Eevent to indicate load of workspace into window
		this._onWindowReady.fire(win);

1325
		return result;
1326 1327
	}

1328 1329
	public pickWorkspaceAndOpen(options: INativeOpenDialogOptions): void {
		this.workspacesManager.pickWorkspaceAndOpen(options);
1330 1331 1332
	}

	private onBeforeWindowUnload(e: IWindowUnloadEvent): void {
1333 1334
		const windowClosing = (e.reason === UnloadReason.CLOSE);
		const windowLoading = (e.reason === UnloadReason.LOAD);
1335 1336 1337 1338 1339
		if (!windowClosing && !windowLoading) {
			return; // only interested when window is closing or loading
		}

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

1344
		if (e.window.config && !!e.window.config.extensionDevelopmentPath) {
1345 1346 1347 1348
			// do not ask to save workspace when doing extension development
			// but still delete it.
			this.workspacesMainService.deleteUntitledWorkspaceSync(workspace);
			return;
1349 1350
		}

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

1355
		// Handle untitled workspaces with prompt as needed
B
Benjamin Pasero 已提交
1356 1357 1358 1359 1360 1361 1362 1363 1364 1365 1366
		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
			return TPromise.timeout(0).then(() => veto);
		}));
1367 1368
	}

1369
	public focusLastActive(cli: ParsedArgs, context: OpenContext): ICodeWindow {
B
Benjamin Pasero 已提交
1370
		const lastActive = this.getLastActiveWindow();
E
Erich Gamma 已提交
1371
		if (lastActive) {
B
Benjamin Pasero 已提交
1372
			lastActive.focus();
1373 1374

			return lastActive;
E
Erich Gamma 已提交
1375 1376
		}

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

1381
	public getLastActiveWindow(): ICodeWindow {
1382
		return getLastActiveWindow(WindowsManager.WINDOWS);
E
Erich Gamma 已提交
1383 1384
	}

J
Joao Moreno 已提交
1385 1386
	public openNewWindow(context: OpenContext): ICodeWindow[] {
		return this.open({ context, cli: this.environmentService.args, forceNewWindow: true, forceEmpty: true });
E
Erich Gamma 已提交
1387 1388
	}

1389
	public waitForWindowCloseOrLoad(windowId: number): TPromise<void> {
1390
		return new TPromise<void>(c => {
1391
			function handler(id: number) {
1392
				if (id === windowId) {
1393 1394 1395
					closeListener.dispose();
					loadListener.dispose();

1396 1397
					c(null);
				}
1398 1399 1400 1401
			}

			const closeListener = this.onWindowClose(id => handler(id));
			const loadListener = this.onWindowLoad(id => handler(id));
1402 1403 1404
		});
	}

E
Erich Gamma 已提交
1405 1406 1407 1408
	public sendToFocused(channel: string, ...args: any[]): void {
		const focusedWindow = this.getFocusedWindow() || this.getLastActiveWindow();

		if (focusedWindow) {
1409
			focusedWindow.sendWhenReady(channel, ...args);
E
Erich Gamma 已提交
1410 1411 1412
		}
	}

1413
	public sendToAll(channel: string, payload?: any, windowIdsToIgnore?: number[]): void {
1414
		WindowsManager.WINDOWS.forEach(w => {
B
Benjamin Pasero 已提交
1415
			if (windowIdsToIgnore && windowIdsToIgnore.indexOf(w.id) >= 0) {
E
Erich Gamma 已提交
1416 1417 1418
				return; // do not send if we are instructed to ignore it
			}

1419
			w.sendWhenReady(channel, payload);
E
Erich Gamma 已提交
1420 1421 1422
		});
	}

1423
	public getFocusedWindow(): ICodeWindow {
B
Benjamin Pasero 已提交
1424
		const win = BrowserWindow.getFocusedWindow();
E
Erich Gamma 已提交
1425 1426 1427 1428 1429 1430 1431
		if (win) {
			return this.getWindowById(win.id);
		}

		return null;
	}

1432
	public getWindowById(windowId: number): ICodeWindow {
1433
		const res = WindowsManager.WINDOWS.filter(w => w.id === windowId);
E
Erich Gamma 已提交
1434 1435 1436 1437 1438 1439 1440
		if (res && res.length === 1) {
			return res[0];
		}

		return null;
	}

1441
	public getWindows(): ICodeWindow[] {
E
Erich Gamma 已提交
1442 1443 1444 1445 1446 1447 1448
		return WindowsManager.WINDOWS;
	}

	public getWindowCount(): number {
		return WindowsManager.WINDOWS.length;
	}

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

		// Unresponsive
		if (error === WindowError.UNRESPONSIVE) {
1454
			this.dialogs.showMessageBox({
B
Benjamin Pasero 已提交
1455
				title: product.nameLong,
E
Erich Gamma 已提交
1456
				type: 'warning',
1457
				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"))],
1458 1459
				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 已提交
1460
				noLink: true
1461 1462 1463 1464
			}, window).then(result => {
				if (!window.win) {
					return; // Return early if the window has been going down already
				}
1465

1466 1467 1468 1469 1470 1471 1472
				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 已提交
1473 1474 1475 1476
		}

		// Crashed
		else {
1477
			this.dialogs.showMessageBox({
B
Benjamin Pasero 已提交
1478
				title: product.nameLong,
E
Erich Gamma 已提交
1479
				type: 'warning',
1480
				buttons: [mnemonicButtonLabel(localize({ key: 'reopen', comment: ['&& denotes a mnemonic'] }, "&&Reopen")), mnemonicButtonLabel(localize({ key: 'close', comment: ['&& denotes a mnemonic'] }, "&&Close"))],
1481 1482
				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 已提交
1483
				noLink: true
1484 1485 1486 1487
			}, window).then(result => {
				if (!window.win) {
					return; // Return early if the window has been going down already
				}
1488

1489 1490 1491 1492 1493 1494 1495
				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 已提交
1496 1497 1498
		}
	}

1499
	private onWindowClosed(win: ICodeWindow): void {
E
Erich Gamma 已提交
1500 1501 1502 1503 1504

		// Tell window
		win.dispose();

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

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

B
Benjamin Pasero 已提交
1513 1514
	public pickFileFolderAndOpen(options: INativeOpenDialogOptions): void {
		this.doPickAndOpen(options, true /* pick folders */, true /* pick files */);
1515 1516
	}

B
Benjamin Pasero 已提交
1517 1518
	public pickFolderAndOpen(options: INativeOpenDialogOptions): void {
		this.doPickAndOpen(options, true /* pick folders */, false /* pick files */);
1519 1520
	}

B
Benjamin Pasero 已提交
1521 1522 1523 1524 1525 1526 1527 1528 1529 1530 1531 1532 1533 1534 1535 1536 1537 1538 1539 1540 1541 1542 1543 1544 1545 1546
	public pickFileAndOpen(options: INativeOpenDialogOptions): void {
		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 已提交
1547
				// __GDPR__TODO__ classify event
B
Benjamin Pasero 已提交
1548 1549 1550 1551 1552 1553 1554 1555
				internalOptions.telemetryEventName = 'openFileFolder';
			} else if (pickFolders) {
				internalOptions.telemetryEventName = 'openFolder';
			} else {
				internalOptions.telemetryEventName = 'openFile';
			}
		}

1556 1557 1558
		this.dialogs.pickAndOpen(internalOptions);
	}

1559
	public showMessageBox(options: Electron.MessageBoxOptions, win?: ICodeWindow): TPromise<IMessageBoxResult> {
1560 1561 1562
		return this.dialogs.showMessageBox(options, win);
	}

1563
	public showSaveDialog(options: Electron.SaveDialogOptions, win?: ICodeWindow): TPromise<string> {
1564 1565 1566
		return this.dialogs.showSaveDialog(options, win);
	}

1567
	public showOpenDialog(options: Electron.OpenDialogOptions, win?: ICodeWindow): TPromise<string[]> {
1568
		return this.dialogs.showOpenDialog(options, win);
B
Benjamin Pasero 已提交
1569 1570 1571 1572 1573 1574
	}

	public quit(): void {

		// 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.
1575 1576 1577
		const window = this.getFocusedWindow();
		if (window && window.isExtensionDevelopmentHost && this.getWindowCount() > 1) {
			window.win.close();
B
Benjamin Pasero 已提交
1578 1579 1580 1581 1582 1583 1584 1585
		}

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

B
Benjamin Pasero 已提交
1589
interface IInternalNativeOpenDialogOptions extends INativeOpenDialogOptions {
B
Benjamin Pasero 已提交
1590 1591 1592 1593
	pickFolders?: boolean;
	pickFiles?: boolean;
}

1594
class Dialogs {
B
Benjamin Pasero 已提交
1595

1596
	private static readonly workingDirPickerStorageKey = 'pickerWorkingDir';
1597

1598 1599 1600
	private mapWindowToDialogQueue: Map<number, Queue<any>>;
	private noWindowDialogQueue: Queue<any>;

B
Benjamin Pasero 已提交
1601 1602 1603
	constructor(
		private environmentService: IEnvironmentService,
		private telemetryService: ITelemetryService,
B
Benjamin Pasero 已提交
1604
		private stateService: IStateService,
B
Benjamin Pasero 已提交
1605
		private windowsMainService: IWindowsMainService,
B
Benjamin Pasero 已提交
1606
	) {
1607 1608
		this.mapWindowToDialogQueue = new Map<number, Queue<any>>();
		this.noWindowDialogQueue = new Queue<any>();
B
Benjamin Pasero 已提交
1609 1610
	}

B
Benjamin Pasero 已提交
1611
	public pickAndOpen(options: INativeOpenDialogOptions): void {
1612
		this.getFileOrFolderPaths(options).then(paths => {
B
Benjamin Pasero 已提交
1613 1614 1615 1616
			const numberOfPaths = paths ? paths.length : 0;

			// Telemetry
			if (options.telemetryEventName) {
K
kieferrm 已提交
1617
				// __GDPR__TODO__ Dynamic event names and dynamic properties. Can not be registered statically.
B
Benjamin Pasero 已提交
1618 1619 1620 1621 1622 1623 1624 1625 1626
				this.telemetryService.publicLog(options.telemetryEventName, {
					...options.telemetryExtraData,
					outcome: numberOfPaths ? 'success' : 'canceled',
					numberOfPaths
				});
			}

			// Open
			if (numberOfPaths) {
1627 1628 1629 1630 1631 1632 1633
				this.windowsMainService.open({
					context: OpenContext.DIALOG,
					cli: this.environmentService.args,
					pathsToOpen: paths,
					forceNewWindow: options.forceNewWindow,
					forceOpenWorkspaceAsFile: options.dialogOptions && !equals(options.dialogOptions.filters, WORKSPACE_FILTER)
				});
1634 1635 1636 1637
			}
		});
	}

1638
	private getFileOrFolderPaths(options: IInternalNativeOpenDialogOptions): TPromise<string[]> {
1639

B
Benjamin Pasero 已提交
1640 1641 1642 1643 1644 1645 1646
		// Ensure dialog options
		if (!options.dialogOptions) {
			options.dialogOptions = Object.create(null);
		}

		// Ensure defaultPath
		if (!options.dialogOptions.defaultPath) {
1647
			options.dialogOptions.defaultPath = this.stateService.getItem<string>(Dialogs.workingDirPickerStorageKey);
1648 1649
		}

B
Benjamin Pasero 已提交
1650 1651 1652 1653 1654 1655 1656 1657 1658 1659 1660 1661 1662
		// 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'];
		}

1663 1664 1665 1666
		if (isMacintosh) {
			options.dialogOptions.properties.push('treatPackageAsDirectory'); // always drill into .app files
		}

B
Benjamin Pasero 已提交
1667
		// Show Dialog
1668
		const focusedWindow = this.windowsMainService.getWindowById(options.windowId) || this.windowsMainService.getFocusedWindow();
1669 1670 1671 1672 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 1698 1699

		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]));

				return paths;
			}

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

	public showMessageBox(options: Electron.MessageBoxOptions, window?: ICodeWindow): TPromise<IMessageBoxResult> {
		return this.getDialogQueue(window).queue(() => {
			return new TPromise((c, e) => {
B
Benjamin Pasero 已提交
1700 1701 1702
				dialog.showMessageBox(window ? window.win : void 0, options, (response: number, checkboxChecked: boolean) => {
					c({ button: response, checkboxChecked });
				});
1703 1704 1705 1706 1707
			});
		});
	}

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

1709 1710 1711
		function normalizePath(path: string): string {
			if (path && isMacintosh) {
				path = normalizeNFC(path); // normalize paths returned from the OS
1712
			}
1713

1714 1715
			return path;
		}
1716

1717 1718
		return this.getDialogQueue(window).queue(() => {
			return new TPromise((c, e) => {
B
Benjamin Pasero 已提交
1719 1720 1721
				dialog.showSaveDialog(window ? window.win : void 0, options, path => {
					c(normalizePath(path));
				});
1722 1723 1724 1725 1726
			});
		});
	}

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

1728 1729 1730 1731 1732 1733
		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;
1734
		}
B
Benjamin Pasero 已提交
1735

1736 1737
		return this.getDialogQueue(window).queue(() => {
			return new TPromise((c, e) => {
B
Benjamin Pasero 已提交
1738 1739 1740 1741 1742 1743 1744 1745 1746 1747 1748 1749 1750 1751 1752 1753

				// 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 已提交
1754
				});
1755 1756
			});
		});
1757
	}
1758 1759 1760 1761 1762
}

class WorkspacesManager {

	constructor(
1763 1764
		private workspacesMainService: IWorkspacesMainService,
		private backupMainService: IBackupMainService,
1765 1766 1767 1768 1769
		private environmentService: IEnvironmentService,
		private windowsMainService: IWindowsMainService
	) {
	}

1770
	public saveAndEnterWorkspace(window: ICodeWindow, path: string): TPromise<IEnterWorkspaceResult> {
1771 1772 1773 1774 1775 1776 1777
		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);
	}

1778
	public createAndEnterWorkspace(window: ICodeWindow, folders?: IWorkspaceFolderCreationData[], path?: string): TPromise<IEnterWorkspaceResult> {
1779
		if (!window || !window.win || window.readyState !== ReadyState.READY) {
1780 1781 1782
			return TPromise.as(null); // return early if the window is not ready or disposed
		}

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

1788
			return this.workspacesMainService.createWorkspace(folders).then(workspace => {
1789 1790
				return this.doSaveAndOpenWorkspace(window, workspace, path);
			});
1791
		});
1792

1793 1794
	}

1795
	private isValidTargetWorkspacePath(window: ICodeWindow, path?: string): TPromise<boolean> {
1796
		if (!path) {
1797
			return TPromise.wrap(true);
1798 1799 1800
		}

		if (window.openedWorkspace && window.openedWorkspace.configPath === path) {
1801
			return TPromise.wrap(false); // window is already opened on a workspace with that path
1802 1803 1804
		}

		// Prevent overwriting a workspace that is currently opened in another window
1805
		if (findWindowOnWorkspace(this.windowsMainService.getWindows(), { id: this.workspacesMainService.getWorkspaceId(path), configPath: path })) {
1806 1807 1808 1809 1810
			const options: Electron.MessageBoxOptions = {
				title: product.nameLong,
				type: 'info',
				buttons: [localize('ok', "OK")],
				message: localize('workspaceOpenedMessage', "Unable to save workspace '{0}'", basename(path)),
1811
				detail: localize('workspaceOpenedDetail', "The workspace is already opened in another window. Please close that window first and then try again."),
1812 1813 1814
				noLink: true
			};

1815
			return this.windowsMainService.showMessageBox(options, this.windowsMainService.getFocusedWindow()).then(() => false);
1816 1817
		}

1818
		return TPromise.wrap(true); // OK
1819 1820
	}

1821
	private doSaveAndOpenWorkspace(window: ICodeWindow, workspace: IWorkspaceIdentifier, path?: string): TPromise<IEnterWorkspaceResult> {
1822 1823
		let savePromise: TPromise<IWorkspaceIdentifier>;
		if (path) {
1824
			savePromise = this.workspacesMainService.saveWorkspace(workspace, path);
1825 1826 1827 1828 1829 1830 1831
		} else {
			savePromise = TPromise.as(workspace);
		}

		return savePromise.then(workspace => {
			window.focus();

1832 1833 1834
			// Register window for backups and migrate current backups over
			let backupPath: string;
			if (!window.config.extensionDevelopmentPath) {
1835
				backupPath = this.backupMainService.registerWorkspaceBackupSync(workspace, window.config.backupPath);
1836
			}
1837

1838 1839 1840 1841
			// Update window configuration properly based on transition to workspace
			window.config.folderPath = void 0;
			window.config.workspace = workspace;
			window.config.backupPath = backupPath;
1842

1843
			return { workspace, backupPath };
1844 1845 1846
		});
	}

1847 1848
	public pickWorkspaceAndOpen(options: INativeOpenDialogOptions): void {
		const window = this.windowsMainService.getWindowById(options.windowId) || this.windowsMainService.getFocusedWindow() || this.windowsMainService.getLastActiveWindow();
1849 1850 1851 1852 1853 1854 1855 1856

		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'],
1857
				defaultPath: options.dialogOptions && options.dialogOptions.defaultPath
1858
			},
1859 1860 1861
			forceNewWindow: options.forceNewWindow,
			telemetryEventName: options.telemetryEventName,
			telemetryExtraData: options.telemetryExtraData
1862 1863 1864
		});
	}

1865
	public promptToSaveUntitledWorkspace(window: ICodeWindow, workspace: IWorkspaceIdentifier): TPromise<boolean> {
1866 1867 1868 1869 1870 1871 1872 1873 1874 1875 1876 1877 1878 1879 1880 1881 1882 1883 1884 1885 1886 1887 1888 1889 1890 1891 1892 1893 1894 1895 1896 1897 1898
		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;
		}

1899 1900 1901 1902 1903 1904 1905 1906 1907
		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:
1908
					this.workspacesMainService.deleteUntitledWorkspaceSync(workspace);
1909 1910 1911 1912 1913 1914 1915 1916 1917 1918 1919
					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) {
1920
							return this.workspacesMainService.saveWorkspace(workspace, target).then(() => false, () => false);
1921
						}
1922

1923 1924
						return true; // keep veto if no target was provided
					});
1925 1926
				}
			}
1927
		});
1928 1929
	}

1930
	private getUntitledWorkspaceSaveDialogDefaultPath(workspace?: IWorkspaceIdentifier | ISingleFolderWorkspaceIdentifier): string {
1931 1932
		if (workspace) {
			if (isSingleFolderWorkspaceIdentifier(workspace)) {
J
Johannes Rieken 已提交
1933 1934 1935
				return dirname(workspace);
			}

1936
			const resolvedWorkspace = this.workspacesMainService.resolveWorkspaceSync(workspace.configPath);
J
Johannes Rieken 已提交
1937 1938 1939 1940 1941
			if (resolvedWorkspace && resolvedWorkspace.folders.length > 0) {
				for (const folder of resolvedWorkspace.folders) {
					if (folder.uri.scheme === Schemas.file) {
						return dirname(folder.uri.fsPath);
					}
1942 1943 1944
				}
			}
		}
1945

J
Johannes Rieken 已提交
1946
		return void 0;
1947
	}
J
Johannes Rieken 已提交
1948
}