windows.ts 38.3 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 os from 'os';
B
Benjamin Pasero 已提交
10
import * as fs from 'original-fs';
J
Joao Moreno 已提交
11 12 13
import * as platform from 'vs/base/common/platform';
import * as nls from 'vs/nls';
import * as paths from 'vs/base/common/paths';
14
import * as types from 'vs/base/common/types';
J
Joao Moreno 已提交
15
import * as arrays from 'vs/base/common/arrays';
16
import { assign, mixin } from 'vs/base/common/objects';
J
Joao Moreno 已提交
17
import { EventEmitter } from 'events';
18
import { IEnvironmentService } from 'vs/platform/environment/common/environment';
J
Joao Moreno 已提交
19
import { IStorageService } from 'vs/code/electron-main/storage';
20
import { IPath, VSCodeWindow, ReadyState, IWindowConfiguration, IWindowState as ISingleWindowState, defaultWindowState, IWindowSettings } from 'vs/code/electron-main/window';
21
import { ipcMain as ipc, app, screen, BrowserWindow, dialog } from 'electron';
22
import { IPathWithLineAndColumn, parseLineAndColumnAware } from 'vs/code/electron-main/paths';
23
import { ILifecycleService } from 'vs/code/electron-main/lifecycle';
24
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
B
Benjamin Pasero 已提交
25
import { ILogService } from 'vs/code/electron-main/log';
S
Sandeep Somavarapu 已提交
26
import { IWindowEventService } from 'vs/code/common/windows';
J
Johannes Rieken 已提交
27
import { createDecorator, IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
S
Sandeep Somavarapu 已提交
28
import CommonEvent, { Emitter } from 'vs/base/common/event';
B
Benjamin Pasero 已提交
29
import product from 'vs/platform/product';
B
Benjamin Pasero 已提交
30
import { ParsedArgs } from 'vs/platform/environment/node/argv';
E
Erich Gamma 已提交
31 32 33 34 35 36 37 38 39 40 41 42 43

const EventTypes = {
	OPEN: 'open',
	CLOSE: 'close',
	READY: 'ready'
};

enum WindowError {
	UNRESPONSIVE,
	CRASHED
}

export interface IOpenConfiguration {
B
Benjamin Pasero 已提交
44
	cli: ParsedArgs;
45
	userEnv?: platform.IProcessEnvironment;
E
Erich Gamma 已提交
46
	pathsToOpen?: string[];
47
	preferNewWindow?: boolean;
E
Erich Gamma 已提交
48 49
	forceNewWindow?: boolean;
	forceEmpty?: boolean;
J
Joao Moreno 已提交
50
	windowToUse?: VSCodeWindow;
51
	diffMode?: boolean;
E
Erich Gamma 已提交
52 53 54 55
}

interface IWindowState {
	workspacePath?: string;
J
Joao Moreno 已提交
56
	uiState: ISingleWindowState;
E
Erich Gamma 已提交
57 58 59 60 61 62 63 64
}

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

65
export interface IRecentPathsList {
E
Erich Gamma 已提交
66 67 68 69
	folders: string[];
	files: string[];
}

70 71 72 73 74
interface ILogEntry {
	severity: string;
	arguments: any;
}

75 76 77
interface INativeOpenDialogOptions {
	pickFolders?: boolean;
	pickFiles?: boolean;
78 79
	path?: string;
	forceNewWindow?: boolean;
80 81
}

82 83 84 85 86 87
const ReopenFoldersSetting = {
	ALL: 'all',
	ONE: 'one',
	NONE: 'none'
};

J
Joao Moreno 已提交
88
export const IWindowsMainService = createDecorator<IWindowsMainService>('windowsMainService');
J
Joao Moreno 已提交
89

J
Joao Moreno 已提交
90
export interface IWindowsMainService {
91
	_serviceBrand: any;
J
Joao Moreno 已提交
92 93 94

	// TODO make proper events
	// events
J
Joao Moreno 已提交
95 96
	onOpen(clb: (path: IPath) => void): () => void;
	onReady(clb: (win: VSCodeWindow) => void): () => void;
J
Joao Moreno 已提交
97
	onClose(clb: (id: number) => void): () => void;
S
Sandeep Somavarapu 已提交
98 99
	onNewWindowOpen: CommonEvent<number>;
	onWindowFocus: CommonEvent<number>;
J
Joao Moreno 已提交
100 101

	// methods
102
	ready(initialUserEnv: platform.IProcessEnvironment): void;
B
Benjamin Pasero 已提交
103
	reload(win: VSCodeWindow, cli?: ParsedArgs): void;
J
Joao Moreno 已提交
104
	open(openConfig: IOpenConfiguration): VSCodeWindow[];
J
Joao Moreno 已提交
105 106
	openPluginDevelopmentHostWindow(openConfig: IOpenConfiguration): void;
	openFileFolderPicker(forceNewWindow?: boolean): void;
107
	openFilePicker(forceNewWindow?: boolean, path?: string): void;
J
Joao Moreno 已提交
108
	openFolderPicker(forceNewWindow?: boolean): void;
109
	openAccessibilityOptions(): void;
B
Benjamin Pasero 已提交
110
	focusLastActive(cli: ParsedArgs): VSCodeWindow;
J
Joao Moreno 已提交
111 112
	getLastActiveWindow(): VSCodeWindow;
	findWindow(workspacePath: string, filePath?: string, extensionDevelopmentPath?: string): VSCodeWindow;
J
Joao Moreno 已提交
113 114 115
	openNewWindow(): void;
	sendToFocused(channel: string, ...args: any[]): void;
	sendToAll(channel: string, payload: any, windowIdsToIgnore?: number[]): void;
J
Joao Moreno 已提交
116 117 118
	getFocusedWindow(): VSCodeWindow;
	getWindowById(windowId: number): VSCodeWindow;
	getWindows(): VSCodeWindow[];
J
Joao Moreno 已提交
119
	getWindowCount(): number;
J
Joao Moreno 已提交
120
	getRecentPathsList(workspacePath?: string, filesToOpen?: IPath[]): IRecentPathsList;
121
	removeFromRecentPathsList(path: string);
122
	removeFromRecentPathsList(path: string[]);
123
	clearRecentPathsList(): void;
J
Joao Moreno 已提交
124
	toggleMenuBar(windowId: number): void;
J
Joao Moreno 已提交
125 126
}

J
Joao Moreno 已提交
127
export class WindowsManager implements IWindowsMainService, IWindowEventService {
J
Joao Moreno 已提交
128

129
	_serviceBrand: any;
E
Erich Gamma 已提交
130

131
	private static MAX_TOTAL_RECENT_ENTRIES = 100;
132

133
	private static recentPathsListStorageKey = 'openedPathsList';
134
	private static workingDirPickerStorageKey = 'pickerWorkingDir';
E
Erich Gamma 已提交
135 136
	private static windowsStateStorageKey = 'windowsState';

J
Joao Moreno 已提交
137
	private static WINDOWS: VSCodeWindow[] = [];
E
Erich Gamma 已提交
138

J
Joao Moreno 已提交
139
	private eventEmitter = new EventEmitter();
140
	private initialUserEnv: platform.IProcessEnvironment;
E
Erich Gamma 已提交
141 142
	private windowsState: IWindowsState;

S
Sandeep Somavarapu 已提交
143 144 145 146 147 148
	private _onFocus = new Emitter<number>();
	onWindowFocus: CommonEvent<number> = this._onFocus.event;

	private _onNewWindow = new Emitter<number>();
	onNewWindowOpen: CommonEvent<number> = this._onNewWindow.event;

J
Joao Moreno 已提交
149 150 151
	constructor(
		@IInstantiationService private instantiationService: IInstantiationService,
		@ILogService private logService: ILogService,
J
Joao Moreno 已提交
152
		@IStorageService private storageService: IStorageService,
153
		@IEnvironmentService private environmentService: IEnvironmentService,
J
Joao Moreno 已提交
154
		@ILifecycleService private lifecycleService: ILifecycleService,
D
Daniel Imms 已提交
155
		@IConfigurationService private configurationService: IConfigurationService
156
	) { }
J
Joao Moreno 已提交
157

J
Joao Moreno 已提交
158
	onOpen(clb: (path: IPath) => void): () => void {
J
Joao Moreno 已提交
159 160 161 162 163
		this.eventEmitter.addListener(EventTypes.OPEN, clb);

		return () => this.eventEmitter.removeListener(EventTypes.OPEN, clb);
	}

J
Joao Moreno 已提交
164
	onReady(clb: (win: VSCodeWindow) => void): () => void {
J
Joao Moreno 已提交
165 166 167 168 169 170 171 172 173 174 175
		this.eventEmitter.addListener(EventTypes.READY, clb);

		return () => this.eventEmitter.removeListener(EventTypes.READY, clb);
	}

	onClose(clb: (id: number) => void): () => void {
		this.eventEmitter.addListener(EventTypes.CLOSE, clb);

		return () => this.eventEmitter.removeListener(EventTypes.CLOSE, clb);
	}

176
	public ready(initialUserEnv: platform.IProcessEnvironment): void {
E
Erich Gamma 已提交
177 178
		this.registerListeners();

179
		this.initialUserEnv = initialUserEnv;
J
Joao Moreno 已提交
180
		this.windowsState = this.storageService.getItem<IWindowsState>(WindowsManager.windowsStateStorageKey) || { openedFolders: [] };
E
Erich Gamma 已提交
181 182 183
	}

	private registerListeners(): void {
184
		app.on('activate', (event: Event, hasVisibleWindows: boolean) => {
J
Joao Moreno 已提交
185
			this.logService.log('App#activate');
E
Erich Gamma 已提交
186

G
Giorgos Retsinas 已提交
187
			// Mac only event: open new window when we get activated
E
Erich Gamma 已提交
188
			if (!hasVisibleWindows) {
G
Giorgos Retsinas 已提交
189
				this.openNewWindow();
E
Erich Gamma 已提交
190 191 192 193 194 195
			}
		});

		let macOpenFiles: string[] = [];
		let runningTimeout: number = null;
		app.on('open-file', (event: Event, path: string) => {
J
Joao Moreno 已提交
196
			this.logService.log('App#open-file: ', path);
E
Erich Gamma 已提交
197 198 199 200 201 202 203 204 205 206 207 208 209
			event.preventDefault();

			// Keep in array because more might come!
			macOpenFiles.push(path);

			// Clear previous handler if any
			if (runningTimeout !== null) {
				clearTimeout(runningTimeout);
				runningTimeout = null;
			}

			// Handle paths delayed in case more are coming!
			runningTimeout = setTimeout(() => {
210
				this.open({ cli: this.environmentService.args, pathsToOpen: macOpenFiles, preferNewWindow: true /* dropping on the dock prefers to open in a new window */ });
E
Erich Gamma 已提交
211 212 213 214 215
				macOpenFiles = [];
				runningTimeout = null;
			}, 100);
		});

B
Benjamin Pasero 已提交
216
		ipc.on('vscode:workbenchLoaded', (event, windowId: number) => {
J
Joao Moreno 已提交
217
			this.logService.log('IPC#vscode-workbenchLoaded');
E
Erich Gamma 已提交
218

B
Benjamin Pasero 已提交
219
			const win = this.getWindowById(windowId);
E
Erich Gamma 已提交
220 221 222 223
			if (win) {
				win.setReady();

				// Event
J
Joao Moreno 已提交
224
				this.eventEmitter.emit(EventTypes.READY, win);
E
Erich Gamma 已提交
225 226 227
			}
		});

B
Benjamin Pasero 已提交
228
		ipc.on('vscode:broadcast', (event, windowId: number, target: string, broadcast: { channel: string; payload: any; }) => {
229
			if (broadcast.channel && !types.isUndefinedOrNull(broadcast.payload)) {
J
Joao Moreno 已提交
230
				this.logService.log('IPC#vscode:broadcast', target, broadcast.channel, broadcast.payload);
B
Benjamin Pasero 已提交
231

232 233 234 235
				// Handle specific events on main side
				this.onBroadcast(broadcast.channel, broadcast.payload);

				// Send to windows
236
				if (target) {
B
Benjamin Pasero 已提交
237
					const otherWindowsWithTarget = WindowsManager.WINDOWS.filter(w => w.id !== windowId && typeof w.openedWorkspacePath === 'string');
238 239 240 241 242
					const directTargetMatch = otherWindowsWithTarget.filter(w => this.isPathEqual(target, w.openedWorkspacePath));
					const parentTargetMatch = otherWindowsWithTarget.filter(w => paths.isEqualOrParent(target, w.openedWorkspacePath));

					const targetWindow = directTargetMatch.length ? directTargetMatch[0] : parentTargetMatch[0]; // prefer direct match over parent match
					if (targetWindow) {
243 244 245 246 247
						targetWindow.send('vscode:broadcast', broadcast);
					}
				} else {
					this.sendToAll('vscode:broadcast', broadcast, [windowId]);
				}
E
Erich Gamma 已提交
248
			}
249 250
		});

J
Joao Moreno 已提交
251
		this.lifecycleService.onBeforeQuit(() => {
E
Erich Gamma 已提交
252 253 254 255 256 257 258 259

			// 0-1 window open: Do not keep the list but just rely on the active window to be stored
			if (WindowsManager.WINDOWS.length < 2) {
				this.windowsState.openedFolders = [];
				return;
			}

			// 2-N windows open: Keep a list of windows that are opened on a specific folder to restore it in the next session as needed
J
Joao Moreno 已提交
260
			this.windowsState.openedFolders = WindowsManager.WINDOWS.filter(w => w.readyState === ReadyState.READY && !!w.openedWorkspacePath && !w.isPluginDevelopmentHost).map(w => {
E
Erich Gamma 已提交
261 262 263
				return <IWindowState>{
					workspacePath: w.openedWorkspacePath,
					uiState: w.serializeWindowState()
B
Benjamin Pasero 已提交
264
				};
E
Erich Gamma 已提交
265 266 267 268
			});
		});

		app.on('will-quit', () => {
J
Joao Moreno 已提交
269
			this.storageService.setItem(WindowsManager.windowsStateStorageKey, this.windowsState);
E
Erich Gamma 已提交
270
		});
271 272

		let loggedStartupTimes = false;
J
Joao Moreno 已提交
273
		this.onReady(window => {
274 275 276 277 278 279
			if (loggedStartupTimes) {
				return; // only for the first window
			}

			loggedStartupTimes = true;

B
Benjamin Pasero 已提交
280
			this.logStartupTimes(window);
281
		});
E
Erich Gamma 已提交
282 283
	}

B
Benjamin Pasero 已提交
284 285 286 287 288 289 290 291 292 293 294 295 296 297 298
	private logStartupTimes(window: VSCodeWindow): void {
		let totalmem: number;
		let cpus: { count: number; speed: number; model: string; };

		try {
			totalmem = os.totalmem();

			const rawCpus = os.cpus();
			if (rawCpus && rawCpus.length > 0) {
				cpus = { count: rawCpus.length, speed: rawCpus[0].speed, model: rawCpus[0].model };
			}
		} catch (error) {
			this.logService.log(error); // be on the safe side with these hardware method calls
		}

B
Benjamin Pasero 已提交
299
		window.send('vscode:telemetry', { eventName: 'startupTime', data: { ellapsed: Date.now() - global.vscodeStart, totalmem, cpus } });
B
Benjamin Pasero 已提交
300 301
	}

302 303 304
	private onBroadcast(event: string, payload: any): void {

		// Theme changes
305 306
		if (event === 'vscode:changeColorTheme' && typeof payload === 'string') {
			this.storageService.setItem(VSCodeWindow.colorThemeStorageKey, payload);
307 308 309
		}
	}

B
Benjamin Pasero 已提交
310
	public reload(win: VSCodeWindow, cli?: ParsedArgs): void {
E
Erich Gamma 已提交
311 312

		// Only reload when the window has not vetoed this
313
		this.lifecycleService.unload(win).done(veto => {
E
Erich Gamma 已提交
314 315 316 317 318 319
			if (!veto) {
				win.reload(cli);
			}
		});
	}

J
Joao Moreno 已提交
320 321
	public open(openConfig: IOpenConfiguration): VSCodeWindow[] {
		let iPathsToOpen: IPath[];
B
Benjamin Pasero 已提交
322
		const usedWindows: VSCodeWindow[] = [];
E
Erich Gamma 已提交
323 324 325

		// Find paths from provided paths if any
		if (openConfig.pathsToOpen && openConfig.pathsToOpen.length > 0) {
326
			iPathsToOpen = openConfig.pathsToOpen.map(pathToOpen => {
B
Benjamin Pasero 已提交
327
				const iPath = this.toIPath(pathToOpen, false, openConfig.cli && openConfig.cli.goto);
E
Erich Gamma 已提交
328 329 330

				// Warn if the requested path to open does not exist
				if (!iPath) {
B
Benjamin Pasero 已提交
331
					const options: Electron.ShowMessageBoxOptions = {
B
Benjamin Pasero 已提交
332
						title: product.nameLong,
E
Erich Gamma 已提交
333 334 335 336 337 338 339
						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
					};

B
Benjamin Pasero 已提交
340
					const activeWindow = BrowserWindow.getFocusedWindow();
E
Erich Gamma 已提交
341
					if (activeWindow) {
342
						dialog.showMessageBox(activeWindow, options);
E
Erich Gamma 已提交
343
					} else {
344
						dialog.showMessageBox(options);
E
Erich Gamma 已提交
345 346 347 348 349 350 351 352 353 354
					}
				}

				return iPath;
			});

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

			if (iPathsToOpen.length === 0) {
355
				return null; // indicate to outside that open failed
E
Erich Gamma 已提交
356 357 358 359 360 361 362 363 364 365
			}
		}

		// Check for force empty
		else if (openConfig.forceEmpty) {
			iPathsToOpen = [Object.create(null)];
		}

		// Otherwise infer from command line arguments
		else {
B
Benjamin Pasero 已提交
366
			const ignoreFileNotFound = openConfig.cli._.length > 0; // we assume the user wants to create this file from command line
E
Erich Gamma 已提交
367 368 369
			iPathsToOpen = this.cliToPaths(openConfig.cli, ignoreFileNotFound);
		}

J
Joao Moreno 已提交
370 371
		let filesToOpen: IPath[] = [];
		let filesToDiff: IPath[] = [];
372 373 374
		let foldersToOpen = iPathsToOpen.filter(iPath => iPath.workspacePath && !iPath.filePath);
		let emptyToOpen = iPathsToOpen.filter(iPath => !iPath.workspacePath && !iPath.filePath);
		let filesToCreate = iPathsToOpen.filter(iPath => !!iPath.filePath && iPath.createFilePath);
375 376

		// Diff mode needs special care
377
		const candidates = iPathsToOpen.filter(iPath => !!iPath.filePath && !iPath.createFilePath);
378 379 380 381 382 383 384
		if (openConfig.diffMode) {
			if (candidates.length === 2) {
				filesToDiff = candidates;
			} else {
				emptyToOpen = [Object.create(null)]; // improper use of diffMode, open empty
			}

385
			foldersToOpen = []; // diff is always in empty workspace
B
Benjamin Pasero 已提交
386
			filesToCreate = []; // diff ignores other files that do not exist
387 388 389 390
		} else {
			filesToOpen = candidates;
		}

J
Joao Moreno 已提交
391
		let configuration: IWindowConfiguration;
E
Erich Gamma 已提交
392

393
		// Handle files to open/diff or to create when we dont open a folder
394
		if (!foldersToOpen.length && (filesToOpen.length > 0 || filesToCreate.length > 0 || filesToDiff.length > 0)) {
E
Erich Gamma 已提交
395

B
Benjamin Pasero 已提交
396
			// const the user settings override how files are open in a new window or same window unless we are forced
397 398 399 400 401 402
			let openFilesInNewWindow: boolean;
			if (openConfig.forceNewWindow) {
				openFilesInNewWindow = true;
			} else {
				openFilesInNewWindow = openConfig.preferNewWindow;
				if (openFilesInNewWindow && !openConfig.cli.extensionDevelopmentPath) { // can be overriden via settings (not for PDE though!)
403 404 405 406
					const windowConfig = this.configurationService.getConfiguration<IWindowSettings>('window');
					if (windowConfig && !windowConfig.openFilesInNewWindow) {
						openFilesInNewWindow = false; // do not open in new window if user configured this explicitly
					}
407
				}
E
Erich Gamma 已提交
408 409 410
			}

			// Open Files in last instance if any and flag tells us so
B
Benjamin Pasero 已提交
411
			const lastActiveWindow = this.getLastActiveWindow();
E
Erich Gamma 已提交
412
			if (!openFilesInNewWindow && lastActiveWindow) {
B
Benjamin Pasero 已提交
413
				lastActiveWindow.focus();
414
				lastActiveWindow.ready().then(readyWindow => {
415
					readyWindow.send('vscode:openFiles', { filesToOpen, filesToCreate, filesToDiff });
E
Erich Gamma 已提交
416
				});
417 418

				usedWindows.push(lastActiveWindow);
E
Erich Gamma 已提交
419 420 421 422
			}

			// Otherwise open instance with files
			else {
423
				configuration = this.toConfiguration(this.getWindowUserEnv(openConfig), openConfig.cli, null, filesToOpen, filesToCreate, filesToDiff);
B
Benjamin Pasero 已提交
424
				const browserWindow = this.openInBrowserWindow(configuration, true /* new window */);
425
				usedWindows.push(browserWindow);
E
Erich Gamma 已提交
426 427 428 429 430 431

				openConfig.forceNewWindow = true; // any other folders to open must open in new window then
			}
		}

		// Handle folders to open
432
		let openInNewWindow = openConfig.preferNewWindow || openConfig.forceNewWindow;
E
Erich Gamma 已提交
433 434 435
		if (foldersToOpen.length > 0) {

			// Check for existing instances
436
			const windowsOnWorkspacePath = arrays.coalesce(foldersToOpen.map(iPath => this.findWindow(iPath.workspacePath)));
E
Erich Gamma 已提交
437
			if (windowsOnWorkspacePath.length > 0) {
B
Benjamin Pasero 已提交
438
				const browserWindow = windowsOnWorkspacePath[0];
439
				browserWindow.focus(); // just focus one of them
440
				browserWindow.ready().then(readyWindow => {
441
					readyWindow.send('vscode:openFiles', { filesToOpen, filesToCreate, filesToDiff });
E
Erich Gamma 已提交
442 443
				});

444 445
				usedWindows.push(browserWindow);

E
Erich Gamma 已提交
446 447 448
				// Reset these because we handled them
				filesToOpen = [];
				filesToCreate = [];
449
				filesToDiff = [];
E
Erich Gamma 已提交
450

451
				openInNewWindow = true; // any other folders to open must open in new window then
E
Erich Gamma 已提交
452 453 454
			}

			// Open remaining ones
455 456
			foldersToOpen.forEach(folderToOpen => {
				if (windowsOnWorkspacePath.some(win => this.isPathEqual(win.openedWorkspacePath, folderToOpen.workspacePath))) {
E
Erich Gamma 已提交
457 458 459
					return; // ignore folders that are already open
				}

460
				configuration = this.toConfiguration(this.getWindowUserEnv(openConfig), openConfig.cli, folderToOpen.workspacePath, filesToOpen, filesToCreate, filesToDiff);
B
Benjamin Pasero 已提交
461
				const browserWindow = this.openInBrowserWindow(configuration, openInNewWindow, openInNewWindow ? void 0 : openConfig.windowToUse);
462
				usedWindows.push(browserWindow);
E
Erich Gamma 已提交
463 464 465 466

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

469
				openInNewWindow = true; // any other folders to open must open in new window then
E
Erich Gamma 已提交
470 471 472 473 474 475
			});
		}

		// Handle empty
		if (emptyToOpen.length > 0) {
			emptyToOpen.forEach(() => {
B
Benjamin Pasero 已提交
476 477
				const configuration = this.toConfiguration(this.getWindowUserEnv(openConfig), openConfig.cli);
				const browserWindow = this.openInBrowserWindow(configuration, openInNewWindow, openInNewWindow ? void 0 : openConfig.windowToUse);
478
				usedWindows.push(browserWindow);
E
Erich Gamma 已提交
479

480
				openInNewWindow = true; // any other folders to open must open in new window then
E
Erich Gamma 已提交
481 482 483
			});
		}

484
		// Remember in recent document list (unless this opens for extension development)
485
		// Also do not add paths when files are opened for diffing, only if opened individually
486
		if (!usedWindows.some(w => w.isPluginDevelopmentHost) && !openConfig.cli.diff) {
487 488 489 490 491 492 493
			iPathsToOpen.forEach(iPath => {
				if (iPath.filePath || iPath.workspacePath) {
					app.addRecentDocument(iPath.filePath || iPath.workspacePath);
					this.addToRecentPathsList(iPath.filePath || iPath.workspacePath, !!iPath.filePath);
				}
			});
		}
E
Erich Gamma 已提交
494 495

		// Emit events
496
		iPathsToOpen.forEach(iPath => this.eventEmitter.emit(EventTypes.OPEN, iPath));
E
Erich Gamma 已提交
497

498
		return arrays.distinct(usedWindows);
E
Erich Gamma 已提交
499 500
	}

501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521
	private addToRecentPathsList(path?: string, isFile?: boolean): void {
		if (!path) {
			return;
		}

		const mru = this.getRecentPathsList();
		if (isFile) {
			mru.files.unshift(path);
			mru.files = arrays.distinct(mru.files, (f) => platform.isLinux ? f : f.toLowerCase());
		} else {
			mru.folders.unshift(path);
			mru.folders = arrays.distinct(mru.folders, (f) => platform.isLinux ? f : f.toLowerCase());
		}

		// Make sure its bounded
		mru.folders = mru.folders.slice(0, WindowsManager.MAX_TOTAL_RECENT_ENTRIES);
		mru.files = mru.files.slice(0, WindowsManager.MAX_TOTAL_RECENT_ENTRIES);

		this.storageService.setItem(WindowsManager.recentPathsListStorageKey, mru);
	}

522 523 524 525 526 527 528 529 530 531
	public removeFromRecentPathsList(path: string): void;
	public removeFromRecentPathsList(paths: string[]): void;
	public removeFromRecentPathsList(arg1: any): void {
		let paths: string[];
		if (Array.isArray(arg1)) {
			paths = arg1;
		} else {
			paths = [arg1];
		}

532
		const mru = this.getRecentPathsList();
533
		let update = false;
534

535 536 537 538 539 540
		paths.forEach(path => {
			let index = mru.files.indexOf(path);
			if (index >= 0) {
				mru.files.splice(index, 1);
				update = true;
			}
541

542 543 544 545 546 547
			index = mru.folders.indexOf(path);
			if (index >= 0) {
				mru.folders.splice(index, 1);
				update = true;
			}
		});
548

549 550 551
		if (update) {
			this.storageService.setItem(WindowsManager.recentPathsListStorageKey, mru);
		}
552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589
	}

	public clearRecentPathsList(): void {
		this.storageService.setItem(WindowsManager.recentPathsListStorageKey, { folders: [], files: [] });
		app.clearRecentDocuments();
	}

	public getRecentPathsList(workspacePath?: string, filesToOpen?: IPath[]): IRecentPathsList {
		let files: string[];
		let folders: string[];

		// Get from storage
		const storedRecents = this.storageService.getItem<IRecentPathsList>(WindowsManager.recentPathsListStorageKey);
		if (storedRecents) {
			files = storedRecents.files || [];
			folders = storedRecents.folders || [];
		} else {
			files = [];
			folders = [];
		}

		// Add currently files to open to the beginning if any
		if (filesToOpen) {
			files.unshift(...filesToOpen.map(f => f.filePath));
		}

		// Add current workspace path to beginning if set
		if (workspacePath) {
			folders.unshift(workspacePath);
		}

		// Clear those dupes
		files = arrays.distinct(files);
		folders = arrays.distinct(folders);

		return { files, folders };
	}

590
	private getWindowUserEnv(openConfig: IOpenConfiguration): platform.IProcessEnvironment {
591 592 593
		return assign({}, this.initialUserEnv, openConfig.userEnv || {});
	}

E
Erich Gamma 已提交
594 595 596 597 598
	public openPluginDevelopmentHostWindow(openConfig: IOpenConfiguration): void {

		// Reload an existing plugin development host window on the same path
		// We currently do not allow more than one extension development window
		// on the same plugin path.
599
		let res = WindowsManager.WINDOWS.filter(w => w.config && this.isPathEqual(w.config.extensionDevelopmentPath, openConfig.cli.extensionDevelopmentPath));
E
Erich Gamma 已提交
600 601
		if (res && res.length === 1) {
			this.reload(res[0], openConfig.cli);
B
Benjamin Pasero 已提交
602
			res[0].focus(); // make sure it gets focus and is restored
E
Erich Gamma 已提交
603 604 605 606

			return;
		}

607
		// Fill in previously opened workspace unless an explicit path is provided and we are not unit testing
B
Benjamin Pasero 已提交
608
		if (openConfig.cli._.length === 0 && !openConfig.cli.extensionTestsPath) {
B
Benjamin Pasero 已提交
609
			const workspaceToOpen = this.windowsState.lastPluginDevelopmentHostWindow && this.windowsState.lastPluginDevelopmentHostWindow.workspacePath;
E
Erich Gamma 已提交
610
			if (workspaceToOpen) {
B
Benjamin Pasero 已提交
611
				openConfig.cli._ = [workspaceToOpen];
E
Erich Gamma 已提交
612 613 614 615
			}
		}

		// Make sure we are not asked to open a path that is already opened
B
Benjamin Pasero 已提交
616 617
		if (openConfig.cli._.length > 0) {
			res = WindowsManager.WINDOWS.filter(w => w.openedWorkspacePath && openConfig.cli._.indexOf(w.openedWorkspacePath) >= 0);
E
Erich Gamma 已提交
618
			if (res.length) {
B
Benjamin Pasero 已提交
619
				openConfig.cli._ = [];
E
Erich Gamma 已提交
620 621 622 623
			}
		}

		// Open it
B
Benjamin Pasero 已提交
624
		this.open({ cli: openConfig.cli, forceNewWindow: true, forceEmpty: openConfig.cli._.length === 0 });
E
Erich Gamma 已提交
625 626
	}

B
Benjamin Pasero 已提交
627
	private toConfiguration(userEnv: platform.IProcessEnvironment, cli: ParsedArgs, workspacePath?: string, filesToOpen?: IPath[], filesToCreate?: IPath[], filesToDiff?: IPath[]): IWindowConfiguration {
B
Benjamin Pasero 已提交
628
		const configuration: IWindowConfiguration = mixin({}, cli); // inherit all properties from CLI
629
		configuration.appRoot = this.environmentService.appRoot;
B
Benjamin Pasero 已提交
630 631
		configuration.execPath = process.execPath;
		configuration.userEnv = userEnv;
E
Erich Gamma 已提交
632 633 634
		configuration.workspacePath = workspacePath;
		configuration.filesToOpen = filesToOpen;
		configuration.filesToCreate = filesToCreate;
635
		configuration.filesToDiff = filesToDiff;
E
Erich Gamma 已提交
636 637 638 639

		return configuration;
	}

J
Joao Moreno 已提交
640
	private toIPath(anyPath: string, ignoreFileNotFound?: boolean, gotoLineMode?: boolean): IPath {
E
Erich Gamma 已提交
641 642 643 644
		if (!anyPath) {
			return null;
		}

645
		let parsedPath: IPathWithLineAndColumn;
E
Erich Gamma 已提交
646
		if (gotoLineMode) {
J
Joao Moreno 已提交
647
			parsedPath = parseLineAndColumnAware(anyPath);
E
Erich Gamma 已提交
648 649 650
			anyPath = parsedPath.path;
		}

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

E
Erich Gamma 已提交
666 667 668 669 670 671 672 673
			if (ignoreFileNotFound) {
				return { filePath: candidate, createFilePath: true }; // assume this is a file that does not yet exist
			}
		}

		return null;
	}

B
Benjamin Pasero 已提交
674
	private cliToPaths(cli: ParsedArgs, ignoreFileNotFound?: boolean): IPath[] {
E
Erich Gamma 已提交
675 676 677

		// Check for pass in candidate or last opened path
		let candidates: string[] = [];
B
Benjamin Pasero 已提交
678 679
		if (cli._.length > 0) {
			candidates = cli._;
E
Erich Gamma 已提交
680 681 682 683
		}

		// No path argument, check settings for what to do now
		else {
684 685 686 687
			let reopenFolders: string;
			if (this.lifecycleService.wasUpdated) {
				reopenFolders = ReopenFoldersSetting.ALL; // always reopen all folders when an update was applied
			} else {
688 689
				const windowConfig = this.configurationService.getConfiguration<IWindowSettings>('window');
				reopenFolders = (windowConfig && windowConfig.reopenFolders) || ReopenFoldersSetting.ONE;
690 691
			}

B
Benjamin Pasero 已提交
692
			const lastActiveFolder = this.windowsState.lastActiveWindow && this.windowsState.lastActiveWindow.workspacePath;
E
Erich Gamma 已提交
693 694

			// Restore all
695
			if (reopenFolders === ReopenFoldersSetting.ALL) {
B
Benjamin Pasero 已提交
696
				const lastOpenedFolders = this.windowsState.openedFolders.map(o => o.workspacePath);
E
Erich Gamma 已提交
697 698 699 700 701 702 703 704 705 706 707

				// If we have a last active folder, move it to the end
				if (lastActiveFolder) {
					lastOpenedFolders.splice(lastOpenedFolders.indexOf(lastActiveFolder), 1);
					lastOpenedFolders.push(lastActiveFolder);
				}

				candidates.push(...lastOpenedFolders);
			}

			// Restore last active
708
			else if (lastActiveFolder && (reopenFolders === ReopenFoldersSetting.ONE || reopenFolders !== ReopenFoldersSetting.NONE)) {
E
Erich Gamma 已提交
709 710 711 712
				candidates.push(lastActiveFolder);
			}
		}

713
		const iPaths = candidates.map(candidate => this.toIPath(candidate, ignoreFileNotFound, cli.goto)).filter(path => !!path);
E
Erich Gamma 已提交
714 715 716 717 718 719 720 721
		if (iPaths.length > 0) {
			return iPaths;
		}

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

J
Joao Moreno 已提交
722 723
	private openInBrowserWindow(configuration: IWindowConfiguration, forceNewWindow?: boolean, windowToUse?: VSCodeWindow): VSCodeWindow {
		let vscodeWindow: VSCodeWindow;
E
Erich Gamma 已提交
724 725 726 727 728

		if (!forceNewWindow) {
			vscodeWindow = windowToUse || this.getLastActiveWindow();

			if (vscodeWindow) {
B
Benjamin Pasero 已提交
729
				vscodeWindow.focus();
E
Erich Gamma 已提交
730 731 732 733 734
			}
		}

		// New window
		if (!vscodeWindow) {
735 736
			const windowConfig = this.configurationService.getConfiguration<IWindowSettings>('window');

J
Joao Moreno 已提交
737
			vscodeWindow = this.instantiationService.createInstance(VSCodeWindow, {
738
				state: this.getNewWindowState(configuration),
739
				extensionDevelopmentPath: configuration.extensionDevelopmentPath,
B
Benjamin Pasero 已提交
740 741
				allowFullscreen: this.lifecycleService.wasUpdated || (windowConfig && windowConfig.restoreFullscreen),
				titleBarStyle: windowConfig ? windowConfig.titleBarStyle : void 0
742 743
			});

E
Erich Gamma 已提交
744 745 746
			WindowsManager.WINDOWS.push(vscodeWindow);

			// Window Events
747 748
			vscodeWindow.win.webContents.removeAllListeners('devtools-reload-page'); // remove built in listener so we can handle this on our own
			vscodeWindow.win.webContents.on('devtools-reload-page', () => this.reload(vscodeWindow));
749 750
			vscodeWindow.win.webContents.on('crashed', () => this.onWindowError(vscodeWindow, WindowError.CRASHED));
			vscodeWindow.win.on('unresponsive', () => this.onWindowError(vscodeWindow, WindowError.UNRESPONSIVE));
E
Erich Gamma 已提交
751 752
			vscodeWindow.win.on('close', () => this.onBeforeWindowClose(vscodeWindow));
			vscodeWindow.win.on('closed', () => this.onWindowClosed(vscodeWindow));
S
Sandeep Somavarapu 已提交
753
			vscodeWindow.win.on('focus', () => this._onFocus.fire(vscodeWindow.id));
E
Erich Gamma 已提交
754

S
Sandeep Somavarapu 已提交
755
			this._onNewWindow.fire(vscodeWindow.id);
E
Erich Gamma 已提交
756
			// Lifecycle
J
Joao Moreno 已提交
757
			this.lifecycleService.registerWindow(vscodeWindow);
E
Erich Gamma 已提交
758 759 760 761 762 763 764
		}

		// Existing window
		else {

			// Some configuration things get inherited if the window is being reused and we are
			// in plugin development host mode. These options are all development related.
B
Benjamin Pasero 已提交
765
			const currentWindowConfig = vscodeWindow.config;
A
Alex Dima 已提交
766 767
			if (!configuration.extensionDevelopmentPath && currentWindowConfig && !!currentWindowConfig.extensionDevelopmentPath) {
				configuration.extensionDevelopmentPath = currentWindowConfig.extensionDevelopmentPath;
B
Benjamin Pasero 已提交
768
				configuration.verbose = currentWindowConfig.verbose;
769
				configuration.debugBrkPluginHost = currentWindowConfig.debugBrkPluginHost;
770
				configuration.debugPluginHost = currentWindowConfig.debugPluginHost;
771
				configuration['extensions-dir'] = currentWindowConfig['extensions-dir'];
E
Erich Gamma 已提交
772 773 774 775
			}
		}

		// Only load when the window has not vetoed this
776
		this.lifecycleService.unload(vscodeWindow).done(veto => {
E
Erich Gamma 已提交
777 778 779 780 781 782
			if (!veto) {

				// Load it
				vscodeWindow.load(configuration);
			}
		});
783 784

		return vscodeWindow;
E
Erich Gamma 已提交
785 786
	}

J
Joao Moreno 已提交
787
	private getNewWindowState(configuration: IWindowConfiguration): ISingleWindowState {
E
Erich Gamma 已提交
788 789

		// plugin development host Window - load from stored settings if any
A
Alex Dima 已提交
790
		if (!!configuration.extensionDevelopmentPath && this.windowsState.lastPluginDevelopmentHostWindow) {
E
Erich Gamma 已提交
791 792 793 794 795
			return this.windowsState.lastPluginDevelopmentHostWindow.uiState;
		}

		// Known Folder - load from stored settings if any
		if (configuration.workspacePath) {
B
Benjamin Pasero 已提交
796
			const stateForWorkspace = this.windowsState.openedFolders.filter(o => this.isPathEqual(o.workspacePath, configuration.workspacePath)).map(o => o.uiState);
E
Erich Gamma 已提交
797 798 799 800 801 802
			if (stateForWorkspace.length) {
				return stateForWorkspace[0];
			}
		}

		// First Window
B
Benjamin Pasero 已提交
803
		const lastActive = this.getLastActiveWindow();
E
Erich Gamma 已提交
804 805 806 807 808 809 810 811 812
		if (!lastActive && this.windowsState.lastActiveWindow) {
			return this.windowsState.lastActiveWindow.uiState;
		}

		//
		// 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
813
		let displayToUse: Electron.Display;
B
Benjamin Pasero 已提交
814
		const displays = screen.getAllDisplays();
E
Erich Gamma 已提交
815 816 817 818 819 820 821 822 823 824 825

		// 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
			if (platform.isMacintosh) {
B
Benjamin Pasero 已提交
826
				const cursorPoint = screen.getCursorScreenPoint();
E
Erich Gamma 已提交
827 828 829 830 831 832 833 834 835 836 837 838 839 840
				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());
			}

			// fallback to first display
			if (!displayToUse) {
				displayToUse = displays[0];
			}
		}

B
Benjamin Pasero 已提交
841
		const defaultState = defaultWindowState();
E
Erich Gamma 已提交
842 843 844 845 846 847
		defaultState.x = displayToUse.bounds.x + (displayToUse.bounds.width / 2) - (defaultState.width / 2);
		defaultState.y = displayToUse.bounds.y + (displayToUse.bounds.height / 2) - (defaultState.height / 2);

		return this.ensureNoOverlap(defaultState);
	}

