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

6
import * as fs from 'fs';
7
import { basename, normalize, join, } from 'vs/base/common/path';
B
Benjamin Pasero 已提交
8
import { localize } from 'vs/nls';
J
Joao Moreno 已提交
9
import * as arrays from 'vs/base/common/arrays';
M
Martin Aeschlimann 已提交
10
import { assign, mixin } from 'vs/base/common/objects';
11 12
import { IBackupMainService } from 'vs/platform/backup/electron-main/backup';
import { IEmptyWindowBackupInfo } from 'vs/platform/backup/node/backup';
J
Joao Moreno 已提交
13
import { IEnvironmentService, ParsedArgs } from 'vs/platform/environment/common/environment';
14
import { IStateService } from 'vs/platform/state/node/state';
15
import { CodeWindow, defaultWindowState } from 'vs/code/electron-main/window';
16
import { ipcMain as ipc, screen, BrowserWindow, systemPreferences, MessageBoxOptions, Display } from 'electron';
17
import { parseLineAndColumnAware } from 'vs/code/node/paths';
18
import { ILifecycleMainService, UnloadReason, LifecycleMainService, LifecycleMainPhase } from 'vs/platform/lifecycle/electron-main/lifecycleMainService';
19
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
20
import { ILogService } from 'vs/platform/log/common/log';
B
Benjamin Pasero 已提交
21
import { IWindowSettings, OpenContext, IPath, IWindowConfiguration, IPathsToWaitFor, isFileToOpen, isWorkspaceToOpen, isFolderToOpen, IWindowOpenable, IOpenEmptyWindowOptions, IAddFoldersRequest } from 'vs/platform/windows/common/windows';
22
import { getLastActiveWindow, findBestWindowOrFolderForFile, findWindowOnWorkspace, findWindowOnExtensionDevelopmentPath, findWindowOnWorkspaceOrFolderUri } from 'vs/platform/windows/node/window';
M
Matt Bierner 已提交
23
import { Event as CommonEvent, Emitter } from 'vs/base/common/event';
24
import product from 'vs/platform/product/common/product';
25
import { IWindowsMainService, IOpenConfiguration, IWindowsCountChangedEvent, ICodeWindow, IWindowState as ISingleWindowState, WindowMode } from 'vs/platform/windows/electron-main/windows';
26
import { IWorkspacesHistoryMainService } from 'vs/platform/workspaces/electron-main/workspacesHistoryMainService';
27
import { IProcessEnvironment, isMacintosh, isWindows } from 'vs/base/common/platform';
28
import { IWorkspaceIdentifier, isSingleFolderWorkspaceIdentifier, hasWorkspaceFileExtension, IRecent } from 'vs/platform/workspaces/common/workspaces';
B
Benjamin Pasero 已提交
29
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
J
Johannes Rieken 已提交
30
import { Schemas } from 'vs/base/common/network';
31
import { URI } from 'vs/base/common/uri';
32
import { getComparisonKey, isEqual, normalizePath, originalFSPath, hasTrailingPathSeparator, removeTrailingPathSeparator } from 'vs/base/common/resources';
M
Martin Aeschlimann 已提交
33
import { getRemoteAuthority } from 'vs/platform/remote/common/remoteHosts';
34
import { restoreWindowsState, WindowsStateStorageData, getWindowsStateStoreData } from 'vs/platform/windows/electron-main/windowsStateStorage';
B
Benjamin Pasero 已提交
35
import { getWorkspaceIdentifier, IWorkspacesMainService } from 'vs/platform/workspaces/electron-main/workspacesMainService';
36
import { once } from 'vs/base/common/functional';
M
Matt Bierner 已提交
37
import { Disposable } from 'vs/base/common/lifecycle';
38 39
import { IDialogMainService } from 'vs/platform/dialogs/electron-main/dialogs';
import { withNullAsUndefined } from 'vs/base/common/types';
40 41
import { isWindowsDriveLetter, toSlashes } from 'vs/base/common/extpath';
import { CharCode } from 'vs/base/common/charCode';
E
Erich Gamma 已提交
42

M
Martin Aeschlimann 已提交
43 44 45 46 47 48 49 50 51 52 53 54 55 56
export interface IWindowState {
	workspace?: IWorkspaceIdentifier;
	folderUri?: URI;
	backupPath?: string;
	remoteAuthority?: string;
	uiState: ISingleWindowState;
}

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

57 58 59 60
interface INewWindowState extends ISingleWindowState {
	hasDefaultState?: boolean;
}

61
type RestoreWindowsSetting = 'all' | 'folders' | 'one' | 'none';
62

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

67
	workspace?: IWorkspaceIdentifier;
68
	folderUri?: URI;
B
Benjamin Pasero 已提交
69

M
Matt Bierner 已提交
70
	remoteAuthority?: string;
M
Martin Aeschlimann 已提交
71

B
Benjamin Pasero 已提交
72 73
	initialStartup?: boolean;

74
	fileInputs?: IFileInputs;
B
Benjamin Pasero 已提交
75 76

	forceNewWindow?: boolean;
77
	forceNewTabbedWindow?: boolean;
78
	windowToUse?: ICodeWindow;
B
Benjamin Pasero 已提交
79

80 81 82 83 84 85
	emptyWindowBackupInfo?: IEmptyWindowBackupInfo;
}

interface IPathParseOptions {
	ignoreFileNotFound?: boolean;
	gotoLineMode?: boolean;
M
Martin Aeschlimann 已提交
86
	remoteAuthority?: string;
87 88 89
}

interface IFileInputs {
90
	filesToOpenOrCreate: IPath[];
91 92
	filesToDiff: IPath[];
	filesToWait?: IPathsToWaitFor;
M
Martin Aeschlimann 已提交
93
	remoteAuthority?: string;
B
Benjamin Pasero 已提交
94 95
}

B
Benjamin Pasero 已提交
96
interface IPathToOpen extends IPath {
97

98
	// the workspace for a Code instance to open
99
	workspace?: IWorkspaceIdentifier;
100

101
	// the folder path for a Code instance to open
102
	folderUri?: URI;
103

104
	// the backup path for a Code instance to use
105 106
	backupPath?: string;

M
Martin Aeschlimann 已提交
107 108 109
	// the remote authority for the Code instance to open. Undefined if not remote.
	remoteAuthority?: string;

M
Martin Aeschlimann 已提交
110 111
	// optional label for the recent history
	label?: string;
112 113
}

114 115 116 117 118 119 120 121 122 123 124 125 126 127
function isFolderPathToOpen(path: IPathToOpen): path is IFolderPathToOpen {
	return !!path.folderUri;
}

interface IFolderPathToOpen {

	// the folder path for a Code instance to open
	folderUri: URI;

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

	// the remote authority for the Code instance to open. Undefined if not remote.
	remoteAuthority?: string;
M
Martin Aeschlimann 已提交
128 129 130

	// optional label for the recent history
	label?: string;
131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146
}

function isWorkspacePathToOpen(path: IPathToOpen): path is IWorkspacePathToOpen {
	return !!path.workspace;
}

interface IWorkspacePathToOpen {

	// the workspace for a Code instance to open
	workspace: IWorkspaceIdentifier;

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

	// the remote authority for the Code instance to open. Undefined if not remote.
	remoteAuthority?: string;
M
Martin Aeschlimann 已提交
147 148 149

	// optional label for the recent history
	label?: string;
150 151
}

152
export class WindowsMainService extends Disposable implements IWindowsMainService {
J
Joao Moreno 已提交
153

154
	_serviceBrand: undefined;
E
Erich Gamma 已提交
155

156
	private static readonly windowsStateStorageKey = 'windowsState';
E
Erich Gamma 已提交
157

158
	private static readonly WINDOWS: ICodeWindow[] = [];
159

160
	private readonly windowsState: IWindowsState;
161
	private lastClosedWindowState?: IWindowState;
E
Erich Gamma 已提交
162

M
Matt Bierner 已提交
163
	private readonly _onWindowReady = this._register(new Emitter<ICodeWindow>());
164
	readonly onWindowReady: CommonEvent<ICodeWindow> = this._onWindowReady.event;
165

M
Matt Bierner 已提交
166
	private readonly _onWindowClose = this._register(new Emitter<number>());
167
	readonly onWindowClose: CommonEvent<number> = this._onWindowClose.event;
168

M
Matt Bierner 已提交
169
	private readonly _onWindowsCountChanged = this._register(new Emitter<IWindowsCountChangedEvent>());
170
	readonly onWindowsCountChanged: CommonEvent<IWindowsCountChangedEvent> = this._onWindowsCountChanged.event;
B
Benjamin Pasero 已提交
171

J
Joao Moreno 已提交
172
	constructor(
B
Benjamin Pasero 已提交
173
		private readonly machineId: string,
174
		private readonly initialUserEnv: IProcessEnvironment,
175 176 177
		@ILogService private readonly logService: ILogService,
		@IStateService private readonly stateService: IStateService,
		@IEnvironmentService private readonly environmentService: IEnvironmentService,
178
		@ILifecycleMainService private readonly lifecycleMainService: ILifecycleMainService,
179 180
		@IBackupMainService private readonly backupMainService: IBackupMainService,
		@IConfigurationService private readonly configurationService: IConfigurationService,
181
		@IWorkspacesHistoryMainService private readonly workspacesHistoryMainService: IWorkspacesHistoryMainService,
182
		@IWorkspacesMainService private readonly workspacesMainService: IWorkspacesMainService,
183 184
		@IInstantiationService private readonly instantiationService: IInstantiationService,
		@IDialogMainService private readonly dialogMainService: IDialogMainService
185
	) {
M
Matt Bierner 已提交
186
		super();
187

188
		this.windowsState = restoreWindowsState(this.stateService.getItem<WindowsStateStorageData>(WindowsMainService.windowsStateStorageKey));
189 190 191
		if (!Array.isArray(this.windowsState.openedWindows)) {
			this.windowsState.openedWindows = [];
		}
192

193 194
		this.lifecycleMainService.when(LifecycleMainPhase.Ready).then(() => this.registerListeners());
		this.lifecycleMainService.when(LifecycleMainPhase.AfterWindowOpen).then(() => this.installWindowsMutex());
195
	}
J
Joao Moreno 已提交
196

197
	private installWindowsMutex(): void {
198 199
		const win32MutexName = product.win32MutexName;
		if (isWindows && win32MutexName) {
200 201
			try {
				const WindowsMutex = (require.__$__nodeRequire('windows-mutex') as typeof import('windows-mutex')).Mutex;
202
				const mutex = new WindowsMutex(win32MutexName);
203
				once(this.lifecycleMainService.onWillShutdown)(() => mutex.release());
204 205 206 207
			} catch (e) {
				this.logService.error(e);
			}
		}
E
Erich Gamma 已提交
208 209 210
	}

