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

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';
13
import URI from 'vs/base/common/uri';
D
Daniel Imms 已提交
14
import { IBackupMainService } from 'vs/platform/backup/common/backup';
J
Joao Moreno 已提交
15
import { IEnvironmentService, ParsedArgs } from 'vs/platform/environment/common/environment';
16
import { IStorageService } from 'vs/platform/storage/node/storage';
17
import { CodeWindow, IWindowState as ISingleWindowState, defaultWindowState, WindowMode } from 'vs/code/electron-main/window';
18
import { ipcMain as ipc, screen, BrowserWindow, dialog, systemPreferences } from 'electron';
B
Benjamin Pasero 已提交
19
import { IPathWithLineAndColumn, parseLineAndColumnAware } from 'vs/code/node/paths';
20
import { ILifecycleService, UnloadReason } from 'vs/platform/lifecycle/electron-main/lifecycleMain';
21
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
22
import { ILogService } from 'vs/platform/log/common/log';
B
Benjamin Pasero 已提交
23
import { IWindowSettings, OpenContext, IPath, IWindowConfiguration } from 'vs/platform/windows/common/windows';
C
Christof Marti 已提交
24
import { getLastActiveWindow, findBestWindowOrFolder } from 'vs/code/node/windowsUtils';
25
import CommonEvent, { Emitter } from 'vs/base/common/event';
26
import product from 'vs/platform/node/product';
27
import { ITelemetryService, ITelemetryData } from 'vs/platform/telemetry/common/telemetry';
28
import { isEqual, isEqualOrParent } from 'vs/base/common/paths';
29 30
import { IWindowsMainService, IOpenConfiguration } from "vs/platform/windows/electron-main/windows";
import { IHistoryMainService } from "vs/platform/history/electron-main/historyMainService";
B
Benjamin Pasero 已提交
31
import { IProcessEnvironment, isLinux, isMacintosh, isWindows } from 'vs/base/common/platform';
32
import { TPromise } from "vs/base/common/winjs.base";
E
Erich Gamma 已提交
33

34

E
Erich Gamma 已提交
35 36 37 38 39
enum WindowError {
	UNRESPONSIVE,
	CRASHED
}

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

E
Erich Gamma 已提交
44 45
interface IWindowState {
	workspacePath?: string;
46
	backupPath: string;
J
Joao Moreno 已提交
47
	uiState: ISingleWindowState;
E
Erich Gamma 已提交
48 49 50 51 52
}

interface IWindowsState {
	lastActiveWindow?: IWindowState;
	lastPluginDevelopmentHostWindow?: IWindowState;
53 54
	openedWindows: IWindowState[];
	openedFolders?: IWindowState[]; // TODO@Ben deprecated
E
Erich Gamma 已提交
55 56
}

57
type RestoreWindowsSetting = 'all' | 'folders' | 'one' | 'none';
58

B
Benjamin Pasero 已提交
59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75
interface IOpenBrowserWindowOptions {
	userEnv?: IProcessEnvironment;
	cli?: ParsedArgs;
	workspacePath?: string;

	initialStartup?: boolean;

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

	forceNewWindow?: boolean;
	windowToUse?: CodeWindow;

	emptyWorkspaceBackupFolder?: string;
}

76 77 78 79 80 81 82 83 84 85 86 87
interface IWindowToOpen extends IPath {

	// the workspace spath for a Code instance to open
	workspacePath?: string;

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

90
	_serviceBrand: any;
E
Erich Gamma 已提交
91 92 93

	private static windowsStateStorageKey = 'windowsState';

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

B
Benjamin Pasero 已提交
96
	private initialUserEnv: IProcessEnvironment;
97

E
Erich Gamma 已提交
98
	private windowsState: IWindowsState;
99
	private lastClosedWindowState: IWindowState;
E
Erich Gamma 已提交
100

B
Benjamin Pasero 已提交
101 102
	private fileDialog: FileDialog;

B
Benjamin Pasero 已提交
103 104
	private _onWindowReady = new Emitter<CodeWindow>();
	onWindowReady: CommonEvent<CodeWindow> = this._onWindowReady.event;
105 106 107 108

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

109 110 111
	private _onWindowReload = new Emitter<number>();
	onWindowReload: CommonEvent<number> = this._onWindowReload.event;

B
Benjamin Pasero 已提交
112
	private _onPathsOpen = new Emitter<IPath[]>();
113
	onPathsOpen: CommonEvent<IPath[]> = this._onPathsOpen.event;
114

J
Joao Moreno 已提交
115 116
	constructor(
		@ILogService private logService: ILogService,
J
Joao Moreno 已提交
117
		@IStorageService private storageService: IStorageService,
118
		@IEnvironmentService private environmentService: IEnvironmentService,
B
Benjamin Pasero 已提交
119
		@ILifecycleService private lifecycleService: ILifecycleService,
120
		@IBackupMainService private backupService: IBackupMainService,
121
		@ITelemetryService private telemetryService: ITelemetryService,
122 123
		@IConfigurationService private configurationService: IConfigurationService,
		@IHistoryMainService private historyService: IHistoryMainService
124
	) {
125 126 127 128 129 130 131 132 133 134
		this.windowsState = this.storageService.getItem<IWindowsState>(WindowsManager.windowsStateStorageKey) || { openedWindows: [] };

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

B
Benjamin Pasero 已提交
135
		this.fileDialog = new FileDialog(environmentService, telemetryService, storageService, this);
136
	}
J
Joao Moreno 已提交
137

B
Benjamin Pasero 已提交
138
	public ready(initialUserEnv: IProcessEnvironment): void {
139
		this.initialUserEnv = initialUserEnv;
140 141

		this.registerListeners();
E
Erich Gamma 已提交
142 143 144
	}

	private registerListeners(): void {
145

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

B
Benjamin Pasero 已提交
150
			const win = this.getWindowById(windowId);
E
Erich Gamma 已提交
151 152 153 154
			if (win) {
				win.setReady();

				// Event
155
				this._onWindowReady.fire(win);
E
Erich Gamma 已提交
156 157 158
			}
		});