J
Joao Moreno 已提交
848
	private ensureNoOverlap(state: ISingleWindowState): ISingleWindowState {
E
Erich Gamma 已提交
849 850 851 852
		if (WindowsManager.WINDOWS.length === 0) {
			return state;
		}

853 854
		const existingWindowBounds = WindowsManager.WINDOWS.map(win => win.getBounds());
		while (existingWindowBounds.some(b => b.x === state.x || b.y === state.y)) {
E
Erich Gamma 已提交
855 856 857 858 859 860 861
			state.x += 30;
			state.y += 30;
		}

		return state;
	}

862
	public openFileFolderPicker(forceNewWindow?: boolean): void {
863
		this.doPickAndOpen({ pickFolders: true, pickFiles: true, forceNewWindow });
864 865
	}

866 867
	public openFilePicker(forceNewWindow?: boolean, path?: string): void {
		this.doPickAndOpen({ pickFiles: true, forceNewWindow, path });
868 869 870
	}

	public openFolderPicker(forceNewWindow?: boolean): void {
871
		this.doPickAndOpen({ pickFolders: true, forceNewWindow });
E
Erich Gamma 已提交
872 873
	}

874 875 876 877 878 879 880 881 882 883 884 885 886 887 888 889
	public openAccessibilityOptions(): void {
		let win = new BrowserWindow({
			alwaysOnTop: true,
			skipTaskbar: true,
			resizable: false,
			width: 450,
			height: 300,
			show: true,
			title: nls.localize('accessibilityOptionsWindowTitle', "Accessibility Options")
		});

		win.setMenuBarVisibility(false);

		win.loadURL('chrome://accessibility');
	}

