windows.ts 50.7 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';

J
Joao Moreno 已提交
8
import * as path from 'path';
B
Benjamin Pasero 已提交
9
import * as fs from 'original-fs';
J
Joao Moreno 已提交
10 11
import * as nls from 'vs/nls';
import * as arrays from 'vs/base/common/arrays';
12
import { assign, mixin } 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';
15
import { IStorageService } from 'vs/platform/storage/node/storage';
16
import { CodeWindow, IWindowState as ISingleWindowState, defaultWindowState, WindowMode } from 'vs/code/electron-main/window';
17
import { ipcMain as ipc, screen, BrowserWindow, dialog, systemPreferences } from 'electron';
B
Benjamin Pasero 已提交
18
import { IPathWithLineAndColumn, parseLineAndColumnAware } from 'vs/code/node/paths';
19
import { ILifecycleService, UnloadReason } 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';
B
Benjamin Pasero 已提交
22
import { IWindowSettings, OpenContext, IPath, IWindowConfiguration } from 'vs/platform/windows/common/windows';
23
import { getLastActiveWindow, findBestWindowOrFolderForFile, findWindowOnFolder, findWindowOnWorkspace } from 'vs/code/node/windowsFinder';
24
import CommonEvent, { Emitter } from 'vs/base/common/event';
25
import product from 'vs/platform/node/product';
26
import { ITelemetryService, ITelemetryData } from 'vs/platform/telemetry/common/telemetry';
27
import { isEqual } from 'vs/base/common/paths';
28
import { IWindowsMainService, IOpenConfiguration } from "vs/platform/windows/electron-main/windows";
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 33
import { IWorkspacesMainService, IWorkspaceIdentifier, ISingleFolderWorkspaceIdentifier, IWorkspaceSavedEvent } from "vs/platform/workspaces/common/workspaces";
import { IInstantiationService } from "vs/platform/instantiation/common/instantiation";
E
Erich Gamma 已提交
34 35 36 37 38 39

enum WindowError {
	UNRESPONSIVE,
	CRASHED
}

40 41 42 43
interface INewWindowState extends ISingleWindowState {
	hasDefaultState?: boolean;
}

44
interface ILegacyWindowState extends IWindowState {
E
Erich Gamma 已提交
45
	workspacePath?: string;
46 47 48
}

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

55 56 57 58
interface ILegacyWindowsState extends IWindowsState {
	openedFolders?: IWindowState[];
}

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

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

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

71
	workspace?: IWorkspaceIdentifier;
72
	folderPath?: string;
B
Benjamin Pasero 已提交
73 74 75 76 77 78 79 80 81 82

	initialStartup?: boolean;

	filesToOpen?: IPath[];
	filesToCreate?: IPath[];
	filesToDiff?: IPath[];

	forceNewWindow?: boolean;
	windowToUse?: CodeWindow;

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

86 87
interface IWindowToOpen extends IPath {

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

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

	// 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 已提交
101
export class WindowsManager implements IWindowsMainService {
J
Joao Moreno 已提交
102

103
	_serviceBrand: any;
E
Erich Gamma 已提交
104 105 106

	private static windowsStateStorageKey = 'windowsState';

B
Benjamin Pasero 已提交
107
	private static WINDOWS: CodeWindow[] = [];
E
Erich Gamma 已提交
108

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

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

B
Benjamin Pasero 已提交
114 115
	private fileDialog: FileDialog;

B
Benjamin Pasero 已提交
116 117
	private _onWindowReady = new Emitter<CodeWindow>();
	onWindowReady: CommonEvent<CodeWindow> = this._onWindowReady.event;
118 119 120 121

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

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

J
Joao Moreno 已提交
125 126
	constructor(
		@ILogService private logService: ILogService,
J
Joao Moreno 已提交
127
		@IStorageService private storageService: IStorageService,
128
		@IEnvironmentService private environmentService: IEnvironmentService,
B
Benjamin Pasero 已提交
129
		@ILifecycleService private lifecycleService: ILifecycleService,
130
		@IBackupMainService private backupService: IBackupMainService,
131
		@ITelemetryService private telemetryService: ITelemetryService,
132
		@IConfigurationService private configurationService: IConfigurationService,
B
Benjamin Pasero 已提交
133
		@IHistoryMainService private historyService: IHistoryMainService,
134 135
		@IWorkspacesMainService private workspacesService: IWorkspacesMainService,
		@IInstantiationService private instantiationService: IInstantiationService
136
	) {
137
		this.windowsState = this.storageService.getItem<IWindowsState>(WindowsManager.windowsStateStorageKey) || { openedWindows: [] };
138
		this.fileDialog = new FileDialog(environmentService, telemetryService, storageService, this);
139

140 141
		this.migrateLegacyWindowState();
	}
142

143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164
	private migrateLegacyWindowState(): void {
		const state: ILegacyWindowsState = this.windowsState;

		// TODO@Ben migration from previous openedFolders to new openedWindows property
		if (Array.isArray(state.openedFolders) && state.openedFolders.length > 0) {
			state.openedWindows = state.openedFolders;
			state.openedFolders = void 0;
		} else if (!state.openedWindows) {
			state.openedWindows = [];
		}

		// TODO@Ben migration from previous workspacePath in window state to folderPath
		const states: ILegacyWindowState[] = [];
		states.push(state.lastActiveWindow);
		states.push(state.lastPluginDevelopmentHostWindow);
		states.push(...state.openedWindows);
		states.forEach(state => {
			if (state && typeof state.workspacePath === 'string') {
				state.folderPath = state.workspacePath;
				state.workspacePath = void 0;
			}
		});
165
	}
J
Joao Moreno 已提交
166

B
Benjamin Pasero 已提交
167
	public ready(initialUserEnv: IProcessEnvironment): void {
168
		this.initialUserEnv = initialUserEnv;
169 170

		this.registerListeners();
E
Erich Gamma 已提交
171 172 173
	}

	private registerListeners(): void {
174

175
		// React to workbench loaded events from windows
B
Benjamin Pasero 已提交
176
		ipc.on('vscode:workbenchLoaded', (event, windowId: number) => {
J
Joao Moreno 已提交
177
			this.logService.log('IPC#vscode-workbenchLoaded');
E
Erich Gamma 已提交
178

B
Benjamin Pasero 已提交
179
			const win = this.getWindowById(windowId);
E
Erich Gamma 已提交
180 181 182 183
			if (win) {
				win.setReady();

				// Event
184
				this._onWindowReady.fire(win);
E
Erich Gamma 已提交
185 186 187
			}
		});

188 189 190 191 192 193 194 195 196 197 198
		// 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');
				}
			});
		}

199
		// Update our windows state before quitting and before closing windows
B
Benjamin Pasero 已提交
200
		this.lifecycleService.onBeforeWindowClose(win => this.onBeforeWindowClose(win as CodeWindow));
201
		this.lifecycleService.onBeforeQuit(() => this.onBeforeQuit());