159 160 161 162 163 164 165 166 167 168 169
		// 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');
				}
			});
		}

170
		// Update our windows state before quitting and before closing windows
B
Benjamin Pasero 已提交
171
		this.lifecycleService.onBeforeWindowClose(win => this.onBeforeWindowClose(win as CodeWindow));
172 173 174 175 176 177 178 179 180 181 182 183
		this.lifecycleService.onBeforeQuit(() => this.onBeforeQuit());
	}

	// 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 {
		const currentWindowsState: IWindowsState = {
184 185
			openedWindows: [],
			openedFolders: [], // TODO@Ben migration so that old clients do not fail over data (prevents NPEs)
186
			lastPluginDevelopmentHostWindow: this.windowsState.lastPluginDevelopmentHostWindow,
187
			lastActiveWindow: this.lastClosedWindowState
188 189 190 191 192 193 194 195
		};

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

197
			if (activeWindow) {
198
				currentWindowsState.lastActiveWindow = { workspacePath: activeWindow.openedWorkspacePath, uiState: activeWindow.serializeWindowState(), backupPath: activeWindow.backupPath };
E
Erich Gamma 已提交
199
			}
200 201 202 203 204
		}

		// 2.) Find extension host window
		const extensionHostWindow = WindowsManager.WINDOWS.filter(w => w.isExtensionDevelopmentHost && !w.isExtensionTestHost)[0];
		if (extensionHostWindow) {
205
			currentWindowsState.lastPluginDevelopmentHostWindow = { workspacePath: extensionHostWindow.openedWorkspacePath, uiState: extensionHostWindow.serializeWindowState(), backupPath: extensionHostWindow.backupPath };
206
		}
E
Erich Gamma 已提交
207

208
		// 3.) All windows (except extension host) for N >= 2 to support restoreWindows: all or for auto update
209 210 211 212 213
		//
		// 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) {
214
			currentWindowsState.openedWindows = WindowsManager.WINDOWS.filter(w => !w.isExtensionDevelopmentHost).map(w => {
E
Erich Gamma 已提交
215 216
				return <IWindowState>{
					workspacePath: w.openedWorkspacePath,
217 218
					uiState: w.serializeWindowState(),
					backupPath: w.backupPath
B
Benjamin Pasero 已提交
219
				};
E
Erich Gamma 已提交
220
			});
221
		}
E
Erich Gamma 已提交
222

223 224 225
		// Persist
		this.storageService.setItem(WindowsManager.windowsStateStorageKey, currentWindowsState);
	}
226

227
	// See note on #onBeforeQuit() for details how these events are flowing
B
Benjamin Pasero 已提交
228
	private onBeforeWindowClose(win: CodeWindow): void {
229 230 231 232 233
		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
234
		const state: IWindowState = { workspacePath: win.openedWorkspacePath, uiState: win.serializeWindowState(), backupPath: win.backupPath };
235 236 237 238 239 240
		if (win.isExtensionDevelopmentHost && !win.isExtensionTestHost) {
			this.windowsState.lastPluginDevelopmentHostWindow = state; // do not let test run window state overwrite our extension development state
		}

		// Any non extension host window with same workspace
		else if (!win.isExtensionDevelopmentHost && !!win.openedWorkspacePath) {
241
			this.windowsState.openedWindows.forEach(o => {
B
Benjamin Pasero 已提交
242
				if (isEqual(o.workspacePath, win.openedWorkspacePath, !isLinux /* ignorecase */)) {
243 244 245 246 247 248 249
					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.
250 251 252
		// 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) {
253 254
			this.lastClosedWindowState = state;
		}
E
Erich Gamma 已提交
255 256
	}

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

260 261 262 263 264 265 266 267 268 269 270 271 272 273 274
		//
		// 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.workspacePath && !win.filePath).map(win => win.workspacePath), folder => isLinux ? folder : folder.toLowerCase()); // prevent duplicates
		const emptyToOpen = windowsToOpen.filter(win => !win.workspacePath && !win.filePath && !win.backupPath).length;

		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 已提交
275 276
		}

277 278 279 280 281
		//
		// These are windows to restore because of hot-exit
		//
		const hotExitRestore = (openConfig.initialStartup && !openConfig.cli.extensionDevelopmentPath);
		const foldersToRestore = hotExitRestore ? this.backupService.getWorkspaceBackupPaths() : [];
282
		let emptyToRestore = hotExitRestore ? this.backupService.getEmptyWorkspaceBackupPaths() : [];
283
		emptyToRestore.push(...windowsToOpen.filter(w => !w.workspacePath && w.backupPath).map(w => path.basename(w.backupPath))); // add empty windows with backupPath
284
		emptyToRestore = arrays.distinct(emptyToRestore); // prevent duplicates
285 286 287 288 289 290 291 292 293 294 295

		// Open based on config
		const usedWindows = this.doOpen(openConfig, foldersToOpen, foldersToRestore, emptyToRestore, emptyToOpen, filesToOpen, filesToCreate, filesToDiff);

		// 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();
			}
		}
296

297 298 299 300 301 302 303 304 305 306 307 308 309
		// 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) {
			const recentPaths: { path: string; isFile?: boolean; }[] = [];

			windowsToOpen.forEach(win => {
				if (win.filePath || win.workspacePath) {
					recentPaths.push({ path: win.filePath || win.workspacePath, isFile: !!win.filePath });
				}
			});

			if (recentPaths.length) {
				this.historyService.addToRecentPathsList(recentPaths);
310
			}
311
		}
312

313 314 315
		// Emit events
		if (windowsToOpen.length) {
			this._onPathsOpen.fire(windowsToOpen);
316 317
		}