	private registerListeners(): void {
211

B
Benjamin Pasero 已提交
212
		// React to workbench ready events from windows
213
		ipc.on('vscode:workbenchReady', (event: Event, windowId: number) => {
B
Benjamin Pasero 已提交
214
			this.logService.trace('IPC#vscode-workbenchReady');
E
Erich Gamma 已提交
215

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

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

225 226
		// React to HC color scheme changes (Windows)
		if (isWindows) {
227 228
			const onHighContrastChange = () => {
				if (systemPreferences.isInvertedColorScheme() || systemPreferences.isHighContrastColorScheme()) {
229 230 231 232
					this.sendToAll('vscode:enterHighContrast');
				} else {
					this.sendToAll('vscode:leaveHighContrast');
				}
233 234 235 236
			};

			systemPreferences.on('inverted-color-scheme-changed', () => onHighContrastChange());
			systemPreferences.on('high-contrast-color-scheme-changed', () => onHighContrastChange());
237 238
		}

239
		// Handle various lifecycle events around windows
240 241
		this.lifecycleMainService.onBeforeWindowClose(window => this.onBeforeWindowClose(window));
		this.lifecycleMainService.onBeforeShutdown(() => this.onBeforeShutdown());
242 243 244 245 246
		this.onWindowsCountChanged(e => {
			if (e.newCount - e.oldCount > 0) {
				// clear last closed window state when a new window opens. this helps on macOS where
				// otherwise closing the last window, opening a new window and then quitting would
				// use the state of the previously closed window when restarting.
R
Rob Lourens 已提交
247
				this.lastClosedWindowState = undefined;
248 249
			}
		});
250 251 252 253 254

		// Signal a window is ready after having entered a workspace
		this._register(this.workspacesMainService.onWorkspaceEntered(event => {
			this._onWindowReady.fire(event.window);
		}));
255 256
	}

257
	// Note that onBeforeShutdown() and onBeforeWindowClose() are fired in different order depending on the OS:
258
	// - macOS: since the app will not quit when closing the last window, you will always first get
259
	//          the onBeforeShutdown() event followed by N onbeforeWindowClose() events for each window
260 261
	// - 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()
262
	//          and then onBeforeShutdown(). Using the quit action however will first issue onBeforeShutdown()
263
	//          and then onBeforeWindowClose().
264 265 266 267 268 269 270
	//
	// Here is the behaviour on different OS dependig on action taken (Electron 1.7.x):
	//
	// Legend
	// -  quit(N): quit application with N windows opened
	// - close(1): close one window via the window close button
	// - closeAll: close all windows via the taskbar command
271
	// - onBeforeShutdown(N): number of windows reported in this event handler
272 273 274
	// - onBeforeWindowClose(N, M): number of windows reported and quitRequested boolean in this event handler
	//
	// macOS
275 276 277
	// 	-     quit(1): onBeforeShutdown(1), onBeforeWindowClose(1, true)
	// 	-     quit(2): onBeforeShutdown(2), onBeforeWindowClose(2, true), onBeforeWindowClose(2, true)
	// 	-     quit(0): onBeforeShutdown(0)
278 279 280
	// 	-    close(1): onBeforeWindowClose(1, false)
	//
	// Windows
281 282
	// 	-     quit(1): onBeforeShutdown(1), onBeforeWindowClose(1, true)
	// 	-     quit(2): onBeforeShutdown(2), onBeforeWindowClose(2, true), onBeforeWindowClose(2, true)
283
	// 	-    close(1): onBeforeWindowClose(2, false)[not last window]
284 285
	// 	-    close(1): onBeforeWindowClose(1, false), onBeforeShutdown(0)[last window]
	// 	- closeAll(2): onBeforeWindowClose(2, false), onBeforeWindowClose(2, false), onBeforeShutdown(0)
286 287
	//
	// Linux
288 289
	// 	-     quit(1): onBeforeShutdown(1), onBeforeWindowClose(1, true)
	// 	-     quit(2): onBeforeShutdown(2), onBeforeWindowClose(2, true), onBeforeWindowClose(2, true)
290
	// 	-    close(1): onBeforeWindowClose(2, false)[not last window]
291 292
	// 	-    close(1): onBeforeWindowClose(1, false), onBeforeShutdown(0)[last window]
	// 	- closeAll(2): onBeforeWindowClose(2, false), onBeforeWindowClose(2, false), onBeforeShutdown(0)
293
	//
294
	private onBeforeShutdown(): void {
295
		const currentWindowsState: IWindowsState = {
296
			openedWindows: [],
297
			lastPluginDevelopmentHostWindow: this.windowsState.lastPluginDevelopmentHostWindow,
298
			lastActiveWindow: this.lastClosedWindowState
299 300 301 302 303 304
		};

		// 1.) Find a last active window (pick any other first window otherwise)
		if (!currentWindowsState.lastActiveWindow) {
			let activeWindow = this.getLastActiveWindow();
			if (!activeWindow || activeWindow.isExtensionDevelopmentHost) {
305
				activeWindow = WindowsMainService.WINDOWS.filter(window => !window.isExtensionDevelopmentHost)[0];
306
			}
E
Erich Gamma 已提交
307

308
			if (activeWindow) {
309
				currentWindowsState.lastActiveWindow = this.toWindowState(activeWindow);
E
Erich Gamma 已提交
310
			}
311 312 313
		}

		// 2.) Find extension host window
314
		const extensionHostWindow = WindowsMainService.WINDOWS.filter(window => window.isExtensionDevelopmentHost && !window.isExtensionTestHost)[0];
315
		if (extensionHostWindow) {
316
			currentWindowsState.lastPluginDevelopmentHostWindow = this.toWindowState(extensionHostWindow);
317
		}
E
Erich Gamma 已提交
318

319
		// 3.) All windows (except extension host) for N >= 2 to support restoreWindows: all or for auto update
320 321 322 323 324
		//
		// 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) {
325
			currentWindowsState.openedWindows = WindowsMainService.WINDOWS.filter(window => !window.isExtensionDevelopmentHost).map(window => this.toWindowState(window));
326
		}
E
Erich Gamma 已提交
327

328
		// Persist
329
		this.stateService.setItem(WindowsMainService.windowsStateStorageKey, getWindowsStateStoreData(currentWindowsState));
330
	}
331

332
	// See note on #onBeforeShutdown() for details how these events are flowing
333
	private onBeforeWindowClose(win: ICodeWindow): void {
334
		if (this.lifecycleMainService.quitRequested) {
335 336 337 338
			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
339
		const state: IWindowState = this.toWindowState(win);
340 341 342 343
		if (win.isExtensionDevelopmentHost && !win.isExtensionTestHost) {
			this.windowsState.lastPluginDevelopmentHostWindow = state; // do not let test run window state overwrite our extension development state
		}

344
		// Any non extension host window with same workspace or folder
345
		else if (!win.isExtensionDevelopmentHost && (!!win.openedWorkspace || !!win.openedFolderUri)) {
346
			this.windowsState.openedWindows.forEach(o => {
B
fix npe  
Benjamin Pasero 已提交
347
				const sameWorkspace = win.openedWorkspace && o.workspace && o.workspace.id === win.openedWorkspace.id;
348
				const sameFolder = win.openedFolderUri && o.folderUri && isEqual(o.folderUri, win.openedFolderUri);
349 350

				if (sameWorkspace || sameFolder) {
351 352 353 354 355 356 357
					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.
358 359 360
		// 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) {
361 362
			this.lastClosedWindowState = state;
		}
E
Erich Gamma 已提交
363 364
	}

365
	private toWindowState(win: ICodeWindow): IWindowState {
366
		return {
367
			workspace: win.openedWorkspace,
368
			folderUri: win.openedFolderUri,
369
			backupPath: win.backupPath,
M
Martin Aeschlimann 已提交
370
			remoteAuthority: win.remoteAuthority,
371 372 373 374
			uiState: win.serializeWindowState()
		};
	}

375 376
	openEmptyWindow(context: OpenContext, options?: IOpenEmptyWindowOptions): ICodeWindow[] {
		let cli = this.environmentService.args;
B
Benjamin Pasero 已提交
377
		const remote = options?.remoteAuthority;
378 379 380 381
		if (cli && (cli.remote !== remote)) {
			cli = { ...cli, remote };
		}

B
Benjamin Pasero 已提交
382
		const forceReuseWindow = options?.forceReuseWindow;
383 384 385 386 387
		const forceNewWindow = !forceReuseWindow;

		return this.open({ context, cli, forceEmpty: true, forceNewWindow, forceReuseWindow });
	}

B
Benjamin Pasero 已提交
388
	open(openConfig: IOpenConfiguration): ICodeWindow[] {
389
		this.logService.trace('windowsManager#open');
390
		openConfig = this.validateOpenConfig(openConfig);
391

392
		const pathsToOpen = this.getPathsToOpen(openConfig);
393

394 395 396 397 398 399
		const foldersToAdd: IFolderPathToOpen[] = [];
		const foldersToOpen: IFolderPathToOpen[] = [];
		const workspacesToOpen: IWorkspacePathToOpen[] = [];
		const emptyToRestore: IEmptyWindowBackupInfo[] = []; // empty windows with backupPath
		let emptyToOpen: number = 0;
		let fileInputs: IFileInputs | undefined; 		// collect all file inputs
400
		for (const path of pathsToOpen) {
401 402 403 404 405 406 407 408 409 410 411
			if (isFolderPathToOpen(path)) {
				if (openConfig.addMode) {
					// When run with --add, take the folders that are to be opened as
					// folders that should be added to the currently active window.
					foldersToAdd.push(path);
				} else {
					foldersToOpen.push(path);
				}
			} else if (isWorkspacePathToOpen(path)) {
				workspacesToOpen.push(path);
			} else if (path.fileUri) {
412
				if (!fileInputs) {
413
					fileInputs = { filesToOpenOrCreate: [], filesToDiff: [], remoteAuthority: path.remoteAuthority };
414
				}
415
				fileInputs.filesToOpenOrCreate.push(path);
416 417 418 419
			} else if (path.backupPath) {
				emptyToRestore.push({ backupFolder: basename(path.backupPath), remoteAuthority: path.remoteAuthority });
			} else {
				emptyToOpen++;
420 421
			}
		}
422 423 424

		// When run with --diff, take the files to open as files to diff
		// if there are exactly two files provided.
425 426 427
		if (fileInputs && openConfig.diffMode && fileInputs.filesToOpenOrCreate.length === 2) {
			fileInputs.filesToDiff = fileInputs.filesToOpenOrCreate;
			fileInputs.filesToOpenOrCreate = [];
E
Erich Gamma 已提交
428 429
		}

430
		// When run with --wait, make sure we keep the paths to wait for
M
Martin Aeschlimann 已提交
431
		if (fileInputs && openConfig.waitMarkerFileURI) {
432
			fileInputs.filesToWait = { paths: [...fileInputs.filesToDiff, ...fileInputs.filesToOpenOrCreate], waitMarkerFileUri: openConfig.waitMarkerFileURI };
433 434
		}

435
		//
436
		// These are windows to restore because of hot-exit or from previous session (only performed once on startup!)
437
		//
438 439
		let foldersToRestore: URI[] = [];
		let workspacesToRestore: IWorkspacePathToOpen[] = [];
B
Benjamin Pasero 已提交
440
		if (openConfig.initialStartup && !openConfig.cli.extensionDevelopmentPath && !openConfig.cli['disable-restore-windows']) {
441
			let foldersToRestore = this.backupMainService.getFolderBackupPaths();
442
			foldersToAdd.push(...foldersToRestore.map(f => ({ folderUri: f, remoteAuhority: getRemoteAuthority(f), isRestored: true })));
443

444 445 446
			// collect from workspaces with hot-exit backups and from previous window session
			workspacesToRestore = [...this.backupMainService.getWorkspaceBackups(), ...this.workspacesMainService.getUntitledWorkspacesSync()];
			workspacesToOpen.push(...workspacesToRestore);
447

448 449 450
			emptyToRestore.push(...this.backupMainService.getEmptyWindowBackupPaths());
		} else {
			emptyToRestore.length = 0;
451
		}
452 453

		// Open based on config
454
		const usedWindows = this.doOpen(openConfig, workspacesToOpen, foldersToOpen, emptyToRestore, emptyToOpen, fileInputs, foldersToAdd);
455

456
		// Make sure to pass focus to the most relevant of the windows if we open multiple
457
		if (usedWindows.length > 1) {
458

459
			const focusLastActive = this.windowsState.lastActiveWindow && !openConfig.forceEmpty && openConfig.cli._.length && !openConfig.cli['file-uri'] && !openConfig.cli['folder-uri'] && !(openConfig.urisToOpen && openConfig.urisToOpen.length);
460 461
			let focusLastOpened = true;
			let focusLastWindow = true;
462

463 464
			// 1.) focus last active window if we are not instructed to open any paths
			if (focusLastActive) {
465
				const lastActiveWindow = usedWindows.filter(window => this.windowsState.lastActiveWindow && window.backupPath === this.windowsState.lastActiveWindow.backupPath);
466 467
				if (lastActiveWindow.length) {
					lastActiveWindow[0].focus();
468 469
					focusLastOpened = false;
					focusLastWindow = false;
470 471 472
				}
			}

473 474 475 476 477
			// 2.) if instructed to open paths, focus last window which is not restored
			if (focusLastOpened) {
				for (let i = usedWindows.length - 1; i >= 0; i--) {
					const usedWindow = usedWindows[i];
					if (
478 479 480
						(usedWindow.openedWorkspace && workspacesToRestore.some(workspace => usedWindow.openedWorkspace && workspace.workspace.id === usedWindow.openedWorkspace.id)) ||	// skip over restored workspace
						(usedWindow.openedFolderUri && foldersToRestore.some(uri => isEqual(uri, usedWindow.openedFolderUri))) ||															// skip over restored folder
						(usedWindow.backupPath && emptyToRestore.some(empty => usedWindow.backupPath && empty.backupFolder === basename(usedWindow.backupPath)))							// skip over restored empty window
481 482 483 484 485 486 487 488 489 490 491 492
					) {
						continue;
					}

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

			// 3.) finally, always ensure to have at least last used window focused
			if (focusLastWindow) {
493
				usedWindows[usedWindows.length - 1].focus();
494 495
			}
		}
496

497 498
		// 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
499 500
		const isDiff = fileInputs && fileInputs.filesToDiff.length > 0;
		if (!usedWindows.some(window => window.isExtensionDevelopmentHost) && !isDiff && !openConfig.noRecentEntry) {
M
Martin Aeschlimann 已提交
501 502 503 504 505 506 507 508
			const recents: IRecent[] = [];
			for (let pathToOpen of pathsToOpen) {
				if (pathToOpen.workspace) {
					recents.push({ label: pathToOpen.label, workspace: pathToOpen.workspace });
				} else if (pathToOpen.folderUri) {
					recents.push({ label: pathToOpen.label, folderUri: pathToOpen.folderUri });
				} else if (pathToOpen.fileUri) {
					recents.push({ label: pathToOpen.label, fileUri: pathToOpen.fileUri });
509
				}
510
			}
511
			this.workspacesHistoryMainService.addRecentlyOpened(recents);
512
		}
513

514
		// If we got started with --wait from the CLI, we need to signal to the outside when the window
515 516
		// used for the edit operation is closed or loaded to a different folder so that the waiting
		// process can continue. We do this by deleting the waitMarkerFilePath.
M
Martin Aeschlimann 已提交
517 518
		const waitMarkerFileURI = openConfig.waitMarkerFileURI;
		if (openConfig.context === OpenContext.CLI && waitMarkerFileURI && usedWindows.length === 1 && usedWindows[0]) {
519
			usedWindows[0].whenClosedOrLoaded.then(() => fs.unlink(waitMarkerFileURI.fsPath, _error => undefined));
520 521
		}

522 523 524
		return usedWindows;
	}

525 526 527 528 529 530 531 532 533 534
	private validateOpenConfig(config: IOpenConfiguration): IOpenConfiguration {

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

		return config;
	}

535 536
	private doOpen(
		openConfig: IOpenConfiguration,
537 538
		workspacesToOpen: IWorkspacePathToOpen[],
		foldersToOpen: IFolderPathToOpen[],
539
		emptyToRestore: IEmptyWindowBackupInfo[],
540
		emptyToOpen: number,
541
		fileInputs: IFileInputs | undefined,
542
		foldersToAdd: IFolderPathToOpen[]
543
	) {
544
		const usedWindows: ICodeWindow[] = [];
545

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

549 550
		// Handle folders to add by looking for the last active workspace (not on initial startup)
		if (!openConfig.initialStartup && foldersToAdd.length > 0) {
551
			const authority = foldersToAdd[0].remoteAuthority;
552
			const lastActiveWindow = this.getLastActiveWindowForAuthority(authority);
553
			if (lastActiveWindow) {
554
				usedWindows.push(this.doAddFoldersToExistingWindow(lastActiveWindow, foldersToAdd.map(f => f.folderUri)));
555 556 557
			}
		}

B
Benjamin Pasero 已提交
558
		// 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
559
		const potentialWindowsCount = foldersToOpen.length + workspacesToOpen.length + emptyToRestore.length;
560
		if (potentialWindowsCount === 0 && fileInputs) {
E
Erich Gamma 已提交
561

562
			// Find suitable window or folder path to open files in
563
			const fileToCheck = fileInputs.filesToOpenOrCreate[0] || fileInputs.filesToDiff[0];
564

M
Martin Aeschlimann 已提交
565
			// only look at the windows with correct authority
566
			const windows = WindowsMainService.WINDOWS.filter(window => fileInputs && window.remoteAuthority === fileInputs.remoteAuthority);
567

568
			const bestWindowOrFolder = findBestWindowOrFolderForFile({
569
				windows,
570 571
				newWindow: openFilesInNewWindow,
				context: openConfig.context,
B
Benjamin Pasero 已提交
572
				fileUri: fileToCheck?.fileUri,
573
				localWorkspaceResolver: workspace => workspace.configPath.scheme === Schemas.file ? this.workspacesMainService.resolveLocalWorkspaceSync(workspace.configPath) : null
574
			});
B
Benjamin Pasero 已提交
575

576 577 578 579 580
			// We found a window to open the files in
			if (bestWindowOrFolder instanceof CodeWindow) {

				// Window is workspace
				if (bestWindowOrFolder.openedWorkspace) {
581
					workspacesToOpen.push({ workspace: bestWindowOrFolder.openedWorkspace, remoteAuthority: bestWindowOrFolder.remoteAuthority });
582 583 584
				}

				// Window is single folder
585
				else if (bestWindowOrFolder.openedFolderUri) {
586
					foldersToOpen.push({ folderUri: bestWindowOrFolder.openedFolderUri, remoteAuthority: bestWindowOrFolder.remoteAuthority });
587 588 589 590 591 592
				}

				// Window is empty
				else {

					// Do open files
593
					usedWindows.push(this.doOpenFilesInExistingWindow(openConfig, bestWindowOrFolder, fileInputs));
594 595

					// Reset these because we handled them
R
Rob Lourens 已提交
596
					fileInputs = undefined;
597
				}
598 599 600
			}

			// Finally, if no window or folder is found, just open the files in an empty window
E
Erich Gamma 已提交
601
			else {
B
Benjamin Pasero 已提交
602
				usedWindows.push(this.openInBrowserWindow({
B
Benjamin Pasero 已提交
603 604 605
					userEnv: openConfig.userEnv,
					cli: openConfig.cli,
					initialStartup: openConfig.initialStartup,
606
					fileInputs,
607
					forceNewWindow: true,
M
Martin Aeschlimann 已提交
608
					remoteAuthority: fileInputs.remoteAuthority,
609
					forceNewTabbedWindow: openConfig.forceNewTabbedWindow
B
Benjamin Pasero 已提交
610
				}));
E
Erich Gamma 已提交
611

612
				// Reset these because we handled them
R
Rob Lourens 已提交
613
				fileInputs = undefined;
E
Erich Gamma 已提交
614 615 616
			}
		}

617
		// Handle workspaces to open (instructed and to restore)
618
		const allWorkspacesToOpen = arrays.distinct(workspacesToOpen, workspace => workspace.workspace.id); // prevent duplicates
619 620 621
		if (allWorkspacesToOpen.length > 0) {

			// Check for existing instances
622
			const windowsOnWorkspace = arrays.coalesce(allWorkspacesToOpen.map(workspaceToOpen => findWindowOnWorkspace(WindowsMainService.WINDOWS, workspaceToOpen.workspace)));
623 624
			if (windowsOnWorkspace.length > 0) {
				const windowOnWorkspace = windowsOnWorkspace[0];
B
Benjamin Pasero 已提交
625
				const fileInputsForWindow = (fileInputs?.remoteAuthority === windowOnWorkspace.remoteAuthority) ? fileInputs : undefined;
626 627

				// Do open files
628
				usedWindows.push(this.doOpenFilesInExistingWindow(openConfig, windowOnWorkspace, fileInputsForWindow));
629 630

				// Reset these because we handled them
631
				if (fileInputsForWindow) {
R
Rob Lourens 已提交
632
					fileInputs = undefined;
633
				}
634 635 636 637 638 639

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

			// Open remaining ones
			allWorkspacesToOpen.forEach(workspaceToOpen => {
640
				if (windowsOnWorkspace.some(win => win.openedWorkspace && win.openedWorkspace.id === workspaceToOpen.workspace.id)) {
641 642 643
					return; // ignore folders that are already open
				}

644
				const remoteAuthority = workspaceToOpen.remoteAuthority;
B
Benjamin Pasero 已提交
645
				const fileInputsForWindow = (fileInputs?.remoteAuthority === remoteAuthority) ? fileInputs : undefined;
646

647
				// Do open folder
648
				usedWindows.push(this.doOpenFolderOrWorkspace(openConfig, workspaceToOpen, openFolderInNewWindow, fileInputsForWindow));
649 650

				// Reset these because we handled them
651
				if (fileInputsForWindow) {
R
Rob Lourens 已提交
652
					fileInputs = undefined;
653
				}
654 655 656 657 658

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

659
		// Handle folders to open (instructed and to restore)
660
		const allFoldersToOpen = arrays.distinct(foldersToOpen, folder => getComparisonKey(folder.folderUri)); // prevent duplicates
661
		if (allFoldersToOpen.length > 0) {
E
Erich Gamma 已提交
662 663

			// Check for existing instances
664
			const windowsOnFolderPath = arrays.coalesce(allFoldersToOpen.map(folderToOpen => findWindowOnWorkspace(WindowsMainService.WINDOWS, folderToOpen.folderUri)));
665
			if (windowsOnFolderPath.length > 0) {
666
				const windowOnFolderPath = windowsOnFolderPath[0];
B
Benjamin Pasero 已提交
667
				const fileInputsForWindow = fileInputs?.remoteAuthority === windowOnFolderPath.remoteAuthority ? fileInputs : undefined;
E
Erich Gamma 已提交
668

669
				// Do open files
670
				usedWindows.push(this.doOpenFilesInExistingWindow(openConfig, windowOnFolderPath, fileInputsForWindow));
671

E
Erich Gamma 已提交
672
				// Reset these because we handled them
673
				if (fileInputsForWindow) {
R
Rob Lourens 已提交
674
					fileInputs = undefined;
675
				}
E
Erich Gamma 已提交
676

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

			// Open remaining ones
681
			allFoldersToOpen.forEach(folderToOpen => {
682

683
				if (windowsOnFolderPath.some(win => isEqual(win.openedFolderUri, folderToOpen.folderUri))) {
E
Erich Gamma 已提交
684 685 686
					return; // ignore folders that are already open
				}

687
				const remoteAuthority = folderToOpen.remoteAuthority;
B
Benjamin Pasero 已提交
688
				const fileInputsForWindow = (fileInputs?.remoteAuthority === remoteAuthority) ? fileInputs : undefined;
689

690
				// Do open folder
691
				usedWindows.push(this.doOpenFolderOrWorkspace(openConfig, folderToOpen, openFolderInNewWindow, fileInputsForWindow));
E
Erich Gamma 已提交
692 693

				// Reset these because we handled them
694
				if (fileInputsForWindow) {
R
Rob Lourens 已提交
695
					fileInputs = undefined;
696
				}
E
Erich Gamma 已提交
697

B
Benjamin Pasero 已提交
698
				openFolderInNewWindow = true; // any other folders to open must open in new window then
E
Erich Gamma 已提交
699 700 701
			});
		}

702
		// Handle empty to restore
703
		const allEmptyToRestore = arrays.distinct(emptyToRestore, info => info.backupFolder); // prevent duplicates
704 705
		if (allEmptyToRestore.length > 0) {
			allEmptyToRestore.forEach(emptyWindowBackupInfo => {
M
Martin Aeschlimann 已提交
706
				const remoteAuthority = emptyWindowBackupInfo.remoteAuthority;
B
Benjamin Pasero 已提交
707
				const fileInputsForWindow = (fileInputs?.remoteAuthority === remoteAuthority) ? fileInputs : undefined;
708

B
Benjamin Pasero 已提交
709
				usedWindows.push(this.openInBrowserWindow({
B
Benjamin Pasero 已提交
710 711 712
					userEnv: openConfig.userEnv,
					cli: openConfig.cli,
					initialStartup: openConfig.initialStartup,
713
					fileInputs: fileInputsForWindow,
M
Martin Aeschlimann 已提交
714
					remoteAuthority,
B
Benjamin Pasero 已提交
715
					forceNewWindow: true,
716
					forceNewTabbedWindow: openConfig.forceNewTabbedWindow,
717
					emptyWindowBackupInfo
B
Benjamin Pasero 已提交
718
				}));
719

B
wip  
Benjamin Pasero 已提交
720
				// Reset these because we handled them
721
				if (fileInputsForWindow) {
R
Rob Lourens 已提交
722
					fileInputs = undefined;
723
				}
B
wip  
Benjamin Pasero 已提交
724

B
Benjamin Pasero 已提交
725
				openFolderInNewWindow = true; // any other folders to open must open in new window then
726 727
			});
		}
B
Benjamin Pasero 已提交
728

729
		// Handle empty to open (only if no other window opened)
730 731 732 733
		if (usedWindows.length === 0 || fileInputs) {
			if (fileInputs && !emptyToOpen) {
				emptyToOpen++;
			}
734

R
Rob Lourens 已提交
735
			const remoteAuthority = fileInputs ? fileInputs.remoteAuthority : (openConfig.cli && openConfig.cli.remote || undefined);
736

737
			for (let i = 0; i < emptyToOpen; i++) {
B
Benjamin Pasero 已提交
738
				usedWindows.push(this.openInBrowserWindow({
B
Benjamin Pasero 已提交
739 740 741
					userEnv: openConfig.userEnv,
					cli: openConfig.cli,
					initialStartup: openConfig.initialStartup,
M
Martin Aeschlimann 已提交
742
					remoteAuthority,
743
					forceNewWindow: openFolderInNewWindow,
744 745
					forceNewTabbedWindow: openConfig.forceNewTabbedWindow,
					fileInputs
B
Benjamin Pasero 已提交
746
				}));
E
Erich Gamma 已提交
747

748
				// Reset these because we handled them
R
Rob Lourens 已提交
749
				fileInputs = undefined;
750
				openFolderInNewWindow = true; // any other window to open must open in new window then
751 752
			}
		}
E
Erich Gamma 已提交
753

754
		return arrays.distinct(usedWindows);
E
Erich Gamma 已提交
755 756
	}

757
	private doOpenFilesInExistingWindow(configuration: IOpenConfiguration, window: ICodeWindow, fileInputs?: IFileInputs): ICodeWindow {
758 759
		window.focus(); // make sure window has focus

760
		const params: { filesToOpenOrCreate?: IPath[], filesToDiff?: IPath[], filesToWait?: IPathsToWaitFor, termProgram?: string } = {};
B
Benjamin Pasero 已提交
761
		if (fileInputs) {
762
			params.filesToOpenOrCreate = fileInputs.filesToOpenOrCreate;
B
Benjamin Pasero 已提交
763 764 765 766 767 768 769 770 771
			params.filesToDiff = fileInputs.filesToDiff;
			params.filesToWait = fileInputs.filesToWait;
		}

		if (configuration.userEnv) {
			params.termProgram = configuration.userEnv['TERM_PROGRAM'];
		}

		window.sendWhenReady('vscode:openFiles', params);
B
Benjamin Pasero 已提交
772 773

		return window;
774 775
	}

776
	private doAddFoldersToExistingWindow(window: ICodeWindow, foldersToAdd: URI[]): ICodeWindow {
777 778
		window.focus(); // make sure window has focus

B
Benjamin Pasero 已提交
779 780 781
		const request: IAddFoldersRequest = { foldersToAdd };

		window.sendWhenReady('vscode:addFolders', request);
782 783 784 785

		return window;
	}

M
Matt Bierner 已提交
786
	private doOpenFolderOrWorkspace(openConfig: IOpenConfiguration, folderOrWorkspace: IPathToOpen, forceNewWindow: boolean, fileInputs: IFileInputs | undefined, windowToUse?: ICodeWindow): ICodeWindow {
B
Benjamin Pasero 已提交
787 788 789 790
		if (!forceNewWindow && !windowToUse && typeof openConfig.contextWindowId === 'number') {
			windowToUse = this.getWindowById(openConfig.contextWindowId); // fix for https://github.com/Microsoft/vscode/issues/49587
		}

791 792 793 794
		const browserWindow = this.openInBrowserWindow({
			userEnv: openConfig.userEnv,
			cli: openConfig.cli,
			initialStartup: openConfig.initialStartup,
795
			workspace: folderOrWorkspace.workspace,
796
			folderUri: folderOrWorkspace.folderUri,
797
			fileInputs,
M
Martin Aeschlimann 已提交
798
			remoteAuthority: folderOrWorkspace.remoteAuthority,
B
Benjamin Pasero 已提交
799
			forceNewWindow,
800
			forceNewTabbedWindow: openConfig.forceNewTabbedWindow,
801
			windowToUse
802 803 804 805 806
		});

		return browserWindow;
	}

B
Benjamin Pasero 已提交
807 808
	private getPathsToOpen(openConfig: IOpenConfiguration): IPathToOpen[] {
		let windowsToOpen: IPathToOpen[];
809
		let isCommandLineOrAPICall = false;
E
Erich Gamma 已提交
810

811
		// Extract paths: from API
S
Sandeep Somavarapu 已提交
812
		if (openConfig.urisToOpen && openConfig.urisToOpen.length > 0) {
813
			windowsToOpen = this.doExtractPathsFromAPI(openConfig);
814
			isCommandLineOrAPICall = true;
E
Erich Gamma 已提交
815 816
		}

B
Benjamin Pasero 已提交
817 818
		// Check for force empty
		else if (openConfig.forceEmpty) {
819
			windowsToOpen = [Object.create(null)];
E
Erich Gamma 已提交
820 821
		}

822
		// Extract paths: from CLI
823
		else if (openConfig.cli._.length || openConfig.cli['folder-uri'] || openConfig.cli['file-uri']) {
824
			windowsToOpen = this.doExtractPathsFromCLI(openConfig.cli);
825
			isCommandLineOrAPICall = true;
B
Benjamin Pasero 已提交
826 827
		}

828
		// Extract windows: from previous session
B
Benjamin Pasero 已提交
829
		else {
830
			windowsToOpen = this.doGetWindowsFromLastSession();
B
Benjamin Pasero 已提交
831 832
		}

833 834
		// Convert multiple folders into workspace (if opened via API or CLI)
		// This will ensure to open these folders in one window instead of multiple
835 836
		// If we are in addMode, we should not do this because in that case all
		// folders should be added to the existing window.
837
		if (!openConfig.addMode && isCommandLineOrAPICall) {
838
			const foldersToOpen = windowsToOpen.filter(path => !!path.folderUri);
839
			if (foldersToOpen.length > 1) {
840
				const remoteAuthority = foldersToOpen[0].remoteAuthority;
841 842 843 844 845 846 847
				if (foldersToOpen.every(f => f.remoteAuthority === remoteAuthority)) { // only if all folder have the same authority
					const workspace = this.workspacesMainService.createUntitledWorkspaceSync(foldersToOpen.map(folder => ({ uri: folder.folderUri! })));

					// Add workspace and remove folders thereby
					windowsToOpen.push({ workspace, remoteAuthority });
					windowsToOpen = windowsToOpen.filter(path => !path.folderUri);
				}
848 849 850
			}
		}

851
		return windowsToOpen;
E
Erich Gamma 已提交
852 853
	}

854
	private doExtractPathsFromAPI(openConfig: IOpenConfiguration): IPathToOpen[] {
M
Matt Bierner 已提交
855
		const pathsToOpen: IPathToOpen[] = [];
856
		const parseOptions: IPathParseOptions = { gotoLineMode: openConfig.gotoLineMode };
M
Matt Bierner 已提交
857
		for (const pathToOpen of openConfig.urisToOpen || []) {
M
Martin Aeschlimann 已提交
858 859 860 861
			if (!pathToOpen) {
				continue;
			}

M
Martin Aeschlimann 已提交
862
			const path = this.parseUri(pathToOpen, parseOptions);
M
Martin Aeschlimann 已提交
863
			if (path) {
M
Martin Aeschlimann 已提交
864
				path.label = pathToOpen.label;
M
Martin Aeschlimann 已提交
865 866
				pathsToOpen.push(path);
			} else {
867 868
				const uri = this.resourceFromURIToOpen(pathToOpen);

B
Benjamin Pasero 已提交
869
				// Warn about the invalid URI or path
M
Martin Aeschlimann 已提交
870
				let message, detail;
M
Martin Aeschlimann 已提交
871
				if (uri.scheme === Schemas.file) {
M
Martin Aeschlimann 已提交
872
					message = localize('pathNotExistTitle', "Path does not exist");
M
Martin Aeschlimann 已提交
873
					detail = localize('pathNotExistDetail', "The path '{0}' does not seem to exist anymore on disk.", uri.fsPath);
M
Martin Aeschlimann 已提交
874 875
				} else {
					message = localize('uriInvalidTitle', "URI can not be opened");
M
Martin Aeschlimann 已提交
876
					detail = localize('uriInvalidDetail', "The URI '{0}' is not valid and can not be opened.", uri.toString());
M
Martin Aeschlimann 已提交
877
				}
878
				const options: MessageBoxOptions = {
879 880
					title: product.nameLong,
					type: 'info',
881
					buttons: [localize('ok', "OK")],
M
Martin Aeschlimann 已提交
882 883
					message,
					detail,
884 885 886
					noLink: true
				};

887
				this.dialogMainService.showMessageBox(options, withNullAsUndefined(BrowserWindow.getFocusedWindow()));
888
			}
M
Martin Aeschlimann 已提交
889
		}
890 891 892 893
		return pathsToOpen;
	}

	private doExtractPathsFromCLI(cli: ParsedArgs): IPath[] {
M
Matt Bierner 已提交
894
		const pathsToOpen: IPathToOpen[] = [];
R
Rob Lourens 已提交
895
		const parseOptions: IPathParseOptions = { ignoreFileNotFound: true, gotoLineMode: cli.goto, remoteAuthority: cli.remote || undefined };
896 897

		// folder uris
898 899 900 901 902 903 904 905 906
		const folderUris = cli['folder-uri'];
		if (folderUris) {
			for (let f of folderUris) {
				const folderUri = this.argToUri(f);
				if (folderUri) {
					const path = this.parseUri({ folderUri }, parseOptions);
					if (path) {
						pathsToOpen.push(path);
					}
M
Martin Aeschlimann 已提交
907
				}
M
Martin Aeschlimann 已提交
908
			}
909 910
		}

911

912
		// file uris
913 914 915 916 917 918 919 920 921
		const fileUris = cli['file-uri'];
		if (fileUris) {
			for (let f of fileUris) {
				const fileUri = this.argToUri(f);
				if (fileUri) {
					const path = this.parseUri(hasWorkspaceFileExtension(f) ? { workspaceUri: fileUri } : { fileUri }, parseOptions);
					if (path) {
						pathsToOpen.push(path);
					}
M
Martin Aeschlimann 已提交
922
				}
M
Martin Aeschlimann 已提交
923
			}
924 925 926
		}

		// folder or file paths
927
		const cliArgs = cli._;
M
Martin Aeschlimann 已提交
928 929 930 931 932
		for (let cliArg of cliArgs) {
			const path = this.parsePath(cliArg, parseOptions);
			if (path) {
				pathsToOpen.push(path);
			}
933 934
		}

M
Martin Aeschlimann 已提交
935
		if (pathsToOpen.length) {
936
			return pathsToOpen;
B
Benjamin Pasero 已提交
937 938 939 940 941 942
		}

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

B
Benjamin Pasero 已提交
943
	private doGetWindowsFromLastSession(): IPathToOpen[] {
944
		const restoreWindows = this.getRestoreWindowsSetting();
B
Benjamin Pasero 已提交
945

946
		switch (restoreWindows) {
B
Benjamin Pasero 已提交
947

948
			// none: we always open an empty window
949 950
			case 'none':
				return [Object.create(null)];
B
Benjamin Pasero 已提交
951

952
			// one: restore last opened workspace/folder or empty window
953 954
			// all: restore all windows
			// folders: restore last opened folders only
955
			case 'one':
956 957
			case 'all':
			case 'folders':
958 959 960
				const openedWindows: IWindowState[] = [];
				if (restoreWindows !== 'one') {
					openedWindows.push(...this.windowsState.openedWindows);
961
				}
962 963
				if (this.windowsState.lastActiveWindow) {
					openedWindows.push(this.windowsState.lastActiveWindow);
964
				}
965

966 967 968
				const windowsToOpen: IPathToOpen[] = [];
				for (const openedWindow of openedWindows) {
					if (openedWindow.workspace) { // Workspaces
M
Martin Aeschlimann 已提交
969
						const pathToOpen = this.parseUri({ workspaceUri: openedWindow.workspace.configPath }, { remoteAuthority: openedWindow.remoteAuthority });
B
Benjamin Pasero 已提交
970
						if (pathToOpen?.workspace) {
971 972 973
							windowsToOpen.push(pathToOpen);
						}
					} else if (openedWindow.folderUri) { // Folders
M
Martin Aeschlimann 已提交
974
						const pathToOpen = this.parseUri({ folderUri: openedWindow.folderUri }, { remoteAuthority: openedWindow.remoteAuthority });
B
Benjamin Pasero 已提交
975
						if (pathToOpen?.folderUri) {
976 977
							windowsToOpen.push(pathToOpen);
						}
978
					} else if (restoreWindows !== 'folders' && openedWindow.backupPath && !openedWindow.remoteAuthority) { // Local windows that were empty. Empty windows with backups will always be restored in open()
M
Martin Aeschlimann 已提交
979
						windowsToOpen.push({ backupPath: openedWindow.backupPath, remoteAuthority: openedWindow.remoteAuthority });
980
					}
981 982 983 984 985 986 987
				}

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

				break;
B
Benjamin Pasero 已提交
988
		}
E
Erich Gamma 已提交
989

990
		// Always fallback to empty window
B
Benjamin Pasero 已提交
991
		return [Object.create(null)];
E
Erich Gamma 已提交
992 993
	}

994 995
	private getRestoreWindowsSetting(): RestoreWindowsSetting {
		let restoreWindows: RestoreWindowsSetting;
996
		if (this.lifecycleMainService.wasRestarted) {
997 998
			restoreWindows = 'all'; // always reopen all windows when an update was applied
		} else {
999
			const windowConfig = this.configurationService.getValue<IWindowSettings>('window');
B
Benjamin Pasero 已提交
1000
			restoreWindows = windowConfig?.restoreWindows || 'one';
1001 1002 1003 1004 1005 1006 1007 1008 1009

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

		return restoreWindows;
	}

1010
	private argToUri(arg: string): URI | undefined {
M
Martin Aeschlimann 已提交
1011
		try {
1012
			const uri = URI.parse(arg);
M
Martin Aeschlimann 已提交
1013
			if (!uri.scheme) {
M
Martin Aeschlimann 已提交
1014
				this.logService.error(`Invalid URI input string, scheme missing: ${arg}`);
1015
				return undefined;
M
Martin Aeschlimann 已提交
1016
			}
1017

M
Martin Aeschlimann 已提交
1018 1019
			return uri;
		} catch (e) {
M
Martin Aeschlimann 已提交
1020
			this.logService.error(`Invalid URI input string: ${arg}, ${e.message}`);
1021
		}
1022

1023
		return undefined;
1024 1025
	}

1026 1027
	private parseUri(toOpen: IWindowOpenable, options: IPathParseOptions = {}): IPathToOpen | undefined {
		if (!toOpen) {
1028
			return undefined;
1029
		}
1030

1031
		let uri = this.resourceFromURIToOpen(toOpen);
M
Martin Aeschlimann 已提交
1032
		if (uri.scheme === Schemas.file) {
1033
			return this.parsePath(uri.fsPath, options, isFileToOpen(toOpen));
1034
		}
M
Martin Aeschlimann 已提交
1035 1036

		// open remote if either specified in the cli or if it's a remotehost URI
1037
		const remoteAuthority = options.remoteAuthority || getRemoteAuthority(uri);
M
Martin Aeschlimann 已提交
1038

1039 1040
		// normalize URI
		uri = normalizePath(uri);
1041 1042

		// remove trailing slash
1043 1044
		if (hasTrailingPathSeparator(uri)) {
			uri = removeTrailingPathSeparator(uri);
1045
		}
1046

1047 1048
		// File
		if (isFileToOpen(toOpen)) {
1049
			if (options.gotoLineMode) {
1050 1051 1052 1053
				const parsedPath = parseLineAndColumnAware(uri.path);
				return {
					fileUri: uri.with({ path: parsedPath.path }),
					lineNumber: parsedPath.line,
M
Martin Aeschlimann 已提交
1054 1055
					columnNumber: parsedPath.column,
					remoteAuthority
1056 1057
				};
			}
1058

1059
			return {
M
Martin Aeschlimann 已提交
1060 1061
				fileUri: uri,
				remoteAuthority
1062
			};
1063 1064 1065 1066
		}

		// Workspace
		else if (isWorkspaceToOpen(toOpen)) {
M
Martin Aeschlimann 已提交
1067 1068 1069 1070
			return {
				workspace: getWorkspaceIdentifier(uri),
				remoteAuthority
			};
1071
		}
1072 1073

		// Folder
1074
		return {
M
Martin Aeschlimann 已提交
1075 1076
			folderUri: uri,
			remoteAuthority
1077 1078 1079
		};
	}

1080 1081 1082 1083 1084 1085 1086 1087 1088 1089 1090 1091
	private resourceFromURIToOpen(openable: IWindowOpenable): URI {
		if (isWorkspaceToOpen(openable)) {
			return openable.workspaceUri;
		}

		if (isFolderToOpen(openable)) {
			return openable.folderUri;
		}

		return openable.fileUri;
	}

M
Martin Aeschlimann 已提交
1092
	private parsePath(anyPath: string, options: IPathParseOptions, forceOpenWorkspaceAsFile?: boolean): IPathToOpen | undefined {
E
Erich Gamma 已提交
1093
		if (!anyPath) {
1094
			return undefined;
E
Erich Gamma 已提交
1095 1096
		}

1097
		let lineNumber, columnNumber: number | undefined;
1098

1099
		if (options.gotoLineMode) {
1100 1101 1102
			const parsedPath = parseLineAndColumnAware(anyPath);
			lineNumber = parsedPath.line;
			columnNumber = parsedPath.column;
1103

E
Erich Gamma 已提交
1104 1105 1106
			anyPath = parsedPath.path;
		}

1107
		// open remote if either specified in the cli even if it is a local file.
1108
		const remoteAuthority = options.remoteAuthority;
M
Martin Aeschlimann 已提交
1109

1110 1111 1112 1113 1114 1115 1116 1117 1118 1119 1120 1121 1122 1123 1124 1125 1126 1127 1128 1129 1130 1131 1132 1133 1134
		if (remoteAuthority) {
			// assume it's a folder or workspace file

			const first = anyPath.charCodeAt(0);
			// make absolute
			if (first !== CharCode.Slash) {
				if (isWindowsDriveLetter(first) && anyPath.charCodeAt(anyPath.charCodeAt(1)) === CharCode.Colon) {
					anyPath = toSlashes(anyPath);
				}
				anyPath = '/' + anyPath;
			}

			const uri = URI.from({ scheme: Schemas.vscodeRemote, authority: remoteAuthority, path: anyPath });

			if (hasWorkspaceFileExtension(anyPath)) {
				if (forceOpenWorkspaceAsFile) {
					return { fileUri: uri, remoteAuthority };
				}
				return { workspace: getWorkspaceIdentifier(uri), remoteAuthority };
			}
			return { folderUri: uri, remoteAuthority };
		}

		let candidate = normalize(anyPath);

E
Erich Gamma 已提交
1135
		try {
1136

B
Benjamin Pasero 已提交
1137
			const candidateStat = fs.statSync(candidate);
1138 1139 1140 1141 1142 1143 1144 1145 1146 1147 1148
			if (candidateStat.isFile()) {

				// Workspace (unless disabled via flag)
				if (!forceOpenWorkspaceAsFile) {
					const workspace = this.workspacesMainService.resolveLocalWorkspaceSync(URI.file(candidate));
					if (workspace) {
						return {
							workspace: { id: workspace.id, configPath: workspace.configPath },
							remoteAuthority: workspace.remoteAuthority,
							exists: true
						};
1149
					}
1150 1151
				}

1152 1153 1154 1155 1156 1157 1158 1159 1160 1161 1162 1163 1164 1165 1166 1167 1168 1169 1170
				// File
				return {
					fileUri: URI.file(candidate),
					lineNumber,
					columnNumber,
					remoteAuthority,
					exists: true
				};
			}

			// Folder (we check for isDirectory() because e.g. paths like /dev/null
			// are neither file nor folder but some external tools might pass them
			// over to us)
			else if (candidateStat.isDirectory()) {
				return {
					folderUri: URI.file(candidate),
					remoteAuthority,
					exists: true
				};
E
Erich Gamma 已提交
1171 1172
			}
		} catch (error) {
S
Sandeep Somavarapu 已提交
1173
			const fileUri = URI.file(candidate);
1174
			this.workspacesHistoryMainService.removeFromRecentlyOpened([fileUri]); // since file does not seem to exist anymore, remove from recent
1175 1176

			// assume this is a file that does not yet exist
B
Benjamin Pasero 已提交
1177
			if (options?.ignoreFileNotFound) {
1178 1179 1180 1181 1182
				return {
					fileUri,
					remoteAuthority,
					exists: false
				};
E
Erich Gamma 已提交
1183 1184 1185
			}
		}

1186
		return undefined;
E
Erich Gamma 已提交
1187 1188
	}

B
Benjamin Pasero 已提交
1189 1190 1191
	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
1192
		const windowConfig = this.configurationService.getValue<IWindowSettings>('window');
B
Benjamin Pasero 已提交
1193 1194
		const openFolderInNewWindowConfig = windowConfig?.openFoldersInNewWindow || 'default' /* default */;
		const openFilesInNewWindowConfig = windowConfig?.openFilesInNewWindow || 'off' /* default */;
1195

B
Benjamin Pasero 已提交
1196
		let openFolderInNewWindow = (openConfig.preferNewWindow || openConfig.forceNewWindow) && !openConfig.forceReuseWindow;
1197 1198
		if (!openConfig.forceNewWindow && !openConfig.forceReuseWindow && (openFolderInNewWindowConfig === 'on' || openFolderInNewWindowConfig === 'off')) {
			openFolderInNewWindow = (openFolderInNewWindowConfig === 'on');
B
Benjamin Pasero 已提交
1199 1200 1201
		}

		// 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)
M
Matt Bierner 已提交
1202
		let openFilesInNewWindow: boolean = false;
B
Benjamin Pasero 已提交
1203
		if (openConfig.forceNewWindow || openConfig.forceReuseWindow) {
M
Matt Bierner 已提交
1204
			openFilesInNewWindow = !!openConfig.forceNewWindow && !openConfig.forceReuseWindow;
B
Benjamin Pasero 已提交
1205
		} else {
1206 1207 1208 1209 1210 1211 1212 1213

			// macOS: by default we open files in a new window if this is triggered via DOCK context
			if (isMacintosh) {
				if (openConfig.context === OpenContext.DOCK) {
					openFilesInNewWindow = true;
				}
			}

1214 1215
			// Linux/Windows: by default we open files in the new window unless triggered via DIALOG / MENU context
			// or from the integrated terminal where we assume the user prefers to open in the current window
1216
			else {
1217
				if (openConfig.context !== OpenContext.DIALOG && openConfig.context !== OpenContext.MENU && !(openConfig.userEnv && openConfig.userEnv['TERM_PROGRAM'] === 'vscode')) {
1218 1219
					openFilesInNewWindow = true;
				}
B
Benjamin Pasero 已提交
1220 1221
			}

1222
			// finally check for overrides of default
1223 1224
			if (!openConfig.cli.extensionDevelopmentPath && (openFilesInNewWindowConfig === 'on' || openFilesInNewWindowConfig === 'off')) {
				openFilesInNewWindow = (openFilesInNewWindowConfig === 'on');
B
Benjamin Pasero 已提交
1225 1226 1227
			}
		}

M
Matt Bierner 已提交
1228
		return { openFolderInNewWindow: !!openFolderInNewWindow, openFilesInNewWindow };
B
Benjamin Pasero 已提交
1229 1230
	}

1231
	openExtensionDevelopmentHostWindow(extensionDevelopmentPath: string[], openConfig: IOpenConfiguration): ICodeWindow[] {
E
Erich Gamma 已提交
1232

B
Benjamin Pasero 已提交
1233 1234 1235
		// 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.
1236
		const existingWindow = findWindowOnExtensionDevelopmentPath(WindowsMainService.WINDOWS, extensionDevelopmentPath);
1237
		if (existingWindow) {
1238
			this.lifecycleMainService.reload(existingWindow, openConfig.cli);
1239
			existingWindow.focus(); // make sure it gets focus and is restored
E
Erich Gamma 已提交
1240

1241
			return [existingWindow];
B
Benjamin Pasero 已提交
1242
		}
1243 1244
		let folderUris = openConfig.cli['folder-uri'] || [];
		let fileUris = openConfig.cli['file-uri'] || [];
1245
		let cliArgs = openConfig.cli._;
E
Erich Gamma 已提交
1246

1247
		// Fill in previously opened workspace unless an explicit path is provided and we are not unit testing
1248
		if (!cliArgs.length && !folderUris.length && !fileUris.length && !openConfig.cli.extensionTestsPath) {
1249
			const extensionDevelopmentWindowState = this.windowsState.lastPluginDevelopmentHostWindow;
1250
			const workspaceToOpen = extensionDevelopmentWindowState && (extensionDevelopmentWindowState.workspace || extensionDevelopmentWindowState.folderUri);
1251
			if (workspaceToOpen) {
1252
				if (isSingleFolderWorkspaceIdentifier(workspaceToOpen)) {
1253
					if (workspaceToOpen.scheme === Schemas.file) {
1254
						cliArgs = [workspaceToOpen.fsPath];
1255
					} else {
1256
						folderUris = [workspaceToOpen.toString()];
1257 1258
					}
				} else {
M
Martin Aeschlimann 已提交
1259
					if (workspaceToOpen.configPath.scheme === Schemas.file) {
1260
						cliArgs = [originalFSPath(workspaceToOpen.configPath)];
M
Martin Aeschlimann 已提交
1261
					} else {
1262
						fileUris = [workspaceToOpen.configPath.toString()];
M
Martin Aeschlimann 已提交
1263
					}
1264
				}
E
Erich Gamma 已提交
1265 1266 1267
			}
		}

1268 1269 1270 1271 1272
		let authority = '';
		for (let p of extensionDevelopmentPath) {
			if (p.match(/^[a-zA-Z][a-zA-Z0-9\+\-\.]+:/)) {
				const url = URI.parse(p);
				if (url.scheme === Schemas.vscodeRemote) {
1273
					if (authority) {
1274 1275
						if (url.authority !== authority) {
							this.logService.error('more than one extension development path authority');
1276 1277
						}
					} else {
1278
						authority = url.authority;
1279 1280 1281
					}
				}
			}
1282 1283 1284 1285 1286 1287 1288 1289
		}

		// Make sure that we do not try to open:
		// - a workspace or folder that is already opened
		// - a workspace or file that has a different authority as the extension development.

		cliArgs = cliArgs.filter(path => {
			const uri = URI.file(path);
1290
			if (!!findWindowOnWorkspaceOrFolderUri(WindowsMainService.WINDOWS, uri)) {
1291
				return false;
1292
			}
1293 1294
			return uri.authority === authority;
		});
1295

1296 1297
		folderUris = folderUris.filter(uri => {
			const u = this.argToUri(uri);
1298
			if (!!findWindowOnWorkspaceOrFolderUri(WindowsMainService.WINDOWS, u)) {
1299 1300 1301 1302 1303 1304 1305
				return false;
			}
			return u ? u.authority === authority : false;
		});

		fileUris = fileUris.filter(uri => {
			const u = this.argToUri(uri);
1306
			if (!!findWindowOnWorkspaceOrFolderUri(WindowsMainService.WINDOWS, u)) {
1307 1308 1309 1310 1311 1312 1313 1314 1315 1316
				return false;
			}
			return u ? u.authority === authority : false;
		});

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

		// if there are no files or folders cli args left, use the "remote" cli argument
1317 1318 1319
		const noFilesOrFolders = !cliArgs.length && !folderUris.length && !fileUris.length;
		if (noFilesOrFolders && authority) {
			openConfig.cli.remote = authority;
1320 1321
		}

B
Benjamin Pasero 已提交
1322
		// Open it
M
Martin Aeschlimann 已提交
1323 1324 1325 1326
		const openArgs: IOpenConfiguration = {
			context: openConfig.context,
			cli: openConfig.cli,
			forceNewWindow: true,
1327
			forceEmpty: noFilesOrFolders,
M
Martin Aeschlimann 已提交
1328 1329 1330 1331
			userEnv: openConfig.userEnv,
			noRecentEntry: true,
			waitMarkerFileURI: openConfig.waitMarkerFileURI
		};
1332 1333

		return this.open(openArgs);
E
Erich Gamma 已提交
1334 1335
	}

1336
	private openInBrowserWindow(options: IOpenBrowserWindowOptions): ICodeWindow {
1337

B
Benjamin Pasero 已提交
1338 1339 1340
		// Build IWindowConfiguration from config and options
		const configuration: IWindowConfiguration = mixin({}, options.cli); // inherit all properties from CLI
		configuration.appRoot = this.environmentService.appRoot;
1341
		configuration.machineId = this.machineId;
1342
		configuration.nodeCachedDataDir = this.environmentService.nodeCachedDataDir;
1343
		configuration.mainPid = process.pid;
B
Benjamin Pasero 已提交
1344 1345 1346
		configuration.execPath = process.execPath;
		configuration.userEnv = assign({}, this.initialUserEnv, options.userEnv || {});
		configuration.isInitialStartup = options.initialStartup;
1347
		configuration.workspace = options.workspace;
1348
		configuration.folderUri = options.folderUri;
M
Martin Aeschlimann 已提交
1349
		configuration.remoteAuthority = options.remoteAuthority;
1350 1351 1352

		const fileInputs = options.fileInputs;
		if (fileInputs) {
1353
			configuration.filesToOpenOrCreate = fileInputs.filesToOpenOrCreate;
1354 1355 1356
			configuration.filesToDiff = fileInputs.filesToDiff;
			configuration.filesToWait = fileInputs.filesToWait;
		}
B
Benjamin Pasero 已提交
1357

1358
		// if we know the backup folder upfront (for empty windows to restore), we can set it
1359
		// directly here which helps for restoring UI state associated with that window.
B
Benjamin Pasero 已提交
1360
		// For all other cases we first call into registerEmptyWindowBackupSync() to set it before
1361
		// loading the window.
1362
		if (options.emptyWindowBackupInfo) {
S
Sandeep Somavarapu 已提交
1363
			configuration.backupPath = join(this.environmentService.backupHome.fsPath, options.emptyWindowBackupInfo.backupFolder);
1364 1365
		}

1366
		let window: ICodeWindow | undefined;
1367
		if (!options.forceNewWindow && !options.forceNewTabbedWindow) {
1368 1369 1370
			window = options.windowToUse || this.getLastActiveWindow();
			if (window) {
				window.focus();
E
Erich Gamma 已提交
1371 1372 1373 1374
			}
		}

		// New window
1375
		if (!window) {
1376
			const windowConfig = this.configurationService.getValue<IWindowSettings>('window');
1377 1378 1379 1380 1381
			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) {
B
Benjamin Pasero 已提交
1382
				allowFullscreen = (windowConfig?.newWindowDimensions && ['fullscreen', 'inherit'].indexOf(windowConfig.newWindowDimensions) >= 0);
1383 1384 1385 1386
			}

			// Window state is from a previous session: only allow fullscreen when we got updated or user wants to restore
			else {
B
Benjamin Pasero 已提交
1387
				allowFullscreen = this.lifecycleMainService.wasRestarted || windowConfig?.restoreFullscreen;
B
Benjamin Pasero 已提交
1388 1389 1390 1391 1392 1393 1394 1395 1396

				if (allowFullscreen && isMacintosh && WindowsMainService.WINDOWS.some(win => win.isFullScreen)) {
					// macOS: Electron does not allow to restore multiple windows in
					// fullscreen. As such, if we already restored a window in that
					// state, we cannot allow more fullscreen windows. See
					// https://github.com/microsoft/vscode/issues/41691 and
					// https://github.com/electron/electron/issues/13077
					allowFullscreen = false;
				}
1397 1398 1399 1400 1401
			}

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

1403
			// Create the window
1404
			const createdWindow = window = this.instantiationService.createInstance(CodeWindow, {
1405
				state,
1406
				extensionDevelopmentPath: configuration.extensionDevelopmentPath,
1407
				isExtensionTestHost: !!configuration.extensionTestsPath
1408
			});
1409

1410 1411 1412 1413 1414 1415 1416 1417
			// Add as window tab if configured (macOS only)
			if (options.forceNewTabbedWindow) {
				const activeWindow = this.getLastActiveWindow();
				if (activeWindow) {
					activeWindow.addTabbedWindow(window);
				}
			}

B
Benjamin Pasero 已提交
1418
			// Add to our list of windows
1419
			WindowsMainService.WINDOWS.push(window);
E
Erich Gamma 已提交
1420

B
Benjamin Pasero 已提交
1421
			// Indicate number change via event
1422
			this._onWindowsCountChanged.fire({ oldCount: WindowsMainService.WINDOWS.length - 1, newCount: WindowsMainService.WINDOWS.length });
B
Benjamin Pasero 已提交
1423

E
Erich Gamma 已提交
1424
			// Window Events
1425 1426
			once(window.onClose)(() => this.onWindowClosed(createdWindow));
			once(window.onDestroy)(() => this.onBeforeWindowClose(createdWindow)); // try to save state before destroy because close will not fire
1427
			window.win.webContents.removeAllListeners('devtools-reload-page'); // remove built in listener so we can handle this on our own
1428
			window.win.webContents.on('devtools-reload-page', () => this.lifecycleMainService.reload(createdWindow));
E
Erich Gamma 已提交
1429 1430

			// Lifecycle
1431
			(this.lifecycleMainService as LifecycleMainService).registerWindow(window);
E
Erich Gamma 已提交
1432 1433 1434 1435 1436 1437
		}

		// Existing window
		else {

			// Some configuration things get inherited if the window is being reused and we are
B
Benjamin Pasero 已提交
1438
			// in extension development host mode. These options are all development related.
1439
			const currentWindowConfig = window.config;
A
Alex Dima 已提交
1440 1441
			if (!configuration.extensionDevelopmentPath && currentWindowConfig && !!currentWindowConfig.extensionDevelopmentPath) {
				configuration.extensionDevelopmentPath = currentWindowConfig.extensionDevelopmentPath;
B
Benjamin Pasero 已提交
1442
				configuration.verbose = currentWindowConfig.verbose;
1443
				configuration['inspect-brk-extensions'] = currentWindowConfig['inspect-brk-extensions'];
1444
				configuration.debugId = currentWindowConfig.debugId;
1445
				configuration['inspect-extensions'] = currentWindowConfig['inspect-extensions'];
1446
				configuration['extensions-dir'] = currentWindowConfig['extensions-dir'];
E
Erich Gamma 已提交
1447 1448 1449
			}
		}

1450 1451 1452 1453
		// If the window was already loaded, make sure to unload it
		// first and only load the new configuration if that was
		// not vetoed
		if (window.isReady) {
1454
			this.lifecycleMainService.unload(window, UnloadReason.LOAD).then(veto => {
1455
				if (!veto) {
M
Matt Bierner 已提交
1456
					this.doOpenInBrowserWindow(window!, configuration, options);
B
Benjamin Pasero 已提交
1457
				}
1458 1459 1460 1461 1462 1463 1464
			});
		} else {
			this.doOpenInBrowserWindow(window, configuration, options);
		}

		return window;
	}
B
Benjamin Pasero 已提交
1465

1466
	private doOpenInBrowserWindow(window: ICodeWindow, configuration: IWindowConfiguration, options: IOpenBrowserWindowOptions): void {
1467

1468 1469 1470
		// Register window for backups
		if (!configuration.extensionDevelopmentPath) {
			if (configuration.workspace) {
1471
				configuration.backupPath = this.backupMainService.registerWorkspaceBackupSync({ workspace: configuration.workspace, remoteAuthority: configuration.remoteAuthority });
1472 1473 1474 1475
			} else if (configuration.folderUri) {
				configuration.backupPath = this.backupMainService.registerFolderBackupSync(configuration.folderUri);
			} else {
				const backupFolder = options.emptyWindowBackupInfo && options.emptyWindowBackupInfo.backupFolder;
1476
				configuration.backupPath = this.backupMainService.registerEmptyWindowBackupSync(backupFolder, configuration.remoteAuthority);
E
Erich Gamma 已提交
1477
			}
1478
		}
1479

1480 1481
		// Load it
		window.load(configuration);
E
Erich Gamma 已提交
1482 1483
	}

1484
	private getNewWindowState(configuration: IWindowConfiguration): INewWindowState {
1485
		const lastActive = this.getLastActiveWindow();
E
Erich Gamma 已提交
1486

1487 1488
		// Restore state unless we are running extension tests
		if (!configuration.extensionTestsPath) {
E
Erich Gamma 已提交
1489

1490 1491 1492
			// extension development host Window - load from stored settings if any
			if (!!configuration.extensionDevelopmentPath && this.windowsState.lastPluginDevelopmentHostWindow) {
				return this.windowsState.lastPluginDevelopmentHostWindow.uiState;
1493 1494
			}

1495
			// Known Workspace - load from stored settings
M
Matt Bierner 已提交
1496 1497 1498
			const workspace = configuration.workspace;
			if (workspace) {
				const stateForWorkspace = this.windowsState.openedWindows.filter(o => o.workspace && o.workspace.id === workspace.id).map(o => o.uiState);
1499 1500 1501 1502 1503 1504
				if (stateForWorkspace.length) {
					return stateForWorkspace[0];
				}
			}

			// Known Folder - load from stored settings
1505
			if (configuration.folderUri) {
1506
				const stateForFolder = this.windowsState.openedWindows.filter(o => o.folderUri && isEqual(o.folderUri, configuration.folderUri)).map(o => o.uiState);
1507 1508 1509
				if (stateForFolder.length) {
					return stateForFolder[0];
				}
1510 1511
			}

1512 1513 1514 1515 1516 1517
			// Empty windows with backups
			else if (configuration.backupPath) {
				const stateForEmptyWindow = this.windowsState.openedWindows.filter(o => o.backupPath === configuration.backupPath).map(o => o.uiState);
				if (stateForEmptyWindow.length) {
					return stateForEmptyWindow[0];
				}
E
Erich Gamma 已提交
1518 1519
			}

1520 1521 1522 1523 1524
			// First Window
			const lastActiveState = this.lastClosedWindowState || this.windowsState.lastActiveWindow;
			if (!lastActive && lastActiveState) {
				return lastActiveState.uiState;
			}
E
Erich Gamma 已提交
1525 1526 1527 1528 1529 1530 1531
		}

		//
		// 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
1532
		let displayToUse: Display | undefined;
B
Benjamin Pasero 已提交
1533
		const displays = screen.getAllDisplays();
E
Erich Gamma 已提交
1534 1535 1536 1537 1538 1539 1540 1541 1542 1543

		// 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 已提交
1544
			if (isMacintosh) {
B
Benjamin Pasero 已提交
1545
				const cursorPoint = screen.getCursorScreenPoint();
E
Erich Gamma 已提交
1546 1547 1548 1549 1550 1551 1552 1553
				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());
			}

1554
			// fallback to primary display or first display
E
Erich Gamma 已提交
1555
			if (!displayToUse) {
1556
				displayToUse = screen.getPrimaryDisplay() || displays[0];
E
Erich Gamma 已提交
1557 1558 1559
			}
		}

1560 1561 1562
		// Compute x/y based on display bounds
		// Note: important to use Math.round() because Electron does not seem to be too happy about
		// display coordinates that are not absolute numbers.
1563
		let state = defaultWindowState();
M
Matt Bierner 已提交
1564 1565
		state.x = Math.round(displayToUse.bounds.x + (displayToUse.bounds.width / 2) - (state.width! / 2));
		state.y = Math.round(displayToUse.bounds.y + (displayToUse.bounds.height / 2) - (state.height! / 2));
E
Erich Gamma 已提交
1566

1567
		// Check for newWindowDimensions setting and adjust accordingly
1568
		const windowConfig = this.configurationService.getValue<IWindowSettings>('window');
1569
		let ensureNoOverlap = true;
B
Benjamin Pasero 已提交
1570
		if (windowConfig?.newWindowDimensions) {
1571 1572 1573 1574 1575 1576 1577
			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 已提交
1578 1579 1580 1581 1582 1583 1584
				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;
				}

1585 1586 1587 1588 1589 1590 1591 1592
				ensureNoOverlap = false;
			}
		}

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

1593
		(state as INewWindowState).hasDefaultState = true; // flag as default state
1594

1595
		return state;
E
Erich Gamma 已提交
1596 1597
	}

J
Joao Moreno 已提交
1598
	private ensureNoOverlap(state: ISingleWindowState): ISingleWindowState {
1599
		if (WindowsMainService.WINDOWS.length === 0) {
E
Erich Gamma 已提交
1600 1601 1602
			return state;
		}

M
Matt Bierner 已提交
1603 1604 1605
		state.x = typeof state.x === 'number' ? state.x : 0;
		state.y = typeof state.y === 'number' ? state.y : 0;

1606
		const existingWindowBounds = WindowsMainService.WINDOWS.map(win => win.getBounds());
1607
		while (existingWindowBounds.some(b => b.x === state.x || b.y === state.y)) {
E
Erich Gamma 已提交
1608 1609 1610 1611 1612 1613 1614
			state.x += 30;
			state.y += 30;
		}

		return state;
	}

B
Benjamin Pasero 已提交
1615
	focusLastActive(cli: ParsedArgs, context: OpenContext): ICodeWindow {
B
Benjamin Pasero 已提交
1616
		const lastActive = this.getLastActiveWindow();
E
Erich Gamma 已提交
1617
		if (lastActive) {
B
Benjamin Pasero 已提交
1618
			lastActive.focus();
1619 1620

			return lastActive;
E
Erich Gamma 已提交
1621 1622
		}

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

M
Matt Bierner 已提交
1627
	getLastActiveWindow(): ICodeWindow | undefined {
1628
		return getLastActiveWindow(WindowsMainService.WINDOWS);
E
Erich Gamma 已提交
1629 1630
	}

1631
	private getLastActiveWindowForAuthority(remoteAuthority: string | undefined): ICodeWindow | undefined {
1632
		return getLastActiveWindow(WindowsMainService.WINDOWS.filter(window => window.remoteAuthority === remoteAuthority));
M
Martin Aeschlimann 已提交
1633 1634
	}

B
Benjamin Pasero 已提交
1635
	sendToFocused(channel: string, ...args: any[]): void {
E
Erich Gamma 已提交
1636 1637 1638
		const focusedWindow = this.getFocusedWindow() || this.getLastActiveWindow();

		if (focusedWindow) {
1639
			focusedWindow.sendWhenReady(channel, ...args);
E
Erich Gamma 已提交
1640 1641 1642
		}
	}

B
Benjamin Pasero 已提交
1643
	sendToAll(channel: string, payload?: any, windowIdsToIgnore?: number[]): void {
1644
		for (const window of WindowsMainService.WINDOWS) {
B
Benjamin Pasero 已提交
1645 1646
			if (windowIdsToIgnore && windowIdsToIgnore.indexOf(window.id) >= 0) {
				continue; // do not send if we are instructed to ignore it
E
Erich Gamma 已提交
1647 1648
			}

B
Benjamin Pasero 已提交
1649 1650
			window.sendWhenReady(channel, payload);
		}
E
Erich Gamma 已提交
1651 1652
	}

1653
	private getFocusedWindow(): ICodeWindow | undefined {
B
Benjamin Pasero 已提交
1654
		const win = BrowserWindow.getFocusedWindow();
E
Erich Gamma 已提交
1655 1656 1657 1658
		if (win) {
			return this.getWindowById(win.id);
		}

M
Matt Bierner 已提交
1659
		return undefined;
E
Erich Gamma 已提交
1660 1661
	}

M
Matt Bierner 已提交
1662
	getWindowById(windowId: number): ICodeWindow | undefined {
1663
		const res = WindowsMainService.WINDOWS.filter(window => window.id === windowId);
1664

M
Matt Bierner 已提交
1665
		return arrays.firstOrDefault(res);
E
Erich Gamma 已提交
1666 1667
	}

B
Benjamin Pasero 已提交
1668
	getWindows(): ICodeWindow[] {
1669
		return WindowsMainService.WINDOWS;
E
Erich Gamma 已提交
1670 1671
	}

B
Benjamin Pasero 已提交
1672
	getWindowCount(): number {
1673
		return WindowsMainService.WINDOWS.length;
E
Erich Gamma 已提交
1674 1675
	}

1676
	private onWindowClosed(win: ICodeWindow): void {
E
Erich Gamma 已提交
1677 1678

		// Remove from our list so that Electron can clean it up
1679 1680
		const index = WindowsMainService.WINDOWS.indexOf(win);
		WindowsMainService.WINDOWS.splice(index, 1);
E
Erich Gamma 已提交
1681 1682

		// Emit
1683
		this._onWindowsCountChanged.fire({ oldCount: WindowsMainService.WINDOWS.length + 1, newCount: WindowsMainService.WINDOWS.length });
1684
		this._onWindowClose.fire(win.id);
E
Erich Gamma 已提交
1685
	}
B
Benjamin Pasero 已提交
1686
}