202 203 204 205 206 207 208 209 210

		// Handle workspace save event
		this.workspacesService.onWorkspaceSaved(e => this.onWorkspaceSaved(e));
	}

	private onWorkspaceSaved(e: IWorkspaceSavedEvent): void {

		// A workspace was saved to a different config location. Make sure to update our
		// window states with this new location.
B
fix  
Benjamin Pasero 已提交
211
		const states = [this.lastClosedWindowState, this.windowsState.lastActiveWindow, this.windowsState.lastPluginDevelopmentHostWindow, ...this.windowsState.openedWindows];
212 213 214 215 216
		states.forEach(state => {
			if (state && state.workspace && state.workspace.id === e.workspace.id && state.workspace.configPath !== e.workspace.configPath) {
				state.workspace.configPath = e.workspace.configPath;
			}
		});
217 218 219 220 221 222 223 224 225 226
	}

	// Note that onBeforeQuit() and onBeforeWindowClose() are fired in different order depending on the OS:
	// - macOS: since the app will not quit when closing the last window, you will always first get
	//          the onBeforeQuit() event followed by N onbeforeWindowClose() events for each window
	// - 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()
	//          and then onBeforeQuit(). Using the quit action however will first issue onBeforeQuit()
	//          and then onBeforeWindowClose().
	private onBeforeQuit(): void {
227
		const currentWindowsState: ILegacyWindowsState = {
228 229
			openedWindows: [],
			openedFolders: [], // TODO@Ben migration so that old clients do not fail over data (prevents NPEs)
230
			lastPluginDevelopmentHostWindow: this.windowsState.lastPluginDevelopmentHostWindow,
231
			lastActiveWindow: this.lastClosedWindowState
232 233 234 235 236 237 238 239
		};

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

241
			if (activeWindow) {
242
				currentWindowsState.lastActiveWindow = this.toWindowState(activeWindow);
E
Erich Gamma 已提交
243
			}
244 245 246 247 248
		}

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

252
		// 3.) All windows (except extension host) for N >= 2 to support restoreWindows: all or for auto update
253 254 255 256 257
		//
		// 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) {
258
			currentWindowsState.openedWindows = WindowsManager.WINDOWS.filter(w => !w.isExtensionDevelopmentHost).map(w => this.toWindowState(w));
259
		}
E
Erich Gamma 已提交
260

261 262 263
		// Persist
		this.storageService.setItem(WindowsManager.windowsStateStorageKey, currentWindowsState);
	}
264

265
	// See note on #onBeforeQuit() for details how these events are flowing