318 319 320 321 322 323 324 325 326 327 328 329 330 331
		return usedWindows;
	}

	private doOpen(
		openConfig: IOpenConfiguration,
		foldersToOpen: string[],
		foldersToRestore: string[],
		emptyToRestore: string[],
		emptyToOpen: number,
		filesToOpen: IPath[],
		filesToCreate: IPath[],
		filesToDiff: IPath[]
	) {

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

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

			// Open Files in last instance if any and flag tells us so
340 341 342 343 344 345 346 347 348
			const fileToCheck = filesToOpen[0] || filesToCreate[0] || filesToDiff[0];
			const windowOrFolder = findBestWindowOrFolder({
				windows: WindowsManager.WINDOWS,
				newWindow: openFilesInNewWindow,
				reuseWindow: openConfig.forceReuseWindow,
				context: openConfig.context,
				filePath: fileToCheck && fileToCheck.filePath,
				userHome: this.environmentService.userHome
			});
B
Benjamin Pasero 已提交
349

B
Benjamin Pasero 已提交
350
			if (windowOrFolder instanceof CodeWindow) {
351
				windowOrFolder.focus();
352
				const files = { filesToOpen, filesToCreate, filesToDiff }; // copy to object because they get reset shortly after
353
				windowOrFolder.ready().then(readyWindow => {
354
					readyWindow.send('vscode:openFiles', files);
E
Erich Gamma 已提交
355
				});
356

357
				usedWindows.push(windowOrFolder);
E
Erich Gamma 已提交
358 359 360 361
			}

			// Otherwise open instance with files
			else {
B
Benjamin Pasero 已提交
362 363 364 365 366 367 368 369 370 371
				const browserWindow = this.openInBrowserWindow({
					userEnv: openConfig.userEnv,
					cli: openConfig.cli,
					initialStartup: openConfig.initialStartup,
					workspacePath: windowOrFolder,
					filesToOpen,
					filesToCreate,
					filesToDiff,
					forceNewWindow: true
				});
372
				usedWindows.push(browserWindow);
E
Erich Gamma 已提交
373

B
Benjamin Pasero 已提交
374
				openFolderInNewWindow = true; // any other folders to open must open in new window then
E
Erich Gamma 已提交
375
			}
376 377 378 379 380

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

383
		// Handle folders to open (instructed and to restore)
B
Benjamin Pasero 已提交
384
		let allFoldersToOpen = arrays.distinct([...foldersToOpen, ...foldersToRestore], folder => isLinux ? folder : folder.toLowerCase()); // prevent duplicates
385
		if (allFoldersToOpen.length > 0) {
E
Erich Gamma 已提交
386 387

			// Check for existing instances
388
			const windowsOnWorkspacePath = arrays.coalesce(allFoldersToOpen.map(folderToOpen => this.findWindow(folderToOpen)));
E
Erich Gamma 已提交
389
			if (windowsOnWorkspacePath.length > 0) {
B
Benjamin Pasero 已提交
390
				const browserWindow = windowsOnWorkspacePath[0];
391
				browserWindow.focus(); // just focus one of them
B
Benjamin Pasero 已提交
392

393
				const files = { filesToOpen, filesToCreate, filesToDiff }; // copy to object because they get reset shortly after
394
				browserWindow.ready().then(readyWindow => {
395
					readyWindow.send('vscode:openFiles', files);
E
Erich Gamma 已提交
396 397
				});

398 399
				usedWindows.push(browserWindow);

E
Erich Gamma 已提交
400 401 402
				// Reset these because we handled them
				filesToOpen = [];
				filesToCreate = [];
403
				filesToDiff = [];
E
Erich Gamma 已提交
404

B
Benjamin Pasero 已提交
405
				openFolderInNewWindow = true; // any other folders to open must open in new window then
E
Erich Gamma 已提交
406 407 408
			}

			// Open remaining ones
409
			allFoldersToOpen.forEach(folderToOpen => {
B
Benjamin Pasero 已提交
410
				if (windowsOnWorkspacePath.some(win => isEqual(win.openedWorkspacePath, folderToOpen, !isLinux /* ignorecase */))) {
E
Erich Gamma 已提交
411 412 413
					return; // ignore folders that are already open
				}

B
Benjamin Pasero 已提交
414 415 416 417 418 419 420 421 422 423 424
				const browserWindow = this.openInBrowserWindow({
					userEnv: openConfig.userEnv,
					cli: openConfig.cli,
					initialStartup: openConfig.initialStartup,
					workspacePath: folderToOpen,
					filesToOpen,
					filesToCreate,
					filesToDiff,
					forceNewWindow: openFolderInNewWindow,
					windowToUse: openFolderInNewWindow ? void 0 : openConfig.windowToUse as CodeWindow
				});
425
				usedWindows.push(browserWindow);
E
Erich Gamma 已提交
426 427 428 429

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

B
Benjamin Pasero 已提交
432
				openFolderInNewWindow = true; // any other folders to open must open in new window then
E
Erich Gamma 已提交
433 434 435 436
			});
		}

		// Handle empty
437
		if (emptyToRestore.length > 0) {
438
			emptyToRestore.forEach(emptyWorkspaceBackupFolder => {
B
Benjamin Pasero 已提交
439 440 441 442 443 444 445 446 447 448
				const browserWindow = this.openInBrowserWindow({
					userEnv: openConfig.userEnv,
					cli: openConfig.cli,
					initialStartup: openConfig.initialStartup,
					filesToOpen,
					filesToCreate,
					filesToDiff,
					forceNewWindow: true,
					emptyWorkspaceBackupFolder
				});
449 450
				usedWindows.push(browserWindow);

B
wip  
Benjamin Pasero 已提交
451 452 453 454 455
				// Reset these because we handled them
				filesToOpen = [];
				filesToCreate = [];
				filesToDiff = [];

B
Benjamin Pasero 已提交
456
				openFolderInNewWindow = true; // any other folders to open must open in new window then
457 458
			});
		}
B
Benjamin Pasero 已提交
459

460
		// Only open empty if no empty workspaces were restored
461 462
		else if (emptyToOpen > 0) {
			for (let i = 0; i < emptyToOpen; i++) {
B
Benjamin Pasero 已提交
463 464 465 466 467 468 469
				const browserWindow = this.openInBrowserWindow({
					userEnv: openConfig.userEnv,
					cli: openConfig.cli,
					initialStartup: openConfig.initialStartup,
					forceNewWindow: openFolderInNewWindow,
					windowToUse: openFolderInNewWindow ? void 0 : openConfig.windowToUse as CodeWindow
				});
470
				usedWindows.push(browserWindow);
E
Erich Gamma 已提交
471

B
Benjamin Pasero 已提交
472
				openFolderInNewWindow = true; // any other folders to open must open in new window then
473 474
			}
		}
E
Erich Gamma 已提交
475

476
		return arrays.distinct(usedWindows);
E
Erich Gamma 已提交
477 478
	}