890
	private doPickAndOpen(options: INativeOpenDialogOptions): void {
891
		this.getFileOrFolderPaths(options, (paths: string[]) => {
E
Erich Gamma 已提交
892
			if (paths && paths.length) {
893
				this.open({ cli: this.environmentService.args, pathsToOpen: paths, forceNewWindow: options.forceNewWindow });
E
Erich Gamma 已提交
894 895 896 897
			}
		});
	}

898
	private getFileOrFolderPaths(options: INativeOpenDialogOptions, clb: (paths: string[]) => void): void {
B
Benjamin Pasero 已提交
899 900
		const workingDir = options.path || this.storageService.getItem<string>(WindowsManager.workingDirPickerStorageKey);
		const focussedWindow = this.getFocusedWindow();
E
Erich Gamma 已提交
901

B
Benjamin Pasero 已提交
902
		let pickerProperties: ('openFile' | 'openDirectory' | 'multiSelections' | 'createDirectory')[];
903
		if (options.pickFiles && options.pickFolders) {
E
Erich Gamma 已提交
904 905
			pickerProperties = ['multiSelections', 'openDirectory', 'openFile', 'createDirectory'];
		} else {
906
			pickerProperties = ['multiSelections', options.pickFolders ? 'openDirectory' : 'openFile', 'createDirectory'];
E
Erich Gamma 已提交
907 908
		}

909
		dialog.showOpenDialog(focussedWindow && focussedWindow.win, {
E
Erich Gamma 已提交
910 911
			defaultPath: workingDir,
			properties: pickerProperties
912
		}, paths => {
E
Erich Gamma 已提交
913 914 915
			if (paths && paths.length > 0) {

				// Remember path in storage for next time
J
Joao Moreno 已提交
916
				this.storageService.setItem(WindowsManager.workingDirPickerStorageKey, path.dirname(paths[0]));
E
Erich Gamma 已提交
917 918 919 920 921 922 923 924 925

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

B
Benjamin Pasero 已提交
926
	public focusLastActive(cli: ParsedArgs): VSCodeWindow {
B
Benjamin Pasero 已提交
927
		const lastActive = this.getLastActiveWindow();
E
Erich Gamma 已提交
928
		if (lastActive) {
B
Benjamin Pasero 已提交
929
			lastActive.focus();
930 931

			return lastActive;
E
Erich Gamma 已提交
932 933 934
		}

		// No window - open new one
935 936 937 938
		this.windowsState.openedFolders = []; // make sure we do not open too much
		const res = this.open({ cli: cli });

		return res && res[0];
E
Erich Gamma 已提交
939 940
	}

J
Joao Moreno 已提交
941
	public getLastActiveWindow(): VSCodeWindow {
E
Erich Gamma 已提交
942
		if (WindowsManager.WINDOWS.length) {
943 944
			const lastFocussedDate = Math.max.apply(Math, WindowsManager.WINDOWS.map(w => w.lastFocusTime));
			const res = WindowsManager.WINDOWS.filter(w => w.lastFocusTime === lastFocussedDate);
E
Erich Gamma 已提交
945 946 947 948 949 950 951 952
			if (res && res.length) {
				return res[0];
			}
		}

		return null;
	}

J
Joao Moreno 已提交
953
	public findWindow(workspacePath: string, filePath?: string, extensionDevelopmentPath?: string): VSCodeWindow {
E
Erich Gamma 已提交
954 955 956
		if (WindowsManager.WINDOWS.length) {

			// Sort the last active window to the front of the array of windows to test
B
Benjamin Pasero 已提交
957 958
			const windowsToTest = WindowsManager.WINDOWS.slice(0);
			const lastActiveWindow = this.getLastActiveWindow();
E
Erich Gamma 已提交
959 960 961 962 963 964
			if (lastActiveWindow) {
				windowsToTest.splice(windowsToTest.indexOf(lastActiveWindow), 1);
				windowsToTest.unshift(lastActiveWindow);
			}

			// Find it
965
			const res = windowsToTest.filter(w => {
E
Erich Gamma 已提交
966 967

				// match on workspace
968
				if (typeof w.openedWorkspacePath === 'string' && (this.isPathEqual(w.openedWorkspacePath, workspacePath))) {
E
Erich Gamma 已提交
969 970 971 972
					return true;
				}

				// match on file
B
Benjamin Pasero 已提交
973
				if (typeof w.openedFilePath === 'string' && this.isPathEqual(w.openedFilePath, filePath)) {
E
Erich Gamma 已提交
974 975 976 977 978 979 980 981
					return true;
				}

				// match on file path
				if (typeof w.openedWorkspacePath === 'string' && filePath && paths.isEqualOrParent(filePath, w.openedWorkspacePath)) {
					return true;
				}

982 983 984 985 986
				// match on extension development path
				if (typeof extensionDevelopmentPath === 'string' && w.extensionDevelopmentPath === extensionDevelopmentPath) {
					return true;
				}

E
Erich Gamma 已提交
987 988 989 990 991 992 993 994 995 996 997 998
				return false;
			});

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

		return null;
	}

	public openNewWindow(): void {
999
		this.open({ cli: this.environmentService.args, forceNewWindow: true, forceEmpty: true });
E
Erich Gamma 已提交
1000 1001 1002 1003 1004 1005
	}

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

		if (focusedWindow) {
1006
			focusedWindow.sendWhenReady(channel, ...args);
E
Erich Gamma 已提交
1007 1008 1009 1010
		}
	}

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

1016
			w.sendWhenReady(channel, payload);
E
Erich Gamma 已提交
1017 1018 1019
		});
	}

J
Joao Moreno 已提交
1020
	public getFocusedWindow(): VSCodeWindow {
B
Benjamin Pasero 已提交
1021
		const win = BrowserWindow.getFocusedWindow();
E
Erich Gamma 已提交
1022 1023 1024 1025 1026 1027 1028
		if (win) {
			return this.getWindowById(win.id);
		}

		return null;
	}

J
Joao Moreno 已提交
1029
	public getWindowById(windowId: number): VSCodeWindow {
1030
		const res = WindowsManager.WINDOWS.filter(w => w.id === windowId);
E
Erich Gamma 已提交
1031 1032 1033 1034 1035 1036 1037
		if (res && res.length === 1) {
			return res[0];
		}

		return null;
	}

J
Joao Moreno 已提交
1038
	public getWindows(): VSCodeWindow[] {
E
Erich Gamma 已提交
1039 1040 1041 1042 1043 1044 1045
		return WindowsManager.WINDOWS;
	}

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

J
Joao Moreno 已提交
1046
	private onWindowError(vscodeWindow: VSCodeWindow, error: WindowError): void {
E
Erich Gamma 已提交
1047 1048 1049 1050
		console.error(error === WindowError.CRASHED ? '[VS Code]: render process crashed!' : '[VS Code]: detected unresponsive');

		// Unresponsive
		if (error === WindowError.UNRESPONSIVE) {
1051
			dialog.showMessageBox(vscodeWindow.win, {
B
Benjamin Pasero 已提交
1052
				title: product.nameLong,
E
Erich Gamma 已提交
1053
				type: 'warning',
B
Benjamin Pasero 已提交
1054
				buttons: [nls.localize('reopen', "Reopen"), nls.localize('wait', "Keep Waiting"), nls.localize('close', "Close")],
1055
				message: nls.localize('appStalled', "The window is no longer responding"),
B
Benjamin Pasero 已提交
1056
				detail: nls.localize('appStalledDetail', "You can reopen or close the window or keep waiting."),
E
Erich Gamma 已提交
1057
				noLink: true
1058
			}, result => {
E
Erich Gamma 已提交
1059
				if (result === 0) {
1060 1061
					vscodeWindow.reload();
				} else if (result === 2) {
1062
					this.onBeforeWindowClose(vscodeWindow); // 'close' event will not be fired on destroy(), so run it manually
1063
					vscodeWindow.win.destroy(); // make sure to destroy the window as it is unresponsive
E
Erich Gamma 已提交
1064 1065 1066 1067 1068 1069
				}
			});
		}

		// Crashed
		else {
1070
			dialog.showMessageBox(vscodeWindow.win, {
B
Benjamin Pasero 已提交
1071
				title: product.nameLong,
E
Erich Gamma 已提交
1072
				type: 'warning',
B
Benjamin Pasero 已提交
1073
				buttons: [nls.localize('reopen', "Reopen"), nls.localize('close', "Close")],
1074
				message: nls.localize('appCrashed', "The window has crashed"),
B
Benjamin Pasero 已提交
1075
				detail: nls.localize('appCrashedDetail', "We are sorry for the inconvenience! You can reopen the window to continue where you left off."),
E
Erich Gamma 已提交
1076
				noLink: true
1077
			}, result => {
1078 1079 1080
				if (result === 0) {
					vscodeWindow.reload();
				} else if (result === 1) {
1081
					this.onBeforeWindowClose(vscodeWindow); // 'close' event will not be fired on destroy(), so run it manually
1082 1083
					vscodeWindow.win.destroy(); // make sure to destroy the window as it has crashed
				}
E
Erich Gamma 已提交
1084 1085 1086 1087
			});
		}
	}

J
Joao Moreno 已提交
1088 1089
	private onBeforeWindowClose(win: VSCodeWindow): void {
		if (win.readyState !== ReadyState.READY) {
E
Erich Gamma 已提交
1090 1091 1092 1093
			return; // only persist windows that are fully loaded
		}

		// On Window close, update our stored state of this window
B
Benjamin Pasero 已提交
1094
		const state: IWindowState = { workspacePath: win.openedWorkspacePath, uiState: win.serializeWindowState() };
E
Erich Gamma 已提交
1095 1096 1097 1098 1099 1100
		if (win.isPluginDevelopmentHost) {
			this.windowsState.lastPluginDevelopmentHostWindow = state;
		} else {
			this.windowsState.lastActiveWindow = state;

			this.windowsState.openedFolders.forEach(o => {
B
Benjamin Pasero 已提交
1101
				if (this.isPathEqual(o.workspacePath, win.openedWorkspacePath)) {
E
Erich Gamma 已提交
1102 1103 1104 1105 1106 1107
					o.uiState = state.uiState;
				}
			});
		}
	}

J
Joao Moreno 已提交
1108
	private onWindowClosed(win: VSCodeWindow): void {
E
Erich Gamma 已提交
1109 1110 1111 1112 1113

		// Tell window
		win.dispose();

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

		// Emit
J
Joao Moreno 已提交
1118
		this.eventEmitter.emit(EventTypes.CLOSE, win.id);
E
Erich Gamma 已提交
1119
	}
B
Benjamin Pasero 已提交
1120 1121 1122 1123 1124 1125 1126 1127 1128 1129 1130 1131 1132 1133 1134 1135 1136 1137 1138 1139 1140 1141 1142 1143

	private isPathEqual(pathA: string, pathB: string): boolean {
		if (pathA === pathB) {
			return true;
		}

		if (!pathA || !pathB) {
			return false;
		}

		pathA = path.normalize(pathA);
		pathB = path.normalize(pathB);

		if (pathA === pathB) {
			return true;
		}

		if (!platform.isLinux) {
			pathA = pathA.toLowerCase();
			pathB = pathB.toLowerCase();
		}

		return pathA === pathB;
	}
J
Joao Moreno 已提交
1144 1145 1146 1147 1148 1149 1150 1151 1152 1153 1154 1155 1156 1157 1158 1159 1160 1161

	toggleMenuBar(windowId: number): void {
		// Update in settings
		const menuBarHidden = this.storageService.getItem(VSCodeWindow.menuBarHiddenKey, false);
		const newMenuBarHidden = !menuBarHidden;
		this.storageService.setItem(VSCodeWindow.menuBarHiddenKey, newMenuBarHidden);

		// Update across windows
		WindowsManager.WINDOWS.forEach(w => w.setMenuBarVisibility(!newMenuBarHidden));

		// Inform user if menu bar is now hidden
		if (newMenuBarHidden) {
			const vscodeWindow = this.getWindowById(windowId);
			if (vscodeWindow) {
				vscodeWindow.send('vscode:showInfoMessage', nls.localize('hiddenMenuBar', "You can still access the menu bar by pressing the **Alt** key."));
			}
		}
	}
1162
}