B
Benjamin Pasero 已提交
266
	private onBeforeWindowClose(win: CodeWindow): void {
267 268 269 270 271
		if (this.lifecycleService.isQuitRequested()) {
			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
272
		const state: IWindowState = this.toWindowState(win);
273 274 275 276
		if (win.isExtensionDevelopmentHost && !win.isExtensionTestHost) {
			this.windowsState.lastPluginDevelopmentHostWindow = state; // do not let test run window state overwrite our extension development state
		}

277
		// Any non extension host window with same workspace or folder
278
		else if (!win.isExtensionDevelopmentHost && (!!win.openedWorkspace || !!win.openedFolderPath)) {
279
			this.windowsState.openedWindows.forEach(o => {
B
fix npe  
Benjamin Pasero 已提交
280
				const sameWorkspace = win.openedWorkspace && o.workspace && o.workspace.id === win.openedWorkspace.id;
281 282 283
				const sameFolder = win.openedFolderPath && isEqual(o.folderPath, win.openedFolderPath, !isLinux /* ignorecase */);

				if (sameWorkspace || sameFolder) {
284 285 286 287 288 289 290
					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.
291 292 293
		// 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) {
294 295
			this.lastClosedWindowState = state;
		}
E
Erich Gamma 已提交
296 297
	}

298 299
	private toWindowState(win: CodeWindow): IWindowState {
		return {
300
			workspace: win.openedWorkspace,
301 302 303 304 305 306
			folderPath: win.openedFolderPath,
			backupPath: win.backupPath,
			uiState: win.serializeWindowState()
		};
	}

307 308 309 310 311 312 313
	public closeWorkspace(win: CodeWindow): void {
		this.openInBrowserWindow({
			cli: this.environmentService.args,
			windowToUse: win
		});
	}

B
Benjamin Pasero 已提交
314
	public open(openConfig: IOpenConfiguration): CodeWindow[] {
315
		const windowsToOpen = this.getWindowsToOpen(openConfig);
E
Erich Gamma 已提交
316

317 318 319 320 321 322 323 324 325
		let filesToOpen = windowsToOpen.filter(path => !!path.filePath && !path.createFilePath);
		let filesToCreate = windowsToOpen.filter(path => !!path.filePath && path.createFilePath);
		let filesToDiff: IPath[];
		if (openConfig.diffMode && filesToOpen.length === 2) {
			filesToDiff = filesToOpen;
			filesToOpen = [];
			filesToCreate = []; // diff ignores other files that do not exist
		} else {
			filesToDiff = [];
E
Erich Gamma 已提交
326 327
		}

328 329 330
		//
		// These are windows to open to show workspaces
		//
331
		const workspacesToOpen = arrays.distinct(windowsToOpen.filter(win => !!win.workspace).map(win => win.workspace), workspace => workspace.id); // prevent duplicates
332 333 334 335 336 337

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

338
		//
339
		// These are windows to restore because of hot-exit or from previous session (only performed once on startup!)
340
		//
341 342 343 344 345 346 347 348 349 350 351 352 353
		let foldersToRestore: string[] = [];
		let workspacesToRestore: IWorkspaceIdentifier[] = [];
		let emptyToRestore: string[] = [];
		if (openConfig.initialStartup && !openConfig.cli.extensionDevelopmentPath) {
			foldersToRestore = this.backupService.getFolderBackupPaths();

			workspacesToRestore = this.backupService.getWorkspaceBackups();				// collect from workspaces with hot-exit backups
			workspacesToRestore.push(...this.doGetUntitledWorkspacesFromLastSession());	// collect from previous window session

			emptyToRestore = this.backupService.getEmptyWindowBackupPaths();
			emptyToRestore.push(...windowsToOpen.filter(w => !w.workspace && !w.folderPath && w.backupPath).map(w => path.basename(w.backupPath))); // add empty windows with backupPath
			emptyToRestore = arrays.distinct(emptyToRestore); // prevent duplicates
		}
354

355 356 357
		//
		// These are empty windows to open
		//
358
		const emptyToOpen = windowsToOpen.filter(win => !win.workspace && !win.folderPath && !win.filePath && !win.backupPath).length;
359

360
		// Open based on config
361
		const usedWindows = this.doOpen(openConfig, workspacesToOpen, workspacesToRestore, foldersToOpen, foldersToRestore, emptyToRestore, emptyToOpen, filesToOpen, filesToCreate, filesToDiff);
362 363 364 365 366 367 368 369

		// Make sure the last active window gets focus if we opened multiple
		if (usedWindows.length > 1 && this.windowsState.lastActiveWindow) {
			let lastActiveWindw = usedWindows.filter(w => w.backupPath === this.windowsState.lastActiveWindow.backupPath);
			if (lastActiveWindw.length) {
				lastActiveWindw[0].focus();
			}
		}
370

371 372 373
		// 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) {
374
			const recentlyOpenedWorkspaces: (IWorkspaceIdentifier | ISingleFolderWorkspaceIdentifier)[] = [];
375
			const recentlyOpenedFiles: string[] = [];
376 377

			windowsToOpen.forEach(win => {
378 379 380 381
				if (win.workspace || win.folderPath) {
					recentlyOpenedWorkspaces.push(win.workspace || win.folderPath);
				} else if (win.filePath) {
					recentlyOpenedFiles.push(win.filePath);
382 383 384
				}
			});

385
			this.historyService.addRecentlyOpened(recentlyOpenedWorkspaces, recentlyOpenedFiles);
386
		}
387

388 389 390 391 392 393 394
		// If we got started with --wait from the CLI, we need to signal to the outside when the window
		// used for the edit operation is closed so that the waiting process can continue. We do this by
		// deleting the waitMarkerFilePath.
		if (openConfig.context === OpenContext.CLI && openConfig.cli.wait && openConfig.cli.waitMarkerFilePath && usedWindows.length === 1 && usedWindows[0]) {
			this.waitForWindowClose(usedWindows[0].id).done(() => fs.unlink(openConfig.cli.waitMarkerFilePath, error => void 0));
		}

395 396 397 398 399
		return usedWindows;
	}

	private doOpen(
		openConfig: IOpenConfiguration,
400 401
		workspacesToOpen: IWorkspaceIdentifier[],
		workspacesToRestore: IWorkspaceIdentifier[],
402 403 404 405 406 407 408 409 410
		foldersToOpen: string[],
		foldersToRestore: string[],
		emptyToRestore: string[],
		emptyToOpen: number,
		filesToOpen: IPath[],
		filesToCreate: IPath[],
		filesToDiff: IPath[]
	) {

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

B
Benjamin Pasero 已提交
414
		// 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
B
Benjamin Pasero 已提交
415
		const usedWindows: CodeWindow[] = [];
B
wip  
Benjamin Pasero 已提交
416
		if (!foldersToOpen.length && !foldersToRestore.length && !emptyToRestore.length && (filesToOpen.length > 0 || filesToCreate.length > 0 || filesToDiff.length > 0)) {
E
Erich Gamma 已提交
417

418
			// Find suitable window or folder path to open files in
419
			const fileToCheck = filesToOpen[0] || filesToCreate[0] || filesToDiff[0];
B
Benjamin Pasero 已提交
420
			const bestWindowOrFolder = findBestWindowOrFolderForFile({
421 422 423 424 425 426 427
				windows: WindowsManager.WINDOWS,
				newWindow: openFilesInNewWindow,
				reuseWindow: openConfig.forceReuseWindow,
				context: openConfig.context,
				filePath: fileToCheck && fileToCheck.filePath,
				userHome: this.environmentService.userHome
			});
B
Benjamin Pasero 已提交
428

429
			// We found a suitable window to open the files within: send the files to open over
430 431
			if (bestWindowOrFolder instanceof CodeWindow && bestWindowOrFolder.openedFolderPath) {
				foldersToOpen.push(bestWindowOrFolder.openedFolderPath);
432 433 434 435 436
			}

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

439
			// Finally, if no window or folder is found, just open the files in an empty window
E
Erich Gamma 已提交
440
			else {
B
Benjamin Pasero 已提交
441
				usedWindows.push(this.openInBrowserWindow({
B
Benjamin Pasero 已提交
442 443 444 445 446 447 448
					userEnv: openConfig.userEnv,
					cli: openConfig.cli,
					initialStartup: openConfig.initialStartup,
					filesToOpen,
					filesToCreate,
					filesToDiff,
					forceNewWindow: true
B
Benjamin Pasero 已提交
449
				}));
E
Erich Gamma 已提交
450

451 452 453 454
				// Reset these because we handled them
				filesToOpen = [];
				filesToCreate = [];
				filesToDiff = [];
E
Erich Gamma 已提交
455 456 457
			}
		}

458
		// Handle workspaces to open (instructed and to restore)
459
		const allWorkspacesToOpen = arrays.distinct([...workspacesToOpen, ...workspacesToRestore], workspace => workspace.id); // prevent duplicates
460 461
		if (allWorkspacesToOpen.length > 0) {

462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478
			// Check for existing instances that have same workspace ID but different configuration path
			// For now we reload that window with the new configuration so that the configuration path change
			// can travel properly.
			allWorkspacesToOpen.forEach(workspaceToOpen => {
				const existingWindow = findWindowOnWorkspace(WindowsManager.WINDOWS, workspaceToOpen);
				if (existingWindow && existingWindow.openedWorkspace.configPath !== workspaceToOpen.configPath) {
					usedWindows.push(this.doOpenFolderOrWorkspace(openConfig, { workspace: workspaceToOpen }, false, filesToOpen, filesToCreate, filesToDiff, existingWindow));

					// Reset these because we handled them
					filesToOpen = [];
					filesToCreate = [];
					filesToDiff = [];

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

479 480 481 482 483 484
			// 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
B
Benjamin Pasero 已提交
485
				usedWindows.push(this.doOpenFilesInExistingWindow(windowOnWorkspace, filesToOpen, filesToCreate, filesToDiff));
486 487 488 489 490 491 492 493 494 495 496

				// Reset these because we handled them
				filesToOpen = [];
				filesToCreate = [];
				filesToDiff = [];

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

			// Open remaining ones
			allWorkspacesToOpen.forEach(workspaceToOpen => {
497
				if (windowsOnWorkspace.some(win => win.openedWorkspace.id === workspaceToOpen.id)) {
498 499 500 501
					return; // ignore folders that are already open
				}

				// Do open folder
502
				usedWindows.push(this.doOpenFolderOrWorkspace(openConfig, { workspace: workspaceToOpen }, openFolderInNewWindow, filesToOpen, filesToCreate, filesToDiff));
503 504 505 506 507 508 509 510 511 512

				// Reset these because we handled them
				filesToOpen = [];
				filesToCreate = [];
				filesToDiff = [];

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

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

			// Check for existing instances
518 519
			const windowsOnFolderPath = arrays.coalesce(allFoldersToOpen.map(folderToOpen => findWindowOnFolder(WindowsManager.WINDOWS, folderToOpen)));
			if (windowsOnFolderPath.length > 0) {
520
				const windowOnFolderPath = windowsOnFolderPath[0];
E
Erich Gamma 已提交
521

522
				// Do open files
B
Benjamin Pasero 已提交
523
				usedWindows.push(this.doOpenFilesInExistingWindow(windowOnFolderPath, filesToOpen, filesToCreate, filesToDiff));
524

E
Erich Gamma 已提交
525 526 527
				// Reset these because we handled them
				filesToOpen = [];
				filesToCreate = [];
528
				filesToDiff = [];
E
Erich Gamma 已提交
529

B
Benjamin Pasero 已提交
530
				openFolderInNewWindow = true; // any other folders to open must open in new window then
E
Erich Gamma 已提交
531 532 533
			}

			// Open remaining ones
534
			allFoldersToOpen.forEach(folderToOpen => {
535
				if (windowsOnFolderPath.some(win => isEqual(win.openedFolderPath, folderToOpen, !isLinux /* ignorecase */))) {
E
Erich Gamma 已提交
536 537 538
					return; // ignore folders that are already open
				}

539 540
				// Do open folder
				usedWindows.push(this.doOpenFolderOrWorkspace(openConfig, { folderPath: folderToOpen }, openFolderInNewWindow, filesToOpen, filesToCreate, filesToDiff));
E
Erich Gamma 已提交
541 542 543 544

				// Reset these because we handled them
				filesToOpen = [];
				filesToCreate = [];
545
				filesToDiff = [];
E
Erich Gamma 已提交
546

B
Benjamin Pasero 已提交
547
				openFolderInNewWindow = true; // any other folders to open must open in new window then
E
Erich Gamma 已提交
548 549 550
			});
		}

551
		// Handle empty to restore
552
		if (emptyToRestore.length > 0) {
553
			emptyToRestore.forEach(emptyWindowBackupFolder => {
B
Benjamin Pasero 已提交
554
				usedWindows.push(this.openInBrowserWindow({
B
Benjamin Pasero 已提交
555 556 557 558 559 560 561
					userEnv: openConfig.userEnv,
					cli: openConfig.cli,
					initialStartup: openConfig.initialStartup,
					filesToOpen,
					filesToCreate,
					filesToDiff,
					forceNewWindow: true,
562
					emptyWindowBackupFolder
B
Benjamin Pasero 已提交
563
				}));
564

B
wip  
Benjamin Pasero 已提交
565 566 567 568 569
				// Reset these because we handled them
				filesToOpen = [];
				filesToCreate = [];
				filesToDiff = [];

B
Benjamin Pasero 已提交
570
				openFolderInNewWindow = true; // any other folders to open must open in new window then
571 572
			});
		}
B
Benjamin Pasero 已提交
573

574 575
		// Handle empty to open (only if no other window opened)
		if (usedWindows.length === 0) {
576
			for (let i = 0; i < emptyToOpen; i++) {
B
Benjamin Pasero 已提交
577
				usedWindows.push(this.openInBrowserWindow({
B
Benjamin Pasero 已提交
578 579 580
					userEnv: openConfig.userEnv,
					cli: openConfig.cli,
					initialStartup: openConfig.initialStartup,
581
					forceNewWindow: openFolderInNewWindow
B
Benjamin Pasero 已提交
582
				}));
E
Erich Gamma 已提交
583

584
				openFolderInNewWindow = true; // any other window to open must open in new window then
585 586
			}
		}
E
Erich Gamma 已提交
587

588
		return arrays.distinct(usedWindows);
E
Erich Gamma 已提交
589 590
	}

B
Benjamin Pasero 已提交
591
	private doOpenFilesInExistingWindow(window: CodeWindow, filesToOpen: IPath[], filesToCreate: IPath[], filesToDiff: IPath[]): CodeWindow {
592 593 594 595 596
		window.focus(); // make sure window has focus

		window.ready().then(readyWindow => {
			readyWindow.send('vscode:openFiles', { filesToOpen, filesToCreate, filesToDiff });
		});
B
Benjamin Pasero 已提交
597 598

		return window;
599 600
	}

601
	private doOpenFolderOrWorkspace(openConfig: IOpenConfiguration, folderOrWorkspace: IWindowToOpen, openInNewWindow: boolean, filesToOpen: IPath[], filesToCreate: IPath[], filesToDiff: IPath[], windowToUse?: CodeWindow): CodeWindow {
602 603 604 605
		const browserWindow = this.openInBrowserWindow({
			userEnv: openConfig.userEnv,
			cli: openConfig.cli,
			initialStartup: openConfig.initialStartup,
606
			workspace: folderOrWorkspace.workspace,
607 608 609 610
			folderPath: folderOrWorkspace.folderPath,
			filesToOpen,
			filesToCreate,
			filesToDiff,
611 612
			forceNewWindow: openInNewWindow,
			windowToUse
613 614 615 616 617
		});

		return browserWindow;
	}

618 619
	private getWindowsToOpen(openConfig: IOpenConfiguration): IWindowToOpen[] {
		let windowsToOpen: IWindowToOpen[];
E
Erich Gamma 已提交
620

621
		// Extract paths: from API
B
Benjamin Pasero 已提交
622
		if (openConfig.pathsToOpen && openConfig.pathsToOpen.length > 0) {
623
			windowsToOpen = this.doExtractPathsFromAPI(openConfig.pathsToOpen, openConfig.cli && openConfig.cli.goto);
E
Erich Gamma 已提交
624 625
		}

B
Benjamin Pasero 已提交
626 627
		// Check for force empty
		else if (openConfig.forceEmpty) {
628
			windowsToOpen = [Object.create(null)];
E
Erich Gamma 已提交
629 630
		}

631
		// Extract paths: from CLI
B
Benjamin Pasero 已提交
632
		else if (openConfig.cli._.length > 0) {
633
			windowsToOpen = this.doExtractPathsFromCLI(openConfig.cli);
B
Benjamin Pasero 已提交
634 635
		}

636
		// Extract windows: from previous session
B
Benjamin Pasero 已提交
637
		else {
638
			windowsToOpen = this.doGetWindowsFromLastSession();
B
Benjamin Pasero 已提交
639 640
		}

641
		return windowsToOpen;
E
Erich Gamma 已提交
642 643
	}

644 645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660 661 662 663 664 665
	private doExtractPathsFromAPI(paths: string[], gotoLineMode: boolean): IPath[] {
		let pathsToOpen = paths.map(pathToOpen => {
			const path = this.parsePath(pathToOpen, false, gotoLineMode);

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

				const activeWindow = BrowserWindow.getFocusedWindow();
				if (activeWindow) {
					dialog.showMessageBox(activeWindow, options);
				} else {
					dialog.showMessageBox(options);
				}
			}
B
Benjamin Pasero 已提交
666

667 668 669 670 671 672 673 674 675 676
			return path;
		});

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

		return pathsToOpen;
	}

	private doExtractPathsFromCLI(cli: ParsedArgs): IPath[] {
B
Benjamin Pasero 已提交
677
		const pathsToOpen = arrays.coalesce(cli._.map(candidate => this.parsePath(candidate, true /* ignoreFileNotFound */, cli.goto)));
678 679
		if (pathsToOpen.length > 0) {
			return pathsToOpen;
B
Benjamin Pasero 已提交
680 681 682 683 684 685
		}

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

686 687 688
	private doGetWindowsFromLastSession(): IWindowToOpen[] {
		const restoreWindows = this.getRestoreWindowsSetting();
		const lastActiveWindow = this.windowsState.lastActiveWindow;
B
Benjamin Pasero 已提交
689

690
		switch (restoreWindows) {
B
Benjamin Pasero 已提交
691

692
			// none: we always open an empty window
693 694
			case 'none':
				return [Object.create(null)];
B
Benjamin Pasero 已提交
695

696
			// one: restore last opened workspace/folder or empty window
697 698
			case 'one':
				if (lastActiveWindow) {
B
Benjamin Pasero 已提交
699

700
					// workspace
B
Benjamin Pasero 已提交
701 702 703 704 705 706
					const candidateWorkspace = lastActiveWindow.workspace;
					if (candidateWorkspace) {
						const validatedWorkspace = this.parsePath(candidateWorkspace.configPath);
						if (validatedWorkspace && validatedWorkspace.workspace) {
							return [validatedWorkspace];
						}
707 708 709 710 711 712 713
					}

					// folder (if path is valid)
					else if (lastActiveWindow.folderPath) {
						const validatedFolder = this.parsePath(lastActiveWindow.folderPath);
						if (validatedFolder) {
							return [validatedFolder];
714 715
						}
					}
B
Benjamin Pasero 已提交
716

717
					// otherwise use backup path to restore empty windows
718 719 720 721 722 723 724 725 726 727
					else if (lastActiveWindow.backupPath) {
						return [{ backupPath: lastActiveWindow.backupPath }];
					}
				}
				break;

			// all: restore all windows
			// folders: restore last opened folders only
			case 'all':
			case 'folders':
728
				const windowsToOpen: IWindowToOpen[] = [];
729

730 731 732 733
				// Workspaces
				const workspaces = this.windowsState.openedWindows.filter(w => !!w.workspace).map(w => w.workspace);
				if (lastActiveWindow && lastActiveWindow.workspace) {
					workspaces.push(lastActiveWindow.workspace);
734
				}
B
Benjamin Pasero 已提交
735
				windowsToOpen.push(...arrays.coalesce(workspaces.map(candidate => this.parsePath(candidate.configPath))));
B
Benjamin Pasero 已提交
736

737 738 739 740 741
				// Folders
				const folders = this.windowsState.openedWindows.filter(w => !!w.folderPath).map(w => w.folderPath);
				if (lastActiveWindow && lastActiveWindow.folderPath) {
					folders.push(lastActiveWindow.folderPath);
				}
B
Benjamin Pasero 已提交
742
				windowsToOpen.push(...arrays.coalesce(folders.map(candidate => this.parsePath(candidate))));
B
Benjamin Pasero 已提交
743

744 745
				// Windows that were Empty
				if (restoreWindows === 'all') {
746 747
					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;
748 749 750 751 752 753 754 755 756 757 758 759
					if (lastActiveEmpty) {
						lastOpenedEmpty.push(lastActiveEmpty);
					}

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

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

				break;
B
Benjamin Pasero 已提交
760
		}
E
Erich Gamma 已提交
761

762
		// Always fallback to empty window
B
Benjamin Pasero 已提交
763
		return [Object.create(null)];
E
Erich Gamma 已提交
764 765
	}

766
	private doGetUntitledWorkspacesFromLastSession(): IWorkspaceIdentifier[] {
B
Benjamin Pasero 已提交
767
		const candidates: IWorkspaceIdentifier[] = [];
768 769

		if (this.isUntitledWorkspace(this.windowsState.lastActiveWindow)) {
B
Benjamin Pasero 已提交
770
			candidates.push(this.windowsState.lastActiveWindow.workspace);
771 772 773 774 775
		}

		for (let i = 0; i < this.windowsState.openedWindows.length; i++) {
			const state = this.windowsState.openedWindows[i];
			if (this.isUntitledWorkspace(state)) {
B
Benjamin Pasero 已提交
776
				candidates.push(state.workspace);
777 778 779
			}
		}

B
Benjamin Pasero 已提交
780
		return arrays.coalesce(candidates.map(candidate => this.parsePath(candidate.configPath))).map(window => window.workspace);
781 782 783 784 785 786
	}

	private isUntitledWorkspace(state: IWindowState): boolean {
		return state && state.workspace && this.workspacesService.isUntitledWorkspace(state.workspace);
	}

787 788 789 790 791 792
	private getRestoreWindowsSetting(): RestoreWindowsSetting {
		let restoreWindows: RestoreWindowsSetting;
		if (this.lifecycleService.wasRestarted) {
			restoreWindows = 'all'; // always reopen all windows when an update was applied
		} else {
			const windowConfig = this.configurationService.getConfiguration<IWindowSettings>('window');
793
			restoreWindows = ((windowConfig && windowConfig.restoreWindows) || 'one') as RestoreWindowsSetting;
794

B
fix npe  
Benjamin Pasero 已提交
795
			if (restoreWindows === 'one' /* default */ && windowConfig && windowConfig.reopenFolders) {
796 797 798 799 800 801 802 803 804 805 806 807
				restoreWindows = windowConfig.reopenFolders; // TODO@Ben migration
			}

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

		return restoreWindows;
	}

	private parsePath(anyPath: string, ignoreFileNotFound?: boolean, gotoLineMode?: boolean): IWindowToOpen {
E
Erich Gamma 已提交
808 809 810 811
		if (!anyPath) {
			return null;
		}

812
		let parsedPath: IPathWithLineAndColumn;
E
Erich Gamma 已提交
813
		if (gotoLineMode) {
J
Joao Moreno 已提交
814
			parsedPath = parseLineAndColumnAware(anyPath);
E
Erich Gamma 已提交
815 816 817
			anyPath = parsedPath.path;
		}

B
Benjamin Pasero 已提交
818
		const candidate = path.normalize(anyPath);
E
Erich Gamma 已提交
819
		try {
B
Benjamin Pasero 已提交
820
			const candidateStat = fs.statSync(candidate);
E
Erich Gamma 已提交
821
			if (candidateStat) {
822
				if (candidateStat.isFile()) {
823 824 825 826 827 828 829 830

					// Workspace
					const workspace = this.workspacesService.resolveWorkspaceSync(candidate);
					if (workspace) {
						return { workspace };
					}

					// File
831
					return {
832
						filePath: candidate,
E
Erich Gamma 已提交
833
						lineNumber: gotoLineMode ? parsedPath.line : void 0,
834
						columnNumber: gotoLineMode ? parsedPath.column : void 0
835 836 837 838 839 840 841
					};
				}

				// Folder
				return {
					folderPath: candidate
				};
E
Erich Gamma 已提交
842 843
			}
		} catch (error) {
844
			this.historyService.removeFromRecentlyOpened([candidate]); // since file does not seem to exist anymore, remove from recent
845

E
Erich Gamma 已提交
846 847 848 849 850 851 852 853
			if (ignoreFileNotFound) {
				return { filePath: candidate, createFilePath: true }; // assume this is a file that does not yet exist
			}
		}

		return null;
	}

B
Benjamin Pasero 已提交
854 855 856 857
	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
		const windowConfig = this.configurationService.getConfiguration<IWindowSettings>('window');
858 859 860
		const openFolderInNewWindowConfig = (windowConfig && windowConfig.openFoldersInNewWindow) || 'default' /* default */;
		const openFilesInNewWindowConfig = (windowConfig && windowConfig.openFilesInNewWindow) || 'off' /* default */;

B
Benjamin Pasero 已提交
861
		let openFolderInNewWindow = (openConfig.preferNewWindow || openConfig.forceNewWindow) && !openConfig.forceReuseWindow;
862 863
		if (!openConfig.forceNewWindow && !openConfig.forceReuseWindow && (openFolderInNewWindowConfig === 'on' || openFolderInNewWindowConfig === 'off')) {
			openFolderInNewWindow = (openFolderInNewWindowConfig === 'on');
B
Benjamin Pasero 已提交
864 865 866 867 868 869 870 871 872 873 874
		}

		// 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 {
			if (openConfig.context === OpenContext.DOCK) {
				openFilesInNewWindow = true; // only on macOS do we allow to open files in a new window if this is triggered via DOCK context
			}

875 876
			if (!openConfig.cli.extensionDevelopmentPath && (openFilesInNewWindowConfig === 'on' || openFilesInNewWindowConfig === 'off')) {
				openFilesInNewWindow = (openFilesInNewWindowConfig === 'on');
B
Benjamin Pasero 已提交
877 878 879 880 881 882
			}
		}

		return { openFolderInNewWindow, openFilesInNewWindow };
	}

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

B
Benjamin Pasero 已提交
885 886 887 888 889 890 891
		// 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.
		let res = WindowsManager.WINDOWS.filter(w => w.config && isEqual(w.config.extensionDevelopmentPath, openConfig.cli.extensionDevelopmentPath, !isLinux /* ignorecase */));
		if (res && res.length === 1) {
			this.reload(res[0], openConfig.cli);
			res[0].focus(); // make sure it gets focus and is restored
E
Erich Gamma 已提交
892

B
Benjamin Pasero 已提交
893 894
			return;
		}
E
Erich Gamma 已提交
895

896
		// Fill in previously opened folder unless an explicit path is provided and we are not unit testing
B
Benjamin Pasero 已提交
897
		if (openConfig.cli._.length === 0 && !openConfig.cli.extensionTestsPath) {
898 899 900
			const folderToOpen = this.windowsState.lastPluginDevelopmentHostWindow && this.windowsState.lastPluginDevelopmentHostWindow.folderPath;
			if (folderToOpen) {
				openConfig.cli._ = [folderToOpen];
E
Erich Gamma 已提交
901 902 903
			}
		}

B
Benjamin Pasero 已提交
904 905
		// Make sure we are not asked to open a path that is already opened
		if (openConfig.cli._.length > 0) {
906
			res = WindowsManager.WINDOWS.filter(w => w.openedFolderPath && openConfig.cli._.indexOf(w.openedFolderPath) >= 0);
B
Benjamin Pasero 已提交
907 908 909
			if (res.length) {
				openConfig.cli._ = [];
			}
E
Erich Gamma 已提交
910 911
		}

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

B
Benjamin Pasero 已提交
916 917 918 919 920 921 922 923
	private openInBrowserWindow(options: IOpenBrowserWindowOptions): CodeWindow {

		// Build IWindowConfiguration from config and options
		const configuration: IWindowConfiguration = mixin({}, options.cli); // inherit all properties from CLI
		configuration.appRoot = this.environmentService.appRoot;
		configuration.execPath = process.execPath;
		configuration.userEnv = assign({}, this.initialUserEnv, options.userEnv || {});
		configuration.isInitialStartup = options.initialStartup;
924
		configuration.workspace = options.workspace;
925
		configuration.folderPath = options.folderPath;
B
Benjamin Pasero 已提交
926 927 928 929 930
		configuration.filesToOpen = options.filesToOpen;
		configuration.filesToCreate = options.filesToCreate;
		configuration.filesToDiff = options.filesToDiff;
		configuration.nodeCachedDataDir = this.environmentService.nodeCachedDataDir;

931
		// if we know the backup folder upfront (for empty windows to restore), we can set it
932
		// directly here which helps for restoring UI state associated with that window.
B
Benjamin Pasero 已提交
933
		// For all other cases we first call into registerEmptyWindowBackupSync() to set it before
934
		// loading the window.
935 936
		if (options.emptyWindowBackupFolder) {
			configuration.backupPath = path.join(this.environmentService.backupHome, options.emptyWindowBackupFolder);
937 938
		}

B
Benjamin Pasero 已提交
939
		let codeWindow: CodeWindow;
B
Benjamin Pasero 已提交
940 941
		if (!options.forceNewWindow) {
			codeWindow = options.windowToUse || this.getLastActiveWindow();
B
Benjamin Pasero 已提交
942 943
			if (codeWindow) {
				codeWindow.focus();
E
Erich Gamma 已提交
944 945 946 947
			}
		}

		// New window
B
Benjamin Pasero 已提交
948
		if (!codeWindow) {
949
			const windowConfig = this.configurationService.getConfiguration<IWindowSettings>('window');
950 951 952 953 954 955 956 957 958 959
			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 {
960
				allowFullscreen = this.lifecycleService.wasRestarted || (windowConfig && windowConfig.restoreFullscreen);
961 962 963 964 965
			}

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

967
			codeWindow = this.instantiationService.createInstance(CodeWindow, {
968
				state,
969
				extensionDevelopmentPath: configuration.extensionDevelopmentPath,
970
				isExtensionTestHost: !!configuration.extensionTestsPath
971
			});
972

B
Benjamin Pasero 已提交
973
			WindowsManager.WINDOWS.push(codeWindow);
E
Erich Gamma 已提交
974 975

			// Window Events
B
Benjamin Pasero 已提交
976 977 978 979 980
			codeWindow.win.webContents.removeAllListeners('devtools-reload-page'); // remove built in listener so we can handle this on our own
			codeWindow.win.webContents.on('devtools-reload-page', () => this.reload(codeWindow));
			codeWindow.win.webContents.on('crashed', () => this.onWindowError(codeWindow, WindowError.CRASHED));
			codeWindow.win.on('unresponsive', () => this.onWindowError(codeWindow, WindowError.UNRESPONSIVE));
			codeWindow.win.on('closed', () => this.onWindowClosed(codeWindow));
E
Erich Gamma 已提交
981 982

			// Lifecycle
B
Benjamin Pasero 已提交
983
			this.lifecycleService.registerWindow(codeWindow);
E
Erich Gamma 已提交
984 985 986 987 988 989
		}

		// Existing window
		else {

			// Some configuration things get inherited if the window is being reused and we are
B
Benjamin Pasero 已提交
990
			// in extension development host mode. These options are all development related.
B
Benjamin Pasero 已提交
991
			const currentWindowConfig = codeWindow.config;
A
Alex Dima 已提交
992 993
			if (!configuration.extensionDevelopmentPath && currentWindowConfig && !!currentWindowConfig.extensionDevelopmentPath) {
				configuration.extensionDevelopmentPath = currentWindowConfig.extensionDevelopmentPath;
B
Benjamin Pasero 已提交
994
				configuration.verbose = currentWindowConfig.verbose;
995
				configuration.debugBrkPluginHost = currentWindowConfig.debugBrkPluginHost;
996
				configuration.debugPluginHost = currentWindowConfig.debugPluginHost;
997
				configuration['extensions-dir'] = currentWindowConfig['extensions-dir'];
E
Erich Gamma 已提交
998 999 1000 1001
			}
		}

		// Only load when the window has not vetoed this
B
Benjamin Pasero 已提交
1002
		this.lifecycleService.unload(codeWindow, UnloadReason.LOAD).done(veto => {
E
Erich Gamma 已提交
1003 1004
			if (!veto) {

B
Benjamin Pasero 已提交
1005 1006
				// Register window for backups
				if (!configuration.extensionDevelopmentPath) {
1007 1008
					if (configuration.workspace) {
						configuration.backupPath = this.backupService.registerWorkspaceBackupSync(configuration.workspace);
1009
					} else if (configuration.folderPath) {
B
Benjamin Pasero 已提交
1010
						configuration.backupPath = this.backupService.registerFolderBackupSync(configuration.folderPath);
B
Benjamin Pasero 已提交
1011 1012 1013
					} else {
						configuration.backupPath = this.backupService.registerEmptyWindowBackupSync(options.emptyWindowBackupFolder);
					}
B
Benjamin Pasero 已提交
1014 1015
				}

E
Erich Gamma 已提交
1016
				// Load it
B
Benjamin Pasero 已提交
1017
				codeWindow.load(configuration);
E
Erich Gamma 已提交
1018 1019
			}
		});
1020

B
Benjamin Pasero 已提交
1021
		return codeWindow;
E
Erich Gamma 已提交
1022 1023
	}

1024
	private getNewWindowState(configuration: IWindowConfiguration): INewWindowState {
1025
		const lastActive = this.getLastActiveWindow();
E
Erich Gamma 已提交
1026

1027 1028
		// Restore state unless we are running extension tests
		if (!configuration.extensionTestsPath) {
E
Erich Gamma 已提交
1029

1030 1031 1032
			// extension development host Window - load from stored settings if any
			if (!!configuration.extensionDevelopmentPath && this.windowsState.lastPluginDevelopmentHostWindow) {
				return this.windowsState.lastPluginDevelopmentHostWindow.uiState;
1033 1034
			}

1035 1036 1037 1038 1039 1040 1041 1042 1043 1044 1045 1046 1047 1048
			// 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];
				}
1049 1050
			}

1051 1052 1053 1054 1055 1056
			// 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 已提交
1057 1058
			}

1059 1060 1061 1062 1063
			// First Window
			const lastActiveState = this.lastClosedWindowState || this.windowsState.lastActiveWindow;
			if (!lastActive && lastActiveState) {
				return lastActiveState.uiState;
			}
E
Erich Gamma 已提交
1064 1065 1066 1067 1068 1069 1070
		}

		//
		// 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
1071
		let displayToUse: Electron.Display;
B
Benjamin Pasero 已提交
1072
		const displays = screen.getAllDisplays();
E
Erich Gamma 已提交
1073 1074 1075 1076 1077 1078 1079 1080 1081 1082

		// 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 已提交
1083
			if (isMacintosh) {
B
Benjamin Pasero 已提交
1084
				const cursorPoint = screen.getCursorScreenPoint();
E
Erich Gamma 已提交
1085 1086 1087 1088 1089 1090 1091 1092
				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());
			}

1093
			// fallback to primary display or first display
E
Erich Gamma 已提交
1094
			if (!displayToUse) {
1095
				displayToUse = screen.getPrimaryDisplay() || displays[0];
E
Erich Gamma 已提交
1096 1097 1098
			}
		}

1099
		let state = defaultWindowState() as INewWindowState;
1100 1101
		state.x = displayToUse.bounds.x + (displayToUse.bounds.width / 2) - (state.width / 2);
		state.y = displayToUse.bounds.y + (displayToUse.bounds.height / 2) - (state.height / 2);
E
Erich Gamma 已提交
1102

1103 1104 1105 1106 1107 1108 1109 1110 1111 1112 1113
		// Check for newWindowDimensions setting and adjust accordingly
		const windowConfig = this.configurationService.getConfiguration<IWindowSettings>('window');
		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 已提交
1114 1115 1116 1117 1118 1119 1120
				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;
				}

1121 1122 1123 1124 1125 1126 1127 1128
				ensureNoOverlap = false;
			}
		}

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

1129 1130
		state.hasDefaultState = true; // flag as default state

1131
		return state;
E
Erich Gamma 已提交
1132 1133
	}