479 480
	private getWindowsToOpen(openConfig: IOpenConfiguration): IWindowToOpen[] {
		let windowsToOpen: IWindowToOpen[];
E
Erich Gamma 已提交
481

482
		// Extract paths: from API
B
Benjamin Pasero 已提交
483
		if (openConfig.pathsToOpen && openConfig.pathsToOpen.length > 0) {
484
			windowsToOpen = this.doExtractPathsFromAPI(openConfig.pathsToOpen, openConfig.cli && openConfig.cli.goto);
E
Erich Gamma 已提交
485 486
		}

B
Benjamin Pasero 已提交
487 488
		// Check for force empty
		else if (openConfig.forceEmpty) {
489
			windowsToOpen = [Object.create(null)];
E
Erich Gamma 已提交
490 491
		}

492
		// Extract paths: from CLI
B
Benjamin Pasero 已提交
493
		else if (openConfig.cli._.length > 0) {
494
			windowsToOpen = this.doExtractPathsFromCLI(openConfig.cli);
B
Benjamin Pasero 已提交
495 496
		}

497
		// Extract windows: from previous session
B
Benjamin Pasero 已提交
498
		else {
499
			windowsToOpen = this.doGetWindowsFromLastSession();
B
Benjamin Pasero 已提交
500 501
		}

502
		return windowsToOpen;
E
Erich Gamma 已提交
503 504
	}

505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526
	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 已提交
527

528 529 530 531 532 533 534 535 536 537 538 539 540
			return path;
		});

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

		return pathsToOpen;
	}

	private doExtractPathsFromCLI(cli: ParsedArgs): IPath[] {
		const pathsToOpen = cli._.map(candidate => this.parsePath(candidate, true /* ignoreFileNotFound */, cli.goto)).filter(path => !!path);
		if (pathsToOpen.length > 0) {
			return pathsToOpen;
B
Benjamin Pasero 已提交
541 542 543 544 545 546
		}

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

547 548 549
	private doGetWindowsFromLastSession(): IWindowToOpen[] {
		const restoreWindows = this.getRestoreWindowsSetting();
		const lastActiveWindow = this.windowsState.lastActiveWindow;
B
Benjamin Pasero 已提交
550

551
		switch (restoreWindows) {
B
Benjamin Pasero 已提交
552

553 554 555
			// none: we always open an empty window
			case 'none':
				return [Object.create(null)];
B
Benjamin Pasero 已提交
556

557 558 559
			// one: restore last opened folder or empty window
			case 'one':
				if (lastActiveWindow) {
B
Benjamin Pasero 已提交
560

561 562 563 564 565 566 567 568
					// return folder path if it is valid
					const folder = lastActiveWindow.workspacePath;
					if (folder) {
						const validatedFolderPath = this.parsePath(folder);
						if (validatedFolderPath) {
							return [validatedFolderPath];
						}
					}
B
Benjamin Pasero 已提交
569

570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587
					// otherwise use backup path to restore empty windows
					else if (lastActiveWindow.backupPath) {
						return [{ backupPath: lastActiveWindow.backupPath }];
					}
				}
				break;

			// all: restore all windows
			// folders: restore last opened folders only
			case 'all':
			case 'folders':

				// Windows with Folders
				const lastOpenedFolders = this.windowsState.openedWindows.filter(w => !!w.workspacePath).map(o => o.workspacePath);
				const lastActiveFolder = lastActiveWindow && lastActiveWindow.workspacePath;
				if (lastActiveFolder) {
					lastOpenedFolders.push(lastActiveFolder);
				}
B
Benjamin Pasero 已提交
588

589
				const windowsToOpen = lastOpenedFolders.map(candidate => this.parsePath(candidate)).filter(path => !!path);
B
Benjamin Pasero 已提交
590

591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606
				// Windows that were Empty
				if (restoreWindows === 'all') {
					const lastOpenedEmpty = this.windowsState.openedWindows.filter(w => !w.workspacePath && w.backupPath).map(w => w.backupPath);
					const lastActiveEmpty = lastActiveWindow && !lastActiveWindow.workspacePath && lastActiveWindow.backupPath;
					if (lastActiveEmpty) {
						lastOpenedEmpty.push(lastActiveEmpty);
					}

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

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

				break;
B
Benjamin Pasero 已提交
607
		}
E
Erich Gamma 已提交
608

609
		// Always fallback to empty window
B
Benjamin Pasero 已提交
610
		return [Object.create(null)];
E
Erich Gamma 已提交
611 612
	}

613 614 615 616 617 618
	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');
619
			restoreWindows = ((windowConfig && windowConfig.restoreWindows) || 'one') as RestoreWindowsSetting;
620

B
fix npe  
Benjamin Pasero 已提交
621
			if (restoreWindows === 'one' /* default */ && windowConfig && windowConfig.reopenFolders) {
622 623 624 625 626 627 628 629 630 631 632 633
				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 已提交
634 635 636 637
		if (!anyPath) {
			return null;
		}

638
		let parsedPath: IPathWithLineAndColumn;
E
Erich Gamma 已提交
639
		if (gotoLineMode) {
J
Joao Moreno 已提交
640
			parsedPath = parseLineAndColumnAware(anyPath);
E
Erich Gamma 已提交
641 642 643
			anyPath = parsedPath.path;
		}

B
Benjamin Pasero 已提交
644
		const candidate = path.normalize(anyPath);
E
Erich Gamma 已提交
645
		try {
B
Benjamin Pasero 已提交
646
			const candidateStat = fs.statSync(candidate);
E
Erich Gamma 已提交
647 648 649 650 651
			if (candidateStat) {
				return candidateStat.isFile() ?
					{
						filePath: candidate,
						lineNumber: gotoLineMode ? parsedPath.line : void 0,
652
						columnNumber: gotoLineMode ? parsedPath.column : void 0
E
Erich Gamma 已提交
653 654 655 656
					} :
					{ workspacePath: candidate };
			}
		} catch (error) {
657
			this.historyService.removeFromRecentPathsList(candidate); // since file does not seem to exist anymore, remove from recent
658

E
Erich Gamma 已提交
659 660 661 662 663 664 665 666
			if (ignoreFileNotFound) {
				return { filePath: candidate, createFilePath: true }; // assume this is a file that does not yet exist
			}
		}

		return null;
	}

B
Benjamin Pasero 已提交
667 668 669 670
	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');
671 672 673
		const openFolderInNewWindowConfig = (windowConfig && windowConfig.openFoldersInNewWindow) || 'default' /* default */;
		const openFilesInNewWindowConfig = (windowConfig && windowConfig.openFilesInNewWindow) || 'off' /* default */;

B
Benjamin Pasero 已提交
674
		let openFolderInNewWindow = (openConfig.preferNewWindow || openConfig.forceNewWindow) && !openConfig.forceReuseWindow;
675 676
		if (!openConfig.forceNewWindow && !openConfig.forceReuseWindow && (openFolderInNewWindowConfig === 'on' || openFolderInNewWindowConfig === 'off')) {
			openFolderInNewWindow = (openFolderInNewWindowConfig === 'on');
B
Benjamin Pasero 已提交
677 678 679 680 681 682 683 684 685 686 687
		}

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

688 689
			if (!openConfig.cli.extensionDevelopmentPath && (openFilesInNewWindowConfig === 'on' || openFilesInNewWindowConfig === 'off')) {
				openFilesInNewWindow = (openFilesInNewWindowConfig === 'on');
B
Benjamin Pasero 已提交
690 691 692 693 694 695
			}
		}

		return { openFolderInNewWindow, openFilesInNewWindow };
	}

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

B
Benjamin Pasero 已提交
698 699 700 701 702 703 704
		// 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 已提交
705

B
Benjamin Pasero 已提交
706 707
			return;
		}
E
Erich Gamma 已提交
708

B
Benjamin Pasero 已提交
709 710 711 712 713
		// Fill in previously opened workspace unless an explicit path is provided and we are not unit testing
		if (openConfig.cli._.length === 0 && !openConfig.cli.extensionTestsPath) {
			const workspaceToOpen = this.windowsState.lastPluginDevelopmentHostWindow && this.windowsState.lastPluginDevelopmentHostWindow.workspacePath;
			if (workspaceToOpen) {
				openConfig.cli._ = [workspaceToOpen];
E
Erich Gamma 已提交
714 715 716
			}
		}

B
Benjamin Pasero 已提交
717 718 719 720 721 722
		// Make sure we are not asked to open a path that is already opened
		if (openConfig.cli._.length > 0) {
			res = WindowsManager.WINDOWS.filter(w => w.openedWorkspacePath && openConfig.cli._.indexOf(w.openedWorkspacePath) >= 0);
			if (res.length) {
				openConfig.cli._ = [];
			}
E
Erich Gamma 已提交
723 724
		}

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

B
Benjamin Pasero 已提交
729 730 731 732 733 734 735 736 737 738 739 740 741 742
	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;
		configuration.workspacePath = options.workspacePath;
		configuration.filesToOpen = options.filesToOpen;
		configuration.filesToCreate = options.filesToCreate;
		configuration.filesToDiff = options.filesToDiff;
		configuration.nodeCachedDataDir = this.environmentService.nodeCachedDataDir;

743 744 745 746 747 748 749 750
		// if we know the backup folder upfront (for empty workspaces to restore), we can set it
		// directly here which helps for restoring UI state associated with that window.
		// For all other cases we first call into registerWindowForBackupsSync() to set it before
		// loading the window.
		if (options.emptyWorkspaceBackupFolder) {
			configuration.backupPath = path.join(this.environmentService.backupHome, options.emptyWorkspaceBackupFolder);
		}

B
Benjamin Pasero 已提交
751
		let codeWindow: CodeWindow;
E
Erich Gamma 已提交
752

B
Benjamin Pasero 已提交
753 754
		if (!options.forceNewWindow) {
			codeWindow = options.windowToUse || this.getLastActiveWindow();
E
Erich Gamma 已提交
755

B
Benjamin Pasero 已提交
756 757
			if (codeWindow) {
				codeWindow.focus();
E
Erich Gamma 已提交
758 759 760 761
			}
		}

		// New window