J
Joao Moreno 已提交
1134
	private ensureNoOverlap(state: ISingleWindowState): ISingleWindowState {
E
Erich Gamma 已提交
1135 1136 1137 1138
		if (WindowsManager.WINDOWS.length === 0) {
			return state;
		}

1139 1140
		const existingWindowBounds = WindowsManager.WINDOWS.map(win => win.getBounds());
		while (existingWindowBounds.some(b => b.x === state.x || b.y === state.y)) {
E
Erich Gamma 已提交
1141 1142 1143 1144 1145 1146 1147
			state.x += 30;
			state.y += 30;
		}

		return state;
	}

B
Benjamin Pasero 已提交
1148 1149 1150 1151 1152 1153 1154 1155 1156 1157 1158 1159 1160
	public reload(win: CodeWindow, cli?: ParsedArgs): void {

		// Only reload when the window has not vetoed this
		this.lifecycleService.unload(win, UnloadReason.RELOAD).done(veto => {
			if (!veto) {
				win.reload(cli);

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

B
Benjamin Pasero 已提交
1161
	public focusLastActive(cli: ParsedArgs, context: OpenContext): CodeWindow {
B
Benjamin Pasero 已提交
1162
		const lastActive = this.getLastActiveWindow();
E
Erich Gamma 已提交
1163
		if (lastActive) {
B
Benjamin Pasero 已提交
1164
			lastActive.focus();
1165 1166

			return lastActive;
E
Erich Gamma 已提交
1167 1168
		}

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

B
Benjamin Pasero 已提交
1173
	public getLastActiveWindow(): CodeWindow {
1174
		return getLastActiveWindow(WindowsManager.WINDOWS);
E
Erich Gamma 已提交
1175 1176
	}

1177 1178
	public openNewWindow(context: OpenContext): void {
		this.open({ context, cli: this.environmentService.args, forceNewWindow: true, forceEmpty: true });
E
Erich Gamma 已提交
1179 1180
	}

1181 1182 1183 1184 1185 1186 1187 1188 1189 1190 1191
	public waitForWindowClose(windowId: number): TPromise<void> {
		return new TPromise<void>(c => {
			const toDispose = this.onWindowClose(id => {
				if (id === windowId) {
					toDispose.dispose();
					c(null);
				}
			});
		});
	}

E
Erich Gamma 已提交
1192 1193 1194 1195
	public sendToFocused(channel: string, ...args: any[]): void {
		const focusedWindow = this.getFocusedWindow() || this.getLastActiveWindow();

		if (focusedWindow) {
1196
			focusedWindow.sendWhenReady(channel, ...args);
E
Erich Gamma 已提交
1197 1198 1199
		}
	}

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

1206
			w.sendWhenReady(channel, payload);
E
Erich Gamma 已提交
1207 1208 1209
		});
	}

B
Benjamin Pasero 已提交
1210
	public getFocusedWindow(): CodeWindow {
B
Benjamin Pasero 已提交
1211
		const win = BrowserWindow.getFocusedWindow();
E
Erich Gamma 已提交
1212 1213 1214 1215 1216 1217 1218
		if (win) {
			return this.getWindowById(win.id);
		}

		return null;
	}

B
Benjamin Pasero 已提交
1219
	public getWindowById(windowId: number): CodeWindow {
1220
		const res = WindowsManager.WINDOWS.filter(w => w.id === windowId);
E
Erich Gamma 已提交
1221 1222 1223 1224 1225 1226 1227
		if (res && res.length === 1) {
			return res[0];
		}

		return null;
	}

B
Benjamin Pasero 已提交
1228
	public getWindows(): CodeWindow[] {
E
Erich Gamma 已提交
1229 1230 1231 1232 1233 1234 1235
		return WindowsManager.WINDOWS;
	}

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

B
Benjamin Pasero 已提交
1236
	private onWindowError(codeWindow: CodeWindow, error: WindowError): void {
B
Benjamin Pasero 已提交
1237
		this.logService.error(error === WindowError.CRASHED ? '[VS Code]: render process crashed!' : '[VS Code]: detected unresponsive');
E
Erich Gamma 已提交
1238 1239 1240

		// Unresponsive
		if (error === WindowError.UNRESPONSIVE) {
B
Benjamin Pasero 已提交
1241
			dialog.showMessageBox(codeWindow.win, {
B
Benjamin Pasero 已提交
1242
				title: product.nameLong,
E
Erich Gamma 已提交
1243
				type: 'warning',
B
Benjamin Pasero 已提交
1244
				buttons: [nls.localize('reopen', "Reopen"), nls.localize('wait', "Keep Waiting"), nls.localize('close', "Close")],
1245
				message: nls.localize('appStalled', "The window is no longer responding"),
B
Benjamin Pasero 已提交
1246
				detail: nls.localize('appStalledDetail', "You can reopen or close the window or keep waiting."),
E
Erich Gamma 已提交
1247
				noLink: true
1248
			}, result => {
B
Benjamin Pasero 已提交
1249
				if (!codeWindow.win) {
1250 1251 1252
					return; // Return early if the window has been going down already
				}

E
Erich Gamma 已提交
1253
				if (result === 0) {
B
Benjamin Pasero 已提交
1254
					codeWindow.reload();
1255
				} else if (result === 2) {
B
Benjamin Pasero 已提交
1256 1257
					this.onBeforeWindowClose(codeWindow); // 'close' event will not be fired on destroy(), so run it manually
					codeWindow.win.destroy(); // make sure to destroy the window as it is unresponsive
E
Erich Gamma 已提交
1258 1259 1260 1261 1262 1263
				}
			});
		}

		// Crashed
		else {
B
Benjamin Pasero 已提交
1264
			dialog.showMessageBox(codeWindow.win, {
B
Benjamin Pasero 已提交
1265
				title: product.nameLong,
E
Erich Gamma 已提交
1266
				type: 'warning',
B
Benjamin Pasero 已提交
1267
				buttons: [nls.localize('reopen', "Reopen"), nls.localize('close', "Close")],
1268
				message: nls.localize('appCrashed', "The window has crashed"),
B
Benjamin Pasero 已提交
1269
				detail: nls.localize('appCrashedDetail', "We are sorry for the inconvenience! You can reopen the window to continue where you left off."),
E
Erich Gamma 已提交
1270
				noLink: true
1271
			}, result => {
B
Benjamin Pasero 已提交
1272
				if (!codeWindow.win) {
1273 1274 1275
					return; // Return early if the window has been going down already
				}

1276
				if (result === 0) {
B
Benjamin Pasero 已提交
1277
					codeWindow.reload();
1278
				} else if (result === 1) {
B
Benjamin Pasero 已提交
1279 1280
					this.onBeforeWindowClose(codeWindow); // 'close' event will not be fired on destroy(), so run it manually
					codeWindow.win.destroy(); // make sure to destroy the window as it has crashed
1281
				}
E
Erich Gamma 已提交
1282 1283 1284 1285
			});
		}
	}

B
Benjamin Pasero 已提交
1286
	private onWindowClosed(win: CodeWindow): void {
E
Erich Gamma 已提交
1287 1288 1289 1290 1291

		// Tell window
		win.dispose();

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

		// Emit
1296
		this._onWindowClose.fire(win.id);
E
Erich Gamma 已提交
1297
	}
B
Benjamin Pasero 已提交
1298

B
renames  
Benjamin Pasero 已提交
1299
	public pickFileFolderAndOpen(forceNewWindow?: boolean, data?: ITelemetryData): void {
B
Benjamin Pasero 已提交
1300
		this.fileDialog.pickAndOpen({ pickFolders: true, pickFiles: true, forceNewWindow }, 'openFileFolder', data);
1301 1302
	}

B
renames  
Benjamin Pasero 已提交
1303
	public pickFileAndOpen(forceNewWindow?: boolean, path?: string, window?: CodeWindow, data?: ITelemetryData): void {
B
Benjamin Pasero 已提交
1304
		this.fileDialog.pickAndOpen({ pickFiles: true, forceNewWindow, path, window, title: nls.localize('openFile', "Open File") }, 'openFile', data);
1305 1306
	}

B
renames  
Benjamin Pasero 已提交
1307
	public pickFolderAndOpen(forceNewWindow?: boolean, window?: CodeWindow, data?: ITelemetryData): void {
B
Benjamin Pasero 已提交
1308
		this.fileDialog.pickAndOpen({ pickFolders: true, forceNewWindow, window, title: nls.localize('openFolder', "Open Folder") }, 'openFolder', data);
B
Benjamin Pasero 已提交
1309 1310 1311 1312 1313 1314 1315 1316 1317 1318 1319 1320 1321 1322 1323 1324 1325
	}

	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.
		const codeWindow = this.getFocusedWindow();
		if (codeWindow && codeWindow.isExtensionDevelopmentHost && this.getWindowCount() > 1) {
			codeWindow.win.close();
		}

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

interface INativeOpenDialogOptions {
B
Benjamin Pasero 已提交
1330
	title?: string;
B
Benjamin Pasero 已提交
1331 1332 1333 1334 1335
	pickFolders?: boolean;
	pickFiles?: boolean;
	path?: string;
	forceNewWindow?: boolean;
	window?: CodeWindow;
1336
	buttonLabel?: string;
B
Benjamin Pasero 已提交
1337 1338 1339 1340 1341
}

class FileDialog {

	private static workingDirPickerStorageKey = 'pickerWorkingDir';
1342

B
Benjamin Pasero 已提交
1343 1344 1345 1346 1347 1348 1349 1350 1351
	constructor(
		private environmentService: IEnvironmentService,
		private telemetryService: ITelemetryService,
		private storageService: IStorageService,
		private windowsMainService: IWindowsMainService
	) {
	}

	public pickAndOpen(options: INativeOpenDialogOptions, eventName: string, data?: ITelemetryData): void {
1352 1353 1354
		this.getFileOrFolderPaths(options, (paths: string[]) => {
			const nOfPaths = paths ? paths.length : 0;
			if (nOfPaths) {
B
Benjamin Pasero 已提交
1355
				this.windowsMainService.open({ context: OpenContext.DIALOG, cli: this.environmentService.args, pathsToOpen: paths, forceNewWindow: options.forceNewWindow });
1356 1357 1358 1359 1360 1361 1362 1363 1364
			}
			this.telemetryService.publicLog(eventName, {
				...data,
				outcome: nOfPaths ? 'success' : 'canceled',
				nOfPaths
			});
		});
	}

1365
	public getFileOrFolderPaths(options: INativeOpenDialogOptions, clb: (paths: string[]) => void): void {
B
Benjamin Pasero 已提交
1366 1367
		const workingDir = options.path || this.storageService.getItem<string>(FileDialog.workingDirPickerStorageKey);
		const focussedWindow = options.window || this.windowsMainService.getFocusedWindow();
1368 1369 1370 1371 1372 1373 1374 1375 1376

		let pickerProperties: ('openFile' | 'openDirectory' | 'multiSelections' | 'createDirectory')[];
		if (options.pickFiles && options.pickFolders) {
			pickerProperties = ['multiSelections', 'openDirectory', 'openFile', 'createDirectory'];
		} else {
			pickerProperties = ['multiSelections', options.pickFolders ? 'openDirectory' : 'openFile', 'createDirectory'];
		}

		dialog.showOpenDialog(focussedWindow && focussedWindow.win, {
B
Benjamin Pasero 已提交
1377
			title: options && options.title ? options.title : void 0,
1378
			defaultPath: workingDir,
1379 1380
			properties: pickerProperties,
			buttonLabel: options && options.buttonLabel ? options.buttonLabel : void 0
1381 1382 1383 1384
		}, paths => {
			if (paths && paths.length > 0) {

				// Remember path in storage for next time
B
Benjamin Pasero 已提交
1385
				this.storageService.setItem(FileDialog.workingDirPickerStorageKey, path.dirname(paths[0]));
1386 1387 1388 1389 1390 1391 1392 1393

				// Return
				clb(paths);
			} else {
				clb(void (0));
			}
		});
	}
1394
}