B
Benjamin Pasero 已提交
762
		if (!codeWindow) {
763
			const windowConfig = this.configurationService.getConfiguration<IWindowSettings>('window');
764 765 766 767 768 769 770 771 772 773
			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 {
774
				allowFullscreen = this.lifecycleService.wasRestarted || (windowConfig && windowConfig.restoreFullscreen);
775 776 777 778 779
			}

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

B
Benjamin Pasero 已提交
781
			codeWindow = new CodeWindow({
782
				state,
783
				extensionDevelopmentPath: configuration.extensionDevelopmentPath,
784
				isExtensionTestHost: !!configuration.extensionTestsPath
J
Johannes Rieken 已提交
785 786 787
			},
				this.logService,
				this.environmentService,
788 789
				this.configurationService,
				this.storageService
J
Johannes Rieken 已提交
790
			);
791

B
Benjamin Pasero 已提交
792
			WindowsManager.WINDOWS.push(codeWindow);
E
Erich Gamma 已提交
793 794

			// Window Events
B
Benjamin Pasero 已提交
795 796 797 798 799
			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 已提交
800

801 802 803 804 805 806 807 808 809 810 811 812
			// Prevent loading on svgs in main renderer
			codeWindow.win.webContents.session.webRequest.onBeforeRequest((details, callback) => {
				if (details.url.indexOf('.svg') > 0) {
					const uri = URI.parse(details.url);
					if (uri && !uri.scheme.match(/file/i) && (uri.path as any).endsWith('.svg')) {
						return callback({ cancel: true });
					}
				}
				return callback({});
			});

			codeWindow.win.webContents.session.webRequest.onHeadersReceived((details, callback) => {
M
Matt Bierner 已提交
813
				const contentType: string[] = (details.responseHeaders['content-type'] || details.responseHeaders['Content-Type']) as any;
814 815 816
				if (contentType && Array.isArray(contentType) && contentType.some(x => x.toLowerCase().indexOf('image/svg') >= 0)) {
					return callback({ cancel: true });
				}
M
Matt Bierner 已提交
817
				return callback({ cancel: false, responseHeaders: details.responseHeaders });
818 819
			});

E
Erich Gamma 已提交
820
			// Lifecycle
B
Benjamin Pasero 已提交
821
			this.lifecycleService.registerWindow(codeWindow);
E
Erich Gamma 已提交
822 823 824 825 826 827
		}

		// Existing window
		else {

			// Some configuration things get inherited if the window is being reused and we are
B
Benjamin Pasero 已提交
828
			// in extension development host mode. These options are all development related.
B
Benjamin Pasero 已提交
829
			const currentWindowConfig = codeWindow.config;
A
Alex Dima 已提交
830 831
			if (!configuration.extensionDevelopmentPath && currentWindowConfig && !!currentWindowConfig.extensionDevelopmentPath) {
				configuration.extensionDevelopmentPath = currentWindowConfig.extensionDevelopmentPath;
B
Benjamin Pasero 已提交
832
				configuration.verbose = currentWindowConfig.verbose;
833
				configuration.debugBrkPluginHost = currentWindowConfig.debugBrkPluginHost;
834
				configuration.debugPluginHost = currentWindowConfig.debugPluginHost;
835
				configuration['extensions-dir'] = currentWindowConfig['extensions-dir'];
E
Erich Gamma 已提交
836 837 838 839
			}
		}

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

B
Benjamin Pasero 已提交
843 844
				// Register window for backups
				if (!configuration.extensionDevelopmentPath) {
845 846
					const backupPath = this.backupService.registerWindowForBackupsSync(codeWindow.id, !configuration.workspacePath, options.emptyWorkspaceBackupFolder, configuration.workspacePath);
					configuration.backupPath = backupPath;
B
Benjamin Pasero 已提交
847 848
				}

E
Erich Gamma 已提交
849
				// Load it
B
Benjamin Pasero 已提交
850
				codeWindow.load(configuration);
E
Erich Gamma 已提交
851 852
			}
		});
853

B
Benjamin Pasero 已提交
854
		return codeWindow;
E
Erich Gamma 已提交
855 856
	}

857
	private getNewWindowState(configuration: IWindowConfiguration): INewWindowState {
E
Erich Gamma 已提交
858

B
Benjamin Pasero 已提交
859
		// extension development host Window - load from stored settings if any
A
Alex Dima 已提交
860
		if (!!configuration.extensionDevelopmentPath && this.windowsState.lastPluginDevelopmentHostWindow) {
E
Erich Gamma 已提交
861 862 863 864 865
			return this.windowsState.lastPluginDevelopmentHostWindow.uiState;
		}

		// Known Folder - load from stored settings if any
		if (configuration.workspacePath) {
866 867 868 869 870 871 872 873 874
			const stateForWorkspace = this.windowsState.openedWindows.filter(o => isEqual(o.workspacePath, configuration.workspacePath, !isLinux /* ignorecase */)).map(o => o.uiState);
			if (stateForWorkspace.length) {
				return stateForWorkspace[0];
			}
		}

		// Empty workspace with backups
		else if (configuration.backupPath) {
			const stateForWorkspace = this.windowsState.openedWindows.filter(o => o.backupPath === configuration.backupPath).map(o => o.uiState);
E
Erich Gamma 已提交
875 876 877 878 879 880
			if (stateForWorkspace.length) {
				return stateForWorkspace[0];
			}
		}

		// First Window
B
Benjamin Pasero 已提交
881
		const lastActive = this.getLastActiveWindow();
882 883 884
		const lastActiveState = this.lastClosedWindowState || this.windowsState.lastActiveWindow;
		if (!lastActive && lastActiveState) {
			return lastActiveState.uiState;
E
Erich Gamma 已提交
885 886 887 888 889 890 891
		}

		//
		// 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
892
		let displayToUse: Electron.Display;
B
Benjamin Pasero 已提交
893
		const displays = screen.getAllDisplays();
E
Erich Gamma 已提交
894 895 896 897 898 899 900 901 902 903

		// 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 已提交
904
			if (isMacintosh) {
B
Benjamin Pasero 已提交
905
				const cursorPoint = screen.getCursorScreenPoint();
E
Erich Gamma 已提交
906 907 908 909 910 911 912 913
				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());
			}

914
			// fallback to primary display or first display
E
Erich Gamma 已提交
915
			if (!displayToUse) {
916
				displayToUse = screen.getPrimaryDisplay() || displays[0];
E
Erich Gamma 已提交
917 918 919
			}
		}

920
		let state = defaultWindowState() as INewWindowState;
921 922
		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 已提交
923

924 925 926 927 928 929 930 931 932 933 934
		// 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 已提交
935 936 937 938 939 940 941
				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;
				}

942 943 944 945 946 947 948 949
				ensureNoOverlap = false;
			}
		}

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

950 951
		state.hasDefaultState = true; // flag as default state

952
		return state;
E
Erich Gamma 已提交
953 954
	}

J
Joao Moreno 已提交
955
	private ensureNoOverlap(state: ISingleWindowState): ISingleWindowState {
E
Erich Gamma 已提交
956 957 958 959
		if (WindowsManager.WINDOWS.length === 0) {
			return state;
		}

960 961
		const existingWindowBounds = WindowsManager.WINDOWS.map(win => win.getBounds());
		while (existingWindowBounds.some(b => b.x === state.x || b.y === state.y)) {
E
Erich Gamma 已提交
962 963 964 965 966 967 968
			state.x += 30;
			state.y += 30;
		}

		return state;
	}

B
Benjamin Pasero 已提交
969 970 971 972 973 974 975 976 977 978 979 980 981
	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 已提交
982
	public focusLastActive(cli: ParsedArgs, context: OpenContext): CodeWindow {
B
Benjamin Pasero 已提交
983
		const lastActive = this.getLastActiveWindow();
E
Erich Gamma 已提交
984
		if (lastActive) {
B
Benjamin Pasero 已提交
985
			lastActive.focus();
986 987

			return lastActive;
E
Erich Gamma 已提交
988 989
		}

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

B
Benjamin Pasero 已提交
994
	public getLastActiveWindow(): CodeWindow {
995
		return getLastActiveWindow(WindowsManager.WINDOWS);
E
Erich Gamma 已提交
996 997
	}

B
Benjamin Pasero 已提交
998
	public findWindow(workspacePath: string, filePath?: string, extensionDevelopmentPath?: string): CodeWindow {
E
Erich Gamma 已提交
999 1000 1001
		if (WindowsManager.WINDOWS.length) {

			// Sort the last active window to the front of the array of windows to test
B
Benjamin Pasero 已提交
1002 1003
			const windowsToTest = WindowsManager.WINDOWS.slice(0);
			const lastActiveWindow = this.getLastActiveWindow();
E
Erich Gamma 已提交
1004 1005 1006 1007 1008 1009
			if (lastActiveWindow) {
				windowsToTest.splice(windowsToTest.indexOf(lastActiveWindow), 1);
				windowsToTest.unshift(lastActiveWindow);
			}

			// Find it
1010
			const res = windowsToTest.filter(w => {
E
Erich Gamma 已提交
1011 1012

				// match on workspace
B
Benjamin Pasero 已提交
1013
				if (typeof w.openedWorkspacePath === 'string' && (isEqual(w.openedWorkspacePath, workspacePath, !isLinux /* ignorecase */))) {
E
Erich Gamma 已提交
1014 1015 1016 1017
					return true;
				}

				// match on file
B
Benjamin Pasero 已提交
1018
				if (typeof w.openedFilePath === 'string' && isEqual(w.openedFilePath, filePath, !isLinux /* ignorecase */)) {
E
Erich Gamma 已提交
1019 1020 1021 1022
					return true;
				}

				// match on file path
B
Benjamin Pasero 已提交
1023
				if (typeof w.openedWorkspacePath === 'string' && filePath && isEqualOrParent(filePath, w.openedWorkspacePath, !isLinux /* ignorecase */)) {
E
Erich Gamma 已提交
1024 1025 1026
					return true;
				}

1027
				// match on extension development path
B
Benjamin Pasero 已提交
1028
				if (typeof extensionDevelopmentPath === 'string' && isEqual(w.extensionDevelopmentPath, extensionDevelopmentPath, !isLinux /* ignorecase */)) {
1029 1030 1031
					return true;
				}

E
Erich Gamma 已提交
1032 1033 1034 1035 1036 1037 1038 1039 1040 1041 1042
				return false;
			});

			if (res && res.length) {
				return res[0];
			}
		}

		return null;
	}

1043 1044
	public openNewWindow(context: OpenContext): void {
		this.open({ context, cli: this.environmentService.args, forceNewWindow: true, forceEmpty: true });
E
Erich Gamma 已提交
1045 1046 1047 1048 1049 1050
	}

	public sendToFocused(channel: string, ...args: any[]): void {
		const focusedWindow = this.getFocusedWindow() || this.getLastActiveWindow();

		if (focusedWindow) {
1051
			focusedWindow.sendWhenReady(channel, ...args);
E
Erich Gamma 已提交
1052 1053 1054
		}
	}

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

1061
			w.sendWhenReady(channel, payload);
E
Erich Gamma 已提交
1062 1063 1064
		});
	}

B
Benjamin Pasero 已提交
1065
	public getFocusedWindow(): CodeWindow {
B
Benjamin Pasero 已提交
1066
		const win = BrowserWindow.getFocusedWindow();
E
Erich Gamma 已提交
1067 1068 1069 1070 1071 1072 1073
		if (win) {
			return this.getWindowById(win.id);
		}

		return null;
	}

B
Benjamin Pasero 已提交
1074
	public getWindowById(windowId: number): CodeWindow {
1075
		const res = WindowsManager.WINDOWS.filter(w => w.id === windowId);
E
Erich Gamma 已提交
1076 1077 1078 1079 1080 1081 1082
		if (res && res.length === 1) {
			return res[0];
		}

		return null;
	}

B
Benjamin Pasero 已提交
1083
	public getWindows(): CodeWindow[] {
E
Erich Gamma 已提交
1084 1085 1086 1087 1088 1089 1090
		return WindowsManager.WINDOWS;
	}

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

B
Benjamin Pasero 已提交
1091
	private onWindowError(codeWindow: CodeWindow, error: WindowError): void {
E
Erich Gamma 已提交
1092 1093 1094 1095
		console.error(error === WindowError.CRASHED ? '[VS Code]: render process crashed!' : '[VS Code]: detected unresponsive');

		// Unresponsive
		if (error === WindowError.UNRESPONSIVE) {
B
Benjamin Pasero 已提交
1096
			dialog.showMessageBox(codeWindow.win, {
B
Benjamin Pasero 已提交
1097
				title: product.nameLong,
E
Erich Gamma 已提交
1098
				type: 'warning',
B
Benjamin Pasero 已提交
1099
				buttons: [nls.localize('reopen', "Reopen"), nls.localize('wait', "Keep Waiting"), nls.localize('close', "Close")],
1100
				message: nls.localize('appStalled', "The window is no longer responding"),
B
Benjamin Pasero 已提交
1101
				detail: nls.localize('appStalledDetail', "You can reopen or close the window or keep waiting."),
E
Erich Gamma 已提交
1102
				noLink: true
1103
			}, result => {
B
Benjamin Pasero 已提交
1104
				if (!codeWindow.win) {
1105 1106 1107
					return; // Return early if the window has been going down already
				}

E
Erich Gamma 已提交
1108
				if (result === 0) {
B
Benjamin Pasero 已提交
1109
					codeWindow.reload();
1110
				} else if (result === 2) {
B
Benjamin Pasero 已提交
1111 1112
					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 已提交
1113 1114 1115 1116 1117 1118
				}
			});
		}

		// Crashed
		else {
B
Benjamin Pasero 已提交
1119
			dialog.showMessageBox(codeWindow.win, {
B
Benjamin Pasero 已提交
1120
				title: product.nameLong,
E
Erich Gamma 已提交
1121
				type: 'warning',
B
Benjamin Pasero 已提交
1122
				buttons: [nls.localize('reopen', "Reopen"), nls.localize('close', "Close")],
1123
				message: nls.localize('appCrashed', "The window has crashed"),
B
Benjamin Pasero 已提交
1124
				detail: nls.localize('appCrashedDetail', "We are sorry for the inconvenience! You can reopen the window to continue where you left off."),
E
Erich Gamma 已提交
1125
				noLink: true
1126
			}, result => {
B
Benjamin Pasero 已提交
1127
				if (!codeWindow.win) {
1128 1129 1130
					return; // Return early if the window has been going down already
				}

1131
				if (result === 0) {
B
Benjamin Pasero 已提交
1132
					codeWindow.reload();
1133
				} else if (result === 1) {
B
Benjamin Pasero 已提交
1134 1135
					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
1136
				}
E
Erich Gamma 已提交
1137 1138 1139 1140
			});
		}
	}

B
Benjamin Pasero 已提交
1141
	private onWindowClosed(win: CodeWindow): void {
E
Erich Gamma 已提交
1142 1143 1144 1145 1146

		// Tell window
		win.dispose();

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

		// Emit
1151
		this._onWindowClose.fire(win.id);
E
Erich Gamma 已提交
1152
	}
B
Benjamin Pasero 已提交
1153

B
renames  
Benjamin Pasero 已提交
1154
	public pickFileFolderAndOpen(forceNewWindow?: boolean, data?: ITelemetryData): void {
B
Benjamin Pasero 已提交
1155
		this.fileDialog.pickAndOpen({ pickFolders: true, pickFiles: true, forceNewWindow }, 'openFileFolder', data);
1156 1157
	}

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

B
renames  
Benjamin Pasero 已提交
1162
	public pickFolderAndOpen(forceNewWindow?: boolean, window?: CodeWindow, data?: ITelemetryData): void {
B
Benjamin Pasero 已提交
1163
		this.fileDialog.pickAndOpen({ pickFolders: true, forceNewWindow, window, title: nls.localize('openFolder', "Open Folder") }, 'openFolder', data);
B
Benjamin Pasero 已提交
1164 1165
	}

B
Benjamin Pasero 已提交
1166
	public pickFolder(options?: { buttonLabel: string; title: string; }): TPromise<string[]> {
1167
		return new TPromise((c, e) => {
1168
			this.fileDialog.getFileOrFolderPaths({ pickFolders: true, buttonLabel: options && options.buttonLabel }, folders => {
1169 1170 1171 1172 1173
				c(folders || []);
			});
		});
	}

B
Benjamin Pasero 已提交
1174 1175 1176 1177 1178 1179 1180 1181 1182 1183 1184 1185 1186 1187 1188
	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) */);
		}
1189
	}
B
Benjamin Pasero 已提交
1190 1191 1192
}

interface INativeOpenDialogOptions {
B
Benjamin Pasero 已提交
1193
	title?: string;
B
Benjamin Pasero 已提交
1194 1195 1196 1197 1198
	pickFolders?: boolean;
	pickFiles?: boolean;
	path?: string;
	forceNewWindow?: boolean;
	window?: CodeWindow;
1199
	buttonLabel?: string;
B
Benjamin Pasero 已提交
1200 1201 1202 1203 1204
}

class FileDialog {

	private static workingDirPickerStorageKey = 'pickerWorkingDir';
1205

B
Benjamin Pasero 已提交
1206 1207 1208 1209 1210 1211 1212 1213 1214
	constructor(
		private environmentService: IEnvironmentService,
		private telemetryService: ITelemetryService,
		private storageService: IStorageService,
		private windowsMainService: IWindowsMainService
	) {
	}

	public pickAndOpen(options: INativeOpenDialogOptions, eventName: string, data?: ITelemetryData): void {
1215 1216 1217
		this.getFileOrFolderPaths(options, (paths: string[]) => {
			const nOfPaths = paths ? paths.length : 0;
			if (nOfPaths) {
B
Benjamin Pasero 已提交
1218
				this.windowsMainService.open({ context: OpenContext.DIALOG, cli: this.environmentService.args, pathsToOpen: paths, forceNewWindow: options.forceNewWindow });
1219 1220 1221 1222 1223 1224 1225 1226 1227
			}
			this.telemetryService.publicLog(eventName, {
				...data,
				outcome: nOfPaths ? 'success' : 'canceled',
				nOfPaths
			});
		});
	}

1228
	public getFileOrFolderPaths(options: INativeOpenDialogOptions, clb: (paths: string[]) => void): void {
B
Benjamin Pasero 已提交
1229 1230
		const workingDir = options.path || this.storageService.getItem<string>(FileDialog.workingDirPickerStorageKey);
		const focussedWindow = options.window || this.windowsMainService.getFocusedWindow();
1231 1232 1233 1234 1235 1236 1237 1238 1239

		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 已提交
1240
			title: options && options.title ? options.title : void 0,
1241
			defaultPath: workingDir,
1242 1243
			properties: pickerProperties,
			buttonLabel: options && options.buttonLabel ? options.buttonLabel : void 0
1244 1245 1246 1247
		}, paths => {
			if (paths && paths.length > 0) {

				// Remember path in storage for next time
B
Benjamin Pasero 已提交
1248
				this.storageService.setItem(FileDialog.workingDirPickerStorageKey, path.dirname(paths[0]));
1249 1250 1251 1252 1253 1254 1255 1256

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