windowsMainService.ts 63.6 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, posix } 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';
10
import { 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';
13
import { IEnvironmentMainService } from 'vs/platform/environment/electron-main/environmentMainService';
14
import { NativeParsedArgs } from 'vs/platform/environment/common/argv';
15
import { IStateService } from 'vs/platform/state/node/state';
16
import { CodeWindow, defaultWindowState } from 'vs/code/electron-main/window';
17
import { screen, BrowserWindow, MessageBoxOptions, Display, app } from 'electron';
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';
21 22
import { IWindowSettings, IPath, isFileToOpen, isWorkspaceToOpen, isFolderToOpen, IWindowOpenable, IOpenEmptyWindowOptions, IAddFoldersRequest, IPathsToWaitFor, INativeWindowConfiguration } from 'vs/platform/windows/common/windows';
import { getLastActiveWindow, findBestWindowOrFolderForFile, findWindowOnWorkspace, findWindowOnExtensionDevelopmentPath, findWindowOnWorkspaceOrFolderUri, OpenContext } from 'vs/platform/windows/node/window';
23
import { 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, IOpenEmptyConfiguration } 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 { normalizePath, originalFSPath, removeTrailingPathSeparator, extUriBiasedIgnorePathCase } 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
import { isWindowsDriveLetter, toSlashes, parseLineAndColumnAware } from 'vs/base/common/extpath';
41
import { CharCode } from 'vs/base/common/charCode';
42
import { getPathLabel } from 'vs/base/common/labels';
E
Erich Gamma 已提交
43

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

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

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

62
type RestoreWindowsSetting = 'preserve' | 'all' | 'folders' | 'one' | 'none';
63

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

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

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

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

B
Benjamin Pasero 已提交
75
	filesToOpen?: IFilesToOpen;
B
Benjamin Pasero 已提交
76 77

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

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

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

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

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

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

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

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

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

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

115 116 117 118 119 120 121 122 123 124 125 126 127 128
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 已提交
129 130 131

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

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 已提交
148 149 150

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

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

155
	declare readonly _serviceBrand: undefined;
E
Erich Gamma 已提交
156

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

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

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

164 165
	private shuttingDown = false;

B
Benjamin Pasero 已提交
166 167 168
	private readonly _onWindowOpened = this._register(new Emitter<ICodeWindow>());
	readonly onWindowOpened = this._onWindowOpened.event;

M
Matt Bierner 已提交
169
	private readonly _onWindowReady = this._register(new Emitter<ICodeWindow>());
170
	readonly onWindowReady = this._onWindowReady.event;
171

M
Matt Bierner 已提交
172
	private readonly _onWindowsCountChanged = this._register(new Emitter<IWindowsCountChangedEvent>());
173
	readonly onWindowsCountChanged = this._onWindowsCountChanged.event;
B
Benjamin Pasero 已提交
174

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

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

196 197
		this.lifecycleMainService.when(LifecycleMainPhase.Ready).then(() => this.registerListeners());
		this.lifecycleMainService.when(LifecycleMainPhase.AfterWindowOpen).then(() => this.installWindowsMutex());
198
	}
J
Joao Moreno 已提交
199

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

	private registerListeners(): void {
214

215 216 217 218 219 220 221 222 223
		// When a window looses focus, save all windows state. This allows to
		// prevent loss of window-state data when OS is restarted without properly
		// shutting down the application (https://github.com/microsoft/vscode/issues/87171)
		app.on('browser-window-blur', () => {
			if (!this.shuttingDown) {
				this.saveWindowsState();
			}
		});

224
		// Handle various lifecycle events around windows
225 226
		this.lifecycleMainService.onBeforeWindowClose(window => this.onBeforeWindowClose(window));
		this.lifecycleMainService.onBeforeShutdown(() => this.onBeforeShutdown());
227 228 229 230 231
		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 已提交
232
				this.lastClosedWindowState = undefined;
233 234
			}
		});
235 236 237 238 239

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

242
	// Note that onBeforeShutdown() and onBeforeWindowClose() are fired in different order depending on the OS:
243
	// - macOS: since the app will not quit when closing the last window, you will always first get
K
Keshav Bohra 已提交
244
	//          the onBeforeShutdown() event followed by N onBeforeWindowClose() events for each window
245 246
	// - 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()
247
	//          and then onBeforeShutdown(). Using the quit action however will first issue onBeforeShutdown()
248
	//          and then onBeforeWindowClose().
249
	//
K
Keshav Bohra 已提交
250
	// Here is the behavior on different OS depending on action taken (Electron 1.7.x):
251 252 253 254 255
	//
	// 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
256
	// - onBeforeShutdown(N): number of windows reported in this event handler
257 258 259
	// - onBeforeWindowClose(N, M): number of windows reported and quitRequested boolean in this event handler
	//
	// macOS
260 261 262
	// 	-     quit(1): onBeforeShutdown(1), onBeforeWindowClose(1, true)
	// 	-     quit(2): onBeforeShutdown(2), onBeforeWindowClose(2, true), onBeforeWindowClose(2, true)
	// 	-     quit(0): onBeforeShutdown(0)
263 264 265
	// 	-    close(1): onBeforeWindowClose(1, false)
	//
	// Windows
266 267
	// 	-     quit(1): onBeforeShutdown(1), onBeforeWindowClose(1, true)
	// 	-     quit(2): onBeforeShutdown(2), onBeforeWindowClose(2, true), onBeforeWindowClose(2, true)
268
	// 	-    close(1): onBeforeWindowClose(2, false)[not last window]
269 270
	// 	-    close(1): onBeforeWindowClose(1, false), onBeforeShutdown(0)[last window]
	// 	- closeAll(2): onBeforeWindowClose(2, false), onBeforeWindowClose(2, false), onBeforeShutdown(0)
271 272
	//
	// Linux
273 274
	// 	-     quit(1): onBeforeShutdown(1), onBeforeWindowClose(1, true)
	// 	-     quit(2): onBeforeShutdown(2), onBeforeWindowClose(2, true), onBeforeWindowClose(2, true)
275
	// 	-    close(1): onBeforeWindowClose(2, false)[not last window]
276 277
	// 	-    close(1): onBeforeWindowClose(1, false), onBeforeShutdown(0)[last window]
	// 	- closeAll(2): onBeforeWindowClose(2, false), onBeforeWindowClose(2, false), onBeforeShutdown(0)
278
	//
279
	private onBeforeShutdown(): void {
280 281 282 283 284 285
		this.shuttingDown = true;

		this.saveWindowsState();
	}

	private saveWindowsState(): void {
286
		const currentWindowsState: IWindowsState = {
287
			openedWindows: [],
288
			lastPluginDevelopmentHostWindow: this.windowsState.lastPluginDevelopmentHostWindow,
289
			lastActiveWindow: this.lastClosedWindowState
290 291 292 293 294 295
		};

		// 1.) Find a last active window (pick any other first window otherwise)
		if (!currentWindowsState.lastActiveWindow) {
			let activeWindow = this.getLastActiveWindow();
			if (!activeWindow || activeWindow.isExtensionDevelopmentHost) {
296
				activeWindow = WindowsMainService.WINDOWS.find(window => !window.isExtensionDevelopmentHost);
297
			}
E
Erich Gamma 已提交
298

299
			if (activeWindow) {
300
				currentWindowsState.lastActiveWindow = this.toWindowState(activeWindow);
E
Erich Gamma 已提交
301
			}
302 303 304
		}

		// 2.) Find extension host window
305
		const extensionHostWindow = WindowsMainService.WINDOWS.find(window => window.isExtensionDevelopmentHost && !window.isExtensionTestHost);
306
		if (extensionHostWindow) {
307
			currentWindowsState.lastPluginDevelopmentHostWindow = this.toWindowState(extensionHostWindow);
308
		}
E
Erich Gamma 已提交
309

310
		// 3.) All windows (except extension host) for N >= 2 to support `restoreWindows: all` or for auto update
311
		//
K
Keshav Bohra 已提交
312
		// Careful here: asking a window for its window state after it has been closed returns bogus values (width: 0, height: 0)
313 314 315
		// 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) {
316
			currentWindowsState.openedWindows = WindowsMainService.WINDOWS.filter(window => !window.isExtensionDevelopmentHost).map(window => this.toWindowState(window));
317
		}
E
Erich Gamma 已提交
318

319
		// Persist
B
Benjamin Pasero 已提交
320 321
		const state = getWindowsStateStoreData(currentWindowsState);
		this.stateService.setItem(WindowsMainService.windowsStateStorageKey, state);
322 323 324 325

		if (this.shuttingDown) {
			this.logService.trace('onBeforeShutdown', state);
		}
326
	}
327

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

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

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

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

371
	openEmptyWindow(openConfig: IOpenEmptyConfiguration, options?: IOpenEmptyWindowOptions): ICodeWindow[] {
372
		let cli = this.environmentService.args;
B
Benjamin Pasero 已提交
373
		const remote = options?.remoteAuthority;
374 375 376 377
		if (cli && (cli.remote !== remote)) {
			cli = { ...cli, remote };
		}

B
Benjamin Pasero 已提交
378
		const forceReuseWindow = options?.forceReuseWindow;
379 380
		const forceNewWindow = !forceReuseWindow;

381
		return this.open({ ...openConfig, cli, forceEmpty: true, forceNewWindow, forceReuseWindow });
382 383
	}

B
Benjamin Pasero 已提交
384
	open(openConfig: IOpenConfiguration): ICodeWindow[] {
385
		this.logService.trace('windowsManager#open');
386
		openConfig = this.validateOpenConfig(openConfig);
387

388 389 390
		const foldersToAdd: IFolderPathToOpen[] = [];
		const foldersToOpen: IFolderPathToOpen[] = [];
		const workspacesToOpen: IWorkspacePathToOpen[] = [];
B
Benjamin Pasero 已提交
391
		const workspacesToRestore: IWorkspacePathToOpen[] = [];
B
Benjamin Pasero 已提交
392 393 394
		const emptyToRestore: IEmptyWindowBackupInfo[] = [];
		let filesToOpen: IFilesToOpen | undefined;
		let emptyToOpen = 0;
B
Benjamin Pasero 已提交
395

B
Benjamin Pasero 已提交
396 397 398
		// Identify things to open from open config
		const pathsToOpen = this.getPathsToOpen(openConfig);
		this.logService.trace('windowsManager#open pathsToOpen', pathsToOpen);
399
		for (const path of pathsToOpen) {
400 401 402 403 404 405 406 407 408 409 410
			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) {
B
Benjamin Pasero 已提交
411 412
				if (!filesToOpen) {
					filesToOpen = { filesToOpenOrCreate: [], filesToDiff: [], remoteAuthority: path.remoteAuthority };
413
				}
B
Benjamin Pasero 已提交
414
				filesToOpen.filesToOpenOrCreate.push(path);
415 416 417 418
			} else if (path.backupPath) {
				emptyToRestore.push({ backupFolder: basename(path.backupPath), remoteAuthority: path.remoteAuthority });
			} else {
				emptyToOpen++;
419 420
			}
		}
421 422 423

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

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

434
		//
435
		// These are windows to restore because of hot-exit or from previous session (only performed once on startup!)
436
		//
B
Benjamin Pasero 已提交
437
		if (openConfig.initialStartup) {
438 439

			// Untitled workspaces are always restored
B
Benjamin Pasero 已提交
440
			workspacesToRestore.push(...this.workspacesMainService.getUntitledWorkspacesSync());
441
			workspacesToOpen.push(...workspacesToRestore);
442

443
			// Empty windows with backups are always restored
444 445 446
			emptyToRestore.push(...this.backupMainService.getEmptyWindowBackupPaths());
		} else {
			emptyToRestore.length = 0;
447
		}
448 449

		// Open based on config
B
Benjamin Pasero 已提交
450
		const usedWindows = this.doOpen(openConfig, workspacesToOpen, foldersToOpen, emptyToRestore, emptyToOpen, filesToOpen, foldersToAdd);
451

B
Benjamin Pasero 已提交
452 453
		this.logService.trace(`windowsManager#open used window count ${usedWindows.length} (workspacesToOpen: ${workspacesToOpen.length}, foldersToOpen: ${foldersToOpen.length}, emptyToRestore: ${emptyToRestore.length}, emptyToOpen: ${emptyToOpen})`);

454
		// Make sure to pass focus to the most relevant of the windows if we open multiple
455
		if (usedWindows.length > 1) {
456
			const focusLastActive = this.windowsState.lastActiveWindow && !openConfig.forceEmpty && !openConfig.cli._.length && !openConfig.cli['file-uri'] && !openConfig.cli['folder-uri'] && !(openConfig.urisToOpen && openConfig.urisToOpen.length);
457 458
			let focusLastOpened = true;
			let focusLastWindow = true;
459

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

470 471 472 473 474
			// 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 (
475 476
						(usedWindow.openedWorkspace && workspacesToRestore.some(workspace => usedWindow.openedWorkspace && workspace.workspace.id === usedWindow.openedWorkspace.id)) ||	// skip over restored workspace
						(usedWindow.backupPath && emptyToRestore.some(empty => usedWindow.backupPath && empty.backupFolder === basename(usedWindow.backupPath)))							// skip over restored empty window
477 478 479 480 481 482 483 484 485 486 487 488
					) {
						continue;
					}

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

			// 3.) finally, always ensure to have at least last used window focused
			if (focusLastWindow) {
489
				usedWindows[usedWindows.length - 1].focus();
490 491
			}
		}
492

493 494
		// 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
B
Benjamin Pasero 已提交
495
		const isDiff = filesToOpen && filesToOpen.filesToDiff.length > 0;
496
		if (!usedWindows.some(window => window.isExtensionDevelopmentHost) && !isDiff && !openConfig.noRecentEntry) {
M
Martin Aeschlimann 已提交
497 498 499 500 501 502 503 504
			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 });
505
				}
506
			}
507

508
			this.workspacesHistoryMainService.addRecentlyOpened(recents);
509
		}
510

511
		// If we got started with --wait from the CLI, we need to signal to the outside when the window
512 513
		// 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 已提交
514 515
		const waitMarkerFileURI = openConfig.waitMarkerFileURI;
		if (openConfig.context === OpenContext.CLI && waitMarkerFileURI && usedWindows.length === 1 && usedWindows[0]) {
516
			usedWindows[0].whenClosedOrLoaded.then(() => fs.unlink(waitMarkerFileURI.fsPath, _error => undefined));
517 518
		}

519 520 521
		return usedWindows;
	}

522 523 524 525 526 527 528 529 530 531
	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;
	}

532 533
	private doOpen(
		openConfig: IOpenConfiguration,
534 535
		workspacesToOpen: IWorkspacePathToOpen[],
		foldersToOpen: IFolderPathToOpen[],
536
		emptyToRestore: IEmptyWindowBackupInfo[],
537
		emptyToOpen: number,
B
Benjamin Pasero 已提交
538
		filesToOpen: IFilesToOpen | undefined,
539
		foldersToAdd: IFolderPathToOpen[]
540
	) {
541
		const usedWindows: ICodeWindow[] = [];
542

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

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

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

559
			// Find suitable window or folder path to open files in
B
Benjamin Pasero 已提交
560
			const fileToCheck = filesToOpen.filesToOpenOrCreate[0] || filesToOpen.filesToDiff[0];
561

M
Martin Aeschlimann 已提交
562
			// only look at the windows with correct authority
B
Benjamin Pasero 已提交
563
			const windows = WindowsMainService.WINDOWS.filter(window => filesToOpen && window.remoteAuthority === filesToOpen.remoteAuthority);
564

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

573 574 575 576 577
			// We found a window to open the files in
			if (bestWindowOrFolder instanceof CodeWindow) {

				// Window is workspace
				if (bestWindowOrFolder.openedWorkspace) {
578
					workspacesToOpen.push({ workspace: bestWindowOrFolder.openedWorkspace, remoteAuthority: bestWindowOrFolder.remoteAuthority });
579 580 581
				}

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

				// Window is empty
				else {

					// Do open files
B
Benjamin Pasero 已提交
590
					usedWindows.push(this.doOpenFilesInExistingWindow(openConfig, bestWindowOrFolder, filesToOpen));
591 592

					// Reset these because we handled them
B
Benjamin Pasero 已提交
593
					filesToOpen = undefined;
594
				}
595 596 597
			}

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

609
				// Reset these because we handled them
B
Benjamin Pasero 已提交
610
				filesToOpen = undefined;
E
Erich Gamma 已提交
611 612 613
			}
		}

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

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

				// Do open files
B
Benjamin Pasero 已提交
625
				usedWindows.push(this.doOpenFilesInExistingWindow(openConfig, windowOnWorkspace, filesToOpenInWindow));
626 627

				// Reset these because we handled them
B
Benjamin Pasero 已提交
628 629
				if (filesToOpenInWindow) {
					filesToOpen = undefined;
630
				}
631 632 633 634 635 636

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

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

641
				const remoteAuthority = workspaceToOpen.remoteAuthority;
B
Benjamin Pasero 已提交
642
				const filesToOpenInWindow = (filesToOpen?.remoteAuthority === remoteAuthority) ? filesToOpen : undefined;
643

644
				// Do open folder
B
Benjamin Pasero 已提交
645
				usedWindows.push(this.doOpenFolderOrWorkspace(openConfig, workspaceToOpen, openFolderInNewWindow, filesToOpenInWindow));
646 647

				// Reset these because we handled them
B
Benjamin Pasero 已提交
648 649
				if (filesToOpenInWindow) {
					filesToOpen = undefined;
650
				}
651 652 653 654 655

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

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

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

666
				// Do open files
B
Benjamin Pasero 已提交
667
				usedWindows.push(this.doOpenFilesInExistingWindow(openConfig, windowOnFolderPath, filesToOpenInWindow));
668

E
Erich Gamma 已提交
669
				// Reset these because we handled them
B
Benjamin Pasero 已提交
670 671
				if (filesToOpenInWindow) {
					filesToOpen = undefined;
672
				}
E
Erich Gamma 已提交
673

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

			// Open remaining ones
678
			allFoldersToOpen.forEach(folderToOpen => {
679

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

684
				const remoteAuthority = folderToOpen.remoteAuthority;
B
Benjamin Pasero 已提交
685
				const filesToOpenInWindow = (filesToOpen?.remoteAuthority === remoteAuthority) ? filesToOpen : undefined;
686

687
				// Do open folder
B
Benjamin Pasero 已提交
688
				usedWindows.push(this.doOpenFolderOrWorkspace(openConfig, folderToOpen, openFolderInNewWindow, filesToOpenInWindow));
E
Erich Gamma 已提交
689 690

				// Reset these because we handled them
B
Benjamin Pasero 已提交
691 692
				if (filesToOpenInWindow) {
					filesToOpen = undefined;
693
				}
E
Erich Gamma 已提交
694

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

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

B
Benjamin Pasero 已提交
706
				usedWindows.push(this.openInBrowserWindow({
B
Benjamin Pasero 已提交
707 708 709
					userEnv: openConfig.userEnv,
					cli: openConfig.cli,
					initialStartup: openConfig.initialStartup,
B
Benjamin Pasero 已提交
710
					filesToOpen: filesToOpenInWindow,
M
Martin Aeschlimann 已提交
711
					remoteAuthority,
B
Benjamin Pasero 已提交
712
					forceNewWindow: true,
713
					forceNewTabbedWindow: openConfig.forceNewTabbedWindow,
714
					emptyWindowBackupInfo
B
Benjamin Pasero 已提交
715
				}));
716

B
wip  
Benjamin Pasero 已提交
717
				// Reset these because we handled them
B
Benjamin Pasero 已提交
718 719
				if (filesToOpenInWindow) {
					filesToOpen = undefined;
720
				}
B
wip  
Benjamin Pasero 已提交
721

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

726
		// Handle empty to open (only if no other window opened)
B
Benjamin Pasero 已提交
727 728
		if (usedWindows.length === 0 || filesToOpen) {
			if (filesToOpen && !emptyToOpen) {
729 730
				emptyToOpen++;
			}
731

B
Benjamin Pasero 已提交
732
			const remoteAuthority = filesToOpen ? filesToOpen.remoteAuthority : (openConfig.cli && openConfig.cli.remote || undefined);
733

734
			for (let i = 0; i < emptyToOpen; i++) {
B
Benjamin Pasero 已提交
735
				usedWindows.push(this.doOpenEmpty(openConfig, openFolderInNewWindow, remoteAuthority, filesToOpen));
E
Erich Gamma 已提交
736

737
				// Reset these because we handled them
B
Benjamin Pasero 已提交
738
				filesToOpen = undefined;
739
				openFolderInNewWindow = true; // any other window to open must open in new window then
740 741
			}
		}
E
Erich Gamma 已提交
742

743
		return arrays.distinct(usedWindows);
E
Erich Gamma 已提交
744 745
	}

B
Benjamin Pasero 已提交
746
	private doOpenFilesInExistingWindow(configuration: IOpenConfiguration, window: ICodeWindow, filesToOpen?: IFilesToOpen): ICodeWindow {
B
Benjamin Pasero 已提交
747 748
		this.logService.trace('windowsManager#doOpenFilesInExistingWindow');

749 750
		window.focus(); // make sure window has focus

751
		const params: { filesToOpenOrCreate?: IPath[], filesToDiff?: IPath[], filesToWait?: IPathsToWaitFor, termProgram?: string } = {};
B
Benjamin Pasero 已提交
752 753 754 755
		if (filesToOpen) {
			params.filesToOpenOrCreate = filesToOpen.filesToOpenOrCreate;
			params.filesToDiff = filesToOpen.filesToDiff;
			params.filesToWait = filesToOpen.filesToWait;
B
Benjamin Pasero 已提交
756 757 758 759 760 761 762
		}

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

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

		return window;
765 766
	}

767
	private doAddFoldersToExistingWindow(window: ICodeWindow, foldersToAdd: URI[]): ICodeWindow {
768 769
		window.focus(); // make sure window has focus

B
Benjamin Pasero 已提交
770 771
		const request: IAddFoldersRequest = { foldersToAdd };
		window.sendWhenReady('vscode:addFolders', request);
772 773 774 775

		return window;
	}

B
Benjamin Pasero 已提交
776
	private doOpenEmpty(openConfig: IOpenConfiguration, forceNewWindow: boolean, remoteAuthority: string | undefined, filesToOpen: IFilesToOpen | undefined, windowToUse?: ICodeWindow): ICodeWindow {
777 778 779 780 781 782 783 784 785 786 787
		if (!forceNewWindow && !windowToUse && typeof openConfig.contextWindowId === 'number') {
			windowToUse = this.getWindowById(openConfig.contextWindowId); // fix for https://github.com/microsoft/vscode/issues/97172
		}

		return this.openInBrowserWindow({
			userEnv: openConfig.userEnv,
			cli: openConfig.cli,
			initialStartup: openConfig.initialStartup,
			remoteAuthority,
			forceNewWindow,
			forceNewTabbedWindow: openConfig.forceNewTabbedWindow,
B
Benjamin Pasero 已提交
788
			filesToOpen,
789 790 791 792
			windowToUse
		});
	}

B
Benjamin Pasero 已提交
793
	private doOpenFolderOrWorkspace(openConfig: IOpenConfiguration, folderOrWorkspace: IPathToOpen, forceNewWindow: boolean, filesToOpen: IFilesToOpen | undefined, windowToUse?: ICodeWindow): ICodeWindow {
B
Benjamin Pasero 已提交
794
		if (!forceNewWindow && !windowToUse && typeof openConfig.contextWindowId === 'number') {
C
ChaseKnowlden 已提交
795
			windowToUse = this.getWindowById(openConfig.contextWindowId); // fix for https://github.com/microsoft/vscode/issues/49587
B
Benjamin Pasero 已提交
796 797
		}

798
		return this.openInBrowserWindow({
799 800 801
			userEnv: openConfig.userEnv,
			cli: openConfig.cli,
			initialStartup: openConfig.initialStartup,
802
			workspace: folderOrWorkspace.workspace,
803
			folderUri: folderOrWorkspace.folderUri,
B
Benjamin Pasero 已提交
804
			filesToOpen,
M
Martin Aeschlimann 已提交
805
			remoteAuthority: folderOrWorkspace.remoteAuthority,
B
Benjamin Pasero 已提交
806
			forceNewWindow,
807
			forceNewTabbedWindow: openConfig.forceNewTabbedWindow,
808
			windowToUse
809 810 811
		});
	}

B
Benjamin Pasero 已提交
812 813
	private getPathsToOpen(openConfig: IOpenConfiguration): IPathToOpen[] {
		let windowsToOpen: IPathToOpen[];
814
		let isCommandLineOrAPICall = false;
815
		let restoredWindows = false;
E
Erich Gamma 已提交
816

817
		// Extract paths: from API
S
Sandeep Somavarapu 已提交
818
		if (openConfig.urisToOpen && openConfig.urisToOpen.length > 0) {
819
			windowsToOpen = this.doExtractPathsFromAPI(openConfig);
820
			isCommandLineOrAPICall = true;
E
Erich Gamma 已提交
821 822
		}

B
Benjamin Pasero 已提交
823 824
		// Check for force empty
		else if (openConfig.forceEmpty) {
825
			windowsToOpen = [Object.create(null)];
E
Erich Gamma 已提交
826 827
		}

828
		// Extract paths: from CLI
829
		else if (openConfig.cli._.length || openConfig.cli['folder-uri'] || openConfig.cli['file-uri']) {
830
			windowsToOpen = this.doExtractPathsFromCLI(openConfig.cli);
831
			isCommandLineOrAPICall = true;
B
Benjamin Pasero 已提交
832 833
		}

834
		// Extract windows: from previous session
B
Benjamin Pasero 已提交
835
		else {
836
			windowsToOpen = this.doGetWindowsFromLastSession();
837
			restoredWindows = true;
B
Benjamin Pasero 已提交
838 839
		}

840 841
		// Convert multiple folders into workspace (if opened via API or CLI)
		// This will ensure to open these folders in one window instead of multiple
B
Benjamin Pasero 已提交
842
		// If we are in `addMode`, we should not do this because in that case all
843
		// folders should be added to the existing window.
844
		if (!openConfig.addMode && isCommandLineOrAPICall) {
845
			const foldersToOpen = windowsToOpen.filter(path => !!path.folderUri);
846
			if (foldersToOpen.length > 1) {
847
				const remoteAuthority = foldersToOpen[0].remoteAuthority;
848 849 850 851 852 853 854
				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);
				}
855 856 857
			}
		}

858 859 860 861 862 863 864 865 866
		// Check for `window.startup` setting to include all windows
		// from the previous session if this is the initial startup and we have
		// not restored windows already otherwise.
		// Use `unshift` to ensure any new window to open comes last
		// for proper focus treatment.
		if (openConfig.initialStartup && !restoredWindows && this.configurationService.getValue<IWindowSettings>('window').restoreWindows === 'preserve') {
			windowsToOpen.unshift(...this.doGetWindowsFromLastSession().filter(window => window.workspace || window.folderUri || window.backupPath));
		}

867
		return windowsToOpen;
E
Erich Gamma 已提交
868 869
	}

870
	private doExtractPathsFromAPI(openConfig: IOpenConfiguration): IPathToOpen[] {
M
Matt Bierner 已提交
871
		const pathsToOpen: IPathToOpen[] = [];
872
		const parseOptions: IPathParseOptions = { gotoLineMode: openConfig.gotoLineMode };
M
Matt Bierner 已提交
873
		for (const pathToOpen of openConfig.urisToOpen || []) {
M
Martin Aeschlimann 已提交
874 875 876 877
			if (!pathToOpen) {
				continue;
			}

M
Martin Aeschlimann 已提交
878
			const path = this.parseUri(pathToOpen, parseOptions);
M
Martin Aeschlimann 已提交
879
			if (path) {
M
Martin Aeschlimann 已提交
880
				path.label = pathToOpen.label;
M
Martin Aeschlimann 已提交
881 882
				pathsToOpen.push(path);
			} else {
883 884
				const uri = this.resourceFromURIToOpen(pathToOpen);

B
Benjamin Pasero 已提交
885
				// Warn about the invalid URI or path
M
Martin Aeschlimann 已提交
886
				let message, detail;
M
Martin Aeschlimann 已提交
887
				if (uri.scheme === Schemas.file) {
M
Martin Aeschlimann 已提交
888
					message = localize('pathNotExistTitle', "Path does not exist");
889
					detail = localize('pathNotExistDetail', "The path '{0}' does not seem to exist anymore on disk.", getPathLabel(uri.fsPath, this.environmentService));
M
Martin Aeschlimann 已提交
890 891
				} else {
					message = localize('uriInvalidTitle', "URI can not be opened");
M
Martin Aeschlimann 已提交
892
					detail = localize('uriInvalidDetail', "The URI '{0}' is not valid and can not be opened.", uri.toString());
M
Martin Aeschlimann 已提交
893
				}
894

895
				const options: MessageBoxOptions = {
896 897
					title: product.nameLong,
					type: 'info',
898
					buttons: [localize('ok', "OK")],
M
Martin Aeschlimann 已提交
899 900
					message,
					detail,
901 902 903
					noLink: true
				};

904
				this.dialogMainService.showMessageBox(options, withNullAsUndefined(BrowserWindow.getFocusedWindow()));
905
			}
M
Martin Aeschlimann 已提交
906
		}
907 908 909
		return pathsToOpen;
	}

910
	private doExtractPathsFromCLI(cli: NativeParsedArgs): IPath[] {
M
Matt Bierner 已提交
911
		const pathsToOpen: IPathToOpen[] = [];
R
Rob Lourens 已提交
912
		const parseOptions: IPathParseOptions = { ignoreFileNotFound: true, gotoLineMode: cli.goto, remoteAuthority: cli.remote || undefined };
913 914

		// folder uris
915 916 917 918 919 920 921 922 923
		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 已提交
924
				}
M
Martin Aeschlimann 已提交
925
			}
926 927 928
		}

		// file uris
929 930 931 932 933 934 935 936 937
		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 已提交
938
				}
M
Martin Aeschlimann 已提交
939
			}
940 941 942
		}

		// folder or file paths
943
		const cliArgs = cli._;
M
Martin Aeschlimann 已提交
944 945 946 947 948
		for (let cliArg of cliArgs) {
			const path = this.parsePath(cliArg, parseOptions);
			if (path) {
				pathsToOpen.push(path);
			}
949 950
		}

M
Martin Aeschlimann 已提交
951
		if (pathsToOpen.length) {
952
			return pathsToOpen;
B
Benjamin Pasero 已提交
953 954 955 956 957 958
		}

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

B
Benjamin Pasero 已提交
959
	private doGetWindowsFromLastSession(): IPathToOpen[] {
B
Benjamin Pasero 已提交
960
		const restoreWindowsSetting = this.getRestoreWindowsSetting();
B
Benjamin Pasero 已提交
961

B
Benjamin Pasero 已提交
962
		switch (restoreWindowsSetting) {
B
Benjamin Pasero 已提交
963

964
			// none: we always open an empty window
965 966
			case 'none':
				return [Object.create(null)];
B
Benjamin Pasero 已提交
967

968
			// one: restore last opened workspace/folder or empty window
969 970
			// all: restore all windows
			// folders: restore last opened folders only
971
			case 'one':
972
			case 'all':
973
			case 'preserve':
974
			case 'folders':
B
Benjamin Pasero 已提交
975 976

				// Collect previously opened windows
977
				const openedWindows: IWindowState[] = [];
B
Benjamin Pasero 已提交
978
				if (restoreWindowsSetting !== 'one') {
979
					openedWindows.push(...this.windowsState.openedWindows);
980
				}
981 982
				if (this.windowsState.lastActiveWindow) {
					openedWindows.push(this.windowsState.lastActiveWindow);
983
				}
984

985 986
				const windowsToOpen: IPathToOpen[] = [];
				for (const openedWindow of openedWindows) {
B
Benjamin Pasero 已提交
987 988 989

					// Workspaces
					if (openedWindow.workspace) {
M
Martin Aeschlimann 已提交
990
						const pathToOpen = this.parseUri({ workspaceUri: openedWindow.workspace.configPath }, { remoteAuthority: openedWindow.remoteAuthority });
B
Benjamin Pasero 已提交
991
						if (pathToOpen?.workspace) {
992 993
							windowsToOpen.push(pathToOpen);
						}
B
Benjamin Pasero 已提交
994 995 996 997
					}

					// Folders
					else if (openedWindow.folderUri) {
M
Martin Aeschlimann 已提交
998
						const pathToOpen = this.parseUri({ folderUri: openedWindow.folderUri }, { remoteAuthority: openedWindow.remoteAuthority });
B
Benjamin Pasero 已提交
999
						if (pathToOpen?.folderUri) {
1000 1001
							windowsToOpen.push(pathToOpen);
						}
B
Benjamin Pasero 已提交
1002 1003 1004 1005
					}

					// Empty window, potentially editors open to be restored
					else if (restoreWindowsSetting !== 'folders' && openedWindow.backupPath) {
M
Martin Aeschlimann 已提交
1006
						windowsToOpen.push({ backupPath: openedWindow.backupPath, remoteAuthority: openedWindow.remoteAuthority });
1007
					}
1008 1009 1010 1011 1012 1013 1014
				}

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

				break;
B
Benjamin Pasero 已提交
1015
		}
E
Erich Gamma 已提交
1016

1017
		// Always fallback to empty window
B
Benjamin Pasero 已提交
1018
		return [Object.create(null)];
E
Erich Gamma 已提交
1019 1020
	}

1021 1022
	private getRestoreWindowsSetting(): RestoreWindowsSetting {
		let restoreWindows: RestoreWindowsSetting;
1023
		if (this.lifecycleMainService.wasRestarted) {
1024 1025
			restoreWindows = 'all'; // always reopen all windows when an update was applied
		} else {
1026
			const windowConfig = this.configurationService.getValue<IWindowSettings>('window');
B
Benjamin Pasero 已提交
1027
			restoreWindows = windowConfig?.restoreWindows || 'all'; // by default restore all windows
1028

1029
			if (!['preserve', 'all', 'folders', 'one', 'none'].includes(restoreWindows)) {
B
Benjamin Pasero 已提交
1030
				restoreWindows = 'all'; // by default restore all windows
1031 1032 1033 1034 1035 1036
			}
		}

		return restoreWindows;
	}

1037
	private argToUri(arg: string): URI | undefined {
M
Martin Aeschlimann 已提交
1038
		try {
1039
			const uri = URI.parse(arg);
M
Martin Aeschlimann 已提交
1040
			if (!uri.scheme) {
M
Martin Aeschlimann 已提交
1041
				this.logService.error(`Invalid URI input string, scheme missing: ${arg}`);
1042
				return undefined;
M
Martin Aeschlimann 已提交
1043
			}
1044

M
Martin Aeschlimann 已提交
1045 1046
			return uri;
		} catch (e) {
M
Martin Aeschlimann 已提交
1047
			this.logService.error(`Invalid URI input string: ${arg}, ${e.message}`);
1048
		}
1049

1050
		return undefined;
1051 1052
	}

1053 1054
	private parseUri(toOpen: IWindowOpenable, options: IPathParseOptions = {}): IPathToOpen | undefined {
		if (!toOpen) {
1055
			return undefined;
1056
		}
1057

1058
		let uri = this.resourceFromURIToOpen(toOpen);
M
Martin Aeschlimann 已提交
1059
		if (uri.scheme === Schemas.file) {
1060
			return this.parsePath(uri.fsPath, options, isFileToOpen(toOpen));
1061
		}
M
Martin Aeschlimann 已提交
1062 1063

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

1066 1067
		// normalize URI
		uri = normalizePath(uri);
1068 1069

		// remove trailing slash
1070
		uri = removeTrailingPathSeparator(uri);
1071

1072 1073
		// File
		if (isFileToOpen(toOpen)) {
1074
			if (options.gotoLineMode) {
1075 1076 1077 1078
				const parsedPath = parseLineAndColumnAware(uri.path);
				return {
					fileUri: uri.with({ path: parsedPath.path }),
					lineNumber: parsedPath.line,
M
Martin Aeschlimann 已提交
1079 1080
					columnNumber: parsedPath.column,
					remoteAuthority
1081 1082
				};
			}
1083

1084
			return {
M
Martin Aeschlimann 已提交
1085 1086
				fileUri: uri,
				remoteAuthority
1087
			};
1088 1089 1090 1091
		}

		// Workspace
		else if (isWorkspaceToOpen(toOpen)) {
M
Martin Aeschlimann 已提交
1092 1093 1094 1095
			return {
				workspace: getWorkspaceIdentifier(uri),
				remoteAuthority
			};
1096
		}
1097 1098

		// Folder
1099
		return {
M
Martin Aeschlimann 已提交
1100 1101
			folderUri: uri,
			remoteAuthority
1102 1103 1104
		};
	}

1105 1106 1107 1108 1109 1110 1111 1112 1113 1114 1115 1116
	private resourceFromURIToOpen(openable: IWindowOpenable): URI {
		if (isWorkspaceToOpen(openable)) {
			return openable.workspaceUri;
		}

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

		return openable.fileUri;
	}

M
Martin Aeschlimann 已提交
1117
	private parsePath(anyPath: string, options: IPathParseOptions, forceOpenWorkspaceAsFile?: boolean): IPathToOpen | undefined {
E
Erich Gamma 已提交
1118
		if (!anyPath) {
1119
			return undefined;
E
Erich Gamma 已提交
1120 1121
		}

1122 1123
		let lineNumber: number | undefined;
		let columnNumber: number | undefined;
1124

1125
		if (options.gotoLineMode) {
1126 1127 1128
			const parsedPath = parseLineAndColumnAware(anyPath);
			lineNumber = parsedPath.line;
			columnNumber = parsedPath.column;
1129

E
Erich Gamma 已提交
1130 1131 1132
			anyPath = parsedPath.path;
		}

1133
		// open remote if either specified in the cli even if it is a local file.
1134
		const remoteAuthority = options.remoteAuthority;
1135 1136
		if (remoteAuthority) {
			const first = anyPath.charCodeAt(0);
1137

1138 1139 1140 1141 1142
			// make absolute
			if (first !== CharCode.Slash) {
				if (isWindowsDriveLetter(first) && anyPath.charCodeAt(anyPath.charCodeAt(1)) === CharCode.Colon) {
					anyPath = toSlashes(anyPath);
				}
1143 1144

				anyPath = `/${anyPath}`;
1145 1146 1147 1148
			}

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

1149 1150 1151 1152 1153 1154
			// guess the file type: If it ends with a slash it's a folder. If it has a file extension, it's a file or a workspace. By defaults it's a folder.
			if (anyPath.charCodeAt(anyPath.length - 1) !== CharCode.Slash) {
				if (hasWorkspaceFileExtension(anyPath)) {
					if (forceOpenWorkspaceAsFile) {
						return { fileUri: uri, remoteAuthority };
					}
1155
				} else if (posix.basename(anyPath).indexOf('.') !== -1) { // file name starts with a dot or has an file extension
1156 1157 1158
					return { fileUri: uri, remoteAuthority };
				}
			}
1159

1160 1161 1162 1163 1164
			return { folderUri: uri, remoteAuthority };
		}

		let candidate = normalize(anyPath);

E
Erich Gamma 已提交
1165
		try {
B
Benjamin Pasero 已提交
1166
			const candidateStat = fs.statSync(candidate);
1167 1168 1169 1170 1171 1172 1173 1174 1175 1176 1177
			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
						};
1178
					}
1179 1180
				}

1181 1182 1183 1184 1185 1186 1187 1188 1189 1190 1191 1192 1193 1194 1195 1196 1197 1198 1199
				// 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 已提交
1200 1201
			}
		} catch (error) {
S
Sandeep Somavarapu 已提交
1202
			const fileUri = URI.file(candidate);
B
Benjamin Pasero 已提交
1203
			this.workspacesHistoryMainService.removeRecentlyOpened([fileUri]); // since file does not seem to exist anymore, remove from recent
1204 1205

			// assume this is a file that does not yet exist
B
Benjamin Pasero 已提交
1206
			if (options?.ignoreFileNotFound) {
1207 1208 1209 1210 1211
				return {
					fileUri,
					remoteAuthority,
					exists: false
				};
E
Erich Gamma 已提交
1212 1213 1214
			}
		}

1215
		return undefined;
E
Erich Gamma 已提交
1216 1217
	}

B
Benjamin Pasero 已提交
1218 1219 1220
	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
1221
		const windowConfig = this.configurationService.getValue<IWindowSettings>('window');
B
Benjamin Pasero 已提交
1222 1223
		const openFolderInNewWindowConfig = windowConfig?.openFoldersInNewWindow || 'default' /* default */;
		const openFilesInNewWindowConfig = windowConfig?.openFilesInNewWindow || 'off' /* default */;
1224

B
Benjamin Pasero 已提交
1225
		let openFolderInNewWindow = (openConfig.preferNewWindow || openConfig.forceNewWindow) && !openConfig.forceReuseWindow;
1226 1227
		if (!openConfig.forceNewWindow && !openConfig.forceReuseWindow && (openFolderInNewWindowConfig === 'on' || openFolderInNewWindowConfig === 'off')) {
			openFolderInNewWindow = (openFolderInNewWindowConfig === 'on');
B
Benjamin Pasero 已提交
1228 1229 1230
		}

		// 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 已提交
1231
		let openFilesInNewWindow: boolean = false;
B
Benjamin Pasero 已提交
1232
		if (openConfig.forceNewWindow || openConfig.forceReuseWindow) {
M
Matt Bierner 已提交
1233
			openFilesInNewWindow = !!openConfig.forceNewWindow && !openConfig.forceReuseWindow;
B
Benjamin Pasero 已提交
1234
		} else {
1235 1236 1237 1238 1239 1240 1241 1242

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

1243 1244
			// 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
1245
			else {
1246
				if (openConfig.context !== OpenContext.DIALOG && openConfig.context !== OpenContext.MENU && !(openConfig.userEnv && openConfig.userEnv['TERM_PROGRAM'] === 'vscode')) {
1247 1248
					openFilesInNewWindow = true;
				}
B
Benjamin Pasero 已提交
1249 1250
			}

1251
			// finally check for overrides of default
1252 1253
			if (!openConfig.cli.extensionDevelopmentPath && (openFilesInNewWindowConfig === 'on' || openFilesInNewWindowConfig === 'off')) {
				openFilesInNewWindow = (openFilesInNewWindowConfig === 'on');
B
Benjamin Pasero 已提交
1254 1255 1256
			}
		}

M
Matt Bierner 已提交
1257
		return { openFolderInNewWindow: !!openFolderInNewWindow, openFilesInNewWindow };
B
Benjamin Pasero 已提交
1258 1259
	}

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

B
Benjamin Pasero 已提交
1262 1263 1264
		// 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.
1265
		const existingWindow = findWindowOnExtensionDevelopmentPath(WindowsMainService.WINDOWS, extensionDevelopmentPath);
1266
		if (existingWindow) {
1267
			this.lifecycleMainService.reload(existingWindow, openConfig.cli);
1268
			existingWindow.focus(); // make sure it gets focus and is restored
E
Erich Gamma 已提交
1269

1270
			return [existingWindow];
B
Benjamin Pasero 已提交
1271
		}
1272

1273 1274
		let folderUris = openConfig.cli['folder-uri'] || [];
		let fileUris = openConfig.cli['file-uri'] || [];
1275
		let cliArgs = openConfig.cli._;
E
Erich Gamma 已提交
1276

1277
		// Fill in previously opened workspace unless an explicit path is provided and we are not unit testing
1278
		if (!cliArgs.length && !folderUris.length && !fileUris.length && !openConfig.cli.extensionTestsPath) {
1279
			const extensionDevelopmentWindowState = this.windowsState.lastPluginDevelopmentHostWindow;
1280
			const workspaceToOpen = extensionDevelopmentWindowState && (extensionDevelopmentWindowState.workspace || extensionDevelopmentWindowState.folderUri);
1281
			if (workspaceToOpen) {
1282
				if (isSingleFolderWorkspaceIdentifier(workspaceToOpen)) {
1283
					if (workspaceToOpen.scheme === Schemas.file) {
1284
						cliArgs = [workspaceToOpen.fsPath];
1285
					} else {
1286
						folderUris = [workspaceToOpen.toString()];
1287 1288
					}
				} else {
M
Martin Aeschlimann 已提交
1289
					if (workspaceToOpen.configPath.scheme === Schemas.file) {
1290
						cliArgs = [originalFSPath(workspaceToOpen.configPath)];
M
Martin Aeschlimann 已提交
1291
					} else {
1292
						fileUris = [workspaceToOpen.configPath.toString()];
M
Martin Aeschlimann 已提交
1293
					}
1294
				}
E
Erich Gamma 已提交
1295 1296 1297
			}
		}

1298 1299 1300 1301 1302
		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) {
1303
					if (authority) {
1304 1305
						if (url.authority !== authority) {
							this.logService.error('more than one extension development path authority');
1306 1307
						}
					} else {
1308
						authority = url.authority;
1309 1310 1311
					}
				}
			}
1312 1313 1314 1315 1316 1317 1318 1319
		}

		// 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);
1320
			if (!!findWindowOnWorkspaceOrFolderUri(WindowsMainService.WINDOWS, uri)) {
1321
				return false;
1322
			}
1323

1324 1325
			return uri.authority === authority;
		});
1326

1327 1328 1329
		folderUris = folderUris.filter(folderUriStr => {
			const folderUri = this.argToUri(folderUriStr);
			if (!!findWindowOnWorkspaceOrFolderUri(WindowsMainService.WINDOWS, folderUri)) {
1330 1331
				return false;
			}
1332 1333

			return folderUri ? folderUri.authority === authority : false;
1334 1335
		});

1336 1337 1338
		fileUris = fileUris.filter(fileUriStr => {
			const fileUri = this.argToUri(fileUriStr);
			if (!!findWindowOnWorkspaceOrFolderUri(WindowsMainService.WINDOWS, fileUri)) {
1339 1340
				return false;
			}
1341 1342

			return fileUri ? fileUri.authority === authority : false;
1343 1344 1345 1346 1347 1348 1349
		});

		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
1350 1351 1352
		const noFilesOrFolders = !cliArgs.length && !folderUris.length && !fileUris.length;
		if (noFilesOrFolders && authority) {
			openConfig.cli.remote = authority;
1353 1354
		}

B
Benjamin Pasero 已提交
1355
		// Open it
M
Martin Aeschlimann 已提交
1356 1357 1358 1359
		const openArgs: IOpenConfiguration = {
			context: openConfig.context,
			cli: openConfig.cli,
			forceNewWindow: true,
1360
			forceEmpty: noFilesOrFolders,
M
Martin Aeschlimann 已提交
1361 1362 1363 1364
			userEnv: openConfig.userEnv,
			noRecentEntry: true,
			waitMarkerFileURI: openConfig.waitMarkerFileURI
		};
1365 1366

		return this.open(openArgs);
E
Erich Gamma 已提交
1367 1368
	}

1369
	private openInBrowserWindow(options: IOpenBrowserWindowOptions): ICodeWindow {
1370

1371 1372
		// Build INativeWindowConfiguration from config and options
		const configuration: INativeWindowConfiguration = mixin({}, options.cli); // inherit all properties from CLI
B
Benjamin Pasero 已提交
1373
		configuration.appRoot = this.environmentService.appRoot;
1374
		configuration.machineId = this.machineId;
1375
		configuration.nodeCachedDataDir = this.environmentService.nodeCachedDataDir;
1376
		configuration.mainPid = process.pid;
B
Benjamin Pasero 已提交
1377
		configuration.execPath = process.execPath;
1378
		configuration.userEnv = { ...this.initialUserEnv, ...options.userEnv };
B
Benjamin Pasero 已提交
1379
		configuration.isInitialStartup = options.initialStartup;
1380
		configuration.workspace = options.workspace;
1381
		configuration.folderUri = options.folderUri;
M
Martin Aeschlimann 已提交
1382
		configuration.remoteAuthority = options.remoteAuthority;
1383

B
Benjamin Pasero 已提交
1384 1385 1386 1387 1388
		const filesToOpen = options.filesToOpen;
		if (filesToOpen) {
			configuration.filesToOpenOrCreate = filesToOpen.filesToOpenOrCreate;
			configuration.filesToDiff = filesToOpen.filesToDiff;
			configuration.filesToWait = filesToOpen.filesToWait;
1389
		}
B
Benjamin Pasero 已提交
1390

1391
		// if we know the backup folder upfront (for empty windows to restore), we can set it
1392
		// directly here which helps for restoring UI state associated with that window.
B
Benjamin Pasero 已提交
1393
		// For all other cases we first call into registerEmptyWindowBackupSync() to set it before
1394
		// loading the window.
1395
		if (options.emptyWindowBackupInfo) {
S
Sandeep Somavarapu 已提交
1396
			configuration.backupPath = join(this.environmentService.backupHome, options.emptyWindowBackupInfo.backupFolder);
1397 1398
		}

1399
		let window: ICodeWindow | undefined;
1400
		if (!options.forceNewWindow && !options.forceNewTabbedWindow) {
1401 1402 1403
			window = options.windowToUse || this.getLastActiveWindow();
			if (window) {
				window.focus();
E
Erich Gamma 已提交
1404 1405 1406 1407
			}
		}

		// New window
1408
		if (!window) {
1409
			const windowConfig = this.configurationService.getValue<IWindowSettings>('window');
1410 1411 1412 1413 1414
			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) {
S
SteVen Batten 已提交
1415
				allowFullscreen = (windowConfig?.newWindowDimensions && ['fullscreen', 'inherit', 'offset'].indexOf(windowConfig.newWindowDimensions) >= 0);
1416 1417 1418 1419
			}

			// Window state is from a previous session: only allow fullscreen when we got updated or user wants to restore
			else {
B
Benjamin Pasero 已提交
1420
				allowFullscreen = this.lifecycleMainService.wasRestarted || windowConfig?.restoreFullscreen;
B
Benjamin Pasero 已提交
1421 1422 1423 1424 1425 1426 1427 1428 1429

				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;
				}
1430 1431 1432 1433 1434
			}

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

1436
			// Create the window
1437
			const createdWindow = window = this.instantiationService.createInstance(CodeWindow, {
1438
				state,
1439
				extensionDevelopmentPath: configuration.extensionDevelopmentPath,
1440
				isExtensionTestHost: !!configuration.extensionTestsPath
1441
			});
1442

1443 1444 1445 1446
			// Add as window tab if configured (macOS only)
			if (options.forceNewTabbedWindow) {
				const activeWindow = this.getLastActiveWindow();
				if (activeWindow) {
1447
					activeWindow.addTabbedWindow(createdWindow);
1448 1449 1450
				}
			}

B
Benjamin Pasero 已提交
1451
			// Add to our list of windows
1452
			WindowsMainService.WINDOWS.push(createdWindow);
E
Erich Gamma 已提交
1453

B
Benjamin Pasero 已提交
1454 1455 1456
			// Indicate new window via event
			this._onWindowOpened.fire(createdWindow);

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

E
Erich Gamma 已提交
1460
			// Window Events
1461 1462 1463 1464 1465
			once(createdWindow.onReady)(() => this._onWindowReady.fire(createdWindow));
			once(createdWindow.onClose)(() => this.onWindowClosed(createdWindow));
			once(createdWindow.onDestroy)(() => this.onBeforeWindowClose(createdWindow)); // try to save state before destroy because close will not fire
			createdWindow.win.webContents.removeAllListeners('devtools-reload-page'); // remove built in listener so we can handle this on our own
			createdWindow.win.webContents.on('devtools-reload-page', () => this.lifecycleMainService.reload(createdWindow));
E
Erich Gamma 已提交
1466 1467

			// Lifecycle
1468
			(this.lifecycleMainService as LifecycleMainService).registerWindow(createdWindow);
E
Erich Gamma 已提交
1469 1470 1471 1472 1473 1474
		}

		// Existing window
		else {

			// Some configuration things get inherited if the window is being reused and we are
B
Benjamin Pasero 已提交
1475
			// in extension development host mode. These options are all development related.
1476
			const currentWindowConfig = window.config;
A
Alex Dima 已提交
1477 1478
			if (!configuration.extensionDevelopmentPath && currentWindowConfig && !!currentWindowConfig.extensionDevelopmentPath) {
				configuration.extensionDevelopmentPath = currentWindowConfig.extensionDevelopmentPath;
B
Benjamin Pasero 已提交
1479
				configuration.verbose = currentWindowConfig.verbose;
1480
				configuration['inspect-brk-extensions'] = currentWindowConfig['inspect-brk-extensions'];
1481
				configuration.debugId = currentWindowConfig.debugId;
1482
				configuration['inspect-extensions'] = currentWindowConfig['inspect-extensions'];
1483
				configuration['extensions-dir'] = currentWindowConfig['extensions-dir'];
E
Erich Gamma 已提交
1484 1485 1486
			}
		}

1487 1488 1489 1490
		// 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) {
1491
			this.lifecycleMainService.unload(window, UnloadReason.LOAD).then(veto => {
1492
				if (!veto) {
M
Matt Bierner 已提交
1493
					this.doOpenInBrowserWindow(window!, configuration, options);
B
Benjamin Pasero 已提交
1494
				}
1495 1496 1497 1498 1499 1500 1501
			});
		} else {
			this.doOpenInBrowserWindow(window, configuration, options);
		}

		return window;
	}
B
Benjamin Pasero 已提交
1502

1503
	private doOpenInBrowserWindow(window: ICodeWindow, configuration: INativeWindowConfiguration, options: IOpenBrowserWindowOptions): void {
1504

1505 1506 1507
		// Register window for backups
		if (!configuration.extensionDevelopmentPath) {
			if (configuration.workspace) {
1508
				configuration.backupPath = this.backupMainService.registerWorkspaceBackupSync({ workspace: configuration.workspace, remoteAuthority: configuration.remoteAuthority });
1509 1510 1511 1512
			} else if (configuration.folderUri) {
				configuration.backupPath = this.backupMainService.registerFolderBackupSync(configuration.folderUri);
			} else {
				const backupFolder = options.emptyWindowBackupInfo && options.emptyWindowBackupInfo.backupFolder;
1513
				configuration.backupPath = this.backupMainService.registerEmptyWindowBackupSync(backupFolder, configuration.remoteAuthority);
E
Erich Gamma 已提交
1514
			}
1515
		}
1516

1517 1518
		// Load it
		window.load(configuration);
E
Erich Gamma 已提交
1519 1520
	}

1521
	private getNewWindowState(configuration: INativeWindowConfiguration): INewWindowState {
1522
		const lastActive = this.getLastActiveWindow();
E
Erich Gamma 已提交
1523

1524 1525
		// Restore state unless we are running extension tests
		if (!configuration.extensionTestsPath) {
E
Erich Gamma 已提交
1526

1527 1528 1529
			// extension development host Window - load from stored settings if any
			if (!!configuration.extensionDevelopmentPath && this.windowsState.lastPluginDevelopmentHostWindow) {
				return this.windowsState.lastPluginDevelopmentHostWindow.uiState;
1530 1531
			}

1532
			// Known Workspace - load from stored settings
M
Matt Bierner 已提交
1533 1534 1535
			const workspace = configuration.workspace;
			if (workspace) {
				const stateForWorkspace = this.windowsState.openedWindows.filter(o => o.workspace && o.workspace.id === workspace.id).map(o => o.uiState);
1536 1537 1538 1539 1540 1541
				if (stateForWorkspace.length) {
					return stateForWorkspace[0];
				}
			}

			// Known Folder - load from stored settings
1542
			if (configuration.folderUri) {
1543
				const stateForFolder = this.windowsState.openedWindows.filter(o => o.folderUri && extUriBiasedIgnorePathCase.isEqual(o.folderUri, configuration.folderUri)).map(o => o.uiState);
1544 1545 1546
				if (stateForFolder.length) {
					return stateForFolder[0];
				}
1547 1548
			}

1549 1550 1551 1552 1553 1554
			// 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 已提交
1555 1556
			}

1557 1558 1559 1560 1561
			// First Window
			const lastActiveState = this.lastClosedWindowState || this.windowsState.lastActiveWindow;
			if (!lastActive && lastActiveState) {
				return lastActiveState.uiState;
			}
E
Erich Gamma 已提交
1562 1563 1564 1565 1566 1567 1568
		}

		//
		// 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
1569
		let displayToUse: Display | undefined;
B
Benjamin Pasero 已提交
1570
		const displays = screen.getAllDisplays();
E
Erich Gamma 已提交
1571 1572 1573 1574 1575 1576 1577 1578 1579 1580

		// 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 已提交
1581
			if (isMacintosh) {
B
Benjamin Pasero 已提交
1582
				const cursorPoint = screen.getCursorScreenPoint();
E
Erich Gamma 已提交
1583 1584 1585 1586 1587 1588 1589 1590
				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());
			}

1591
			// fallback to primary display or first display
E
Erich Gamma 已提交
1592
			if (!displayToUse) {
1593
				displayToUse = screen.getPrimaryDisplay() || displays[0];
E
Erich Gamma 已提交
1594 1595 1596
			}
		}

1597 1598 1599
		// 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.
1600
		let state = defaultWindowState();
M
Matt Bierner 已提交
1601 1602
		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 已提交
1603

1604
		// Check for newWindowDimensions setting and adjust accordingly
1605
		const windowConfig = this.configurationService.getValue<IWindowSettings>('window');
1606
		let ensureNoOverlap = true;
B
Benjamin Pasero 已提交
1607
		if (windowConfig?.newWindowDimensions) {
1608 1609 1610 1611 1612 1613
			if (windowConfig.newWindowDimensions === 'maximized') {
				state.mode = WindowMode.Maximized;
				ensureNoOverlap = false;
			} else if (windowConfig.newWindowDimensions === 'fullscreen') {
				state.mode = WindowMode.Fullscreen;
				ensureNoOverlap = false;
S
SteVen Batten 已提交
1614
			} else if ((windowConfig.newWindowDimensions === 'inherit' || windowConfig.newWindowDimensions === 'offset') && lastActive) {
B
Benjamin Pasero 已提交
1615 1616
				const lastActiveState = lastActive.serializeWindowState();
				if (lastActiveState.mode === WindowMode.Fullscreen) {
C
ChaseKnowlden 已提交
1617
					state.mode = WindowMode.Fullscreen; // only take mode (fixes https://github.com/microsoft/vscode/issues/19331)
B
Benjamin Pasero 已提交
1618 1619 1620 1621
				} else {
					state = lastActiveState;
				}

S
SteVen Batten 已提交
1622
				ensureNoOverlap = state.mode !== WindowMode.Fullscreen && windowConfig.newWindowDimensions === 'offset';
1623 1624 1625 1626 1627 1628 1629
			}
		}

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

1630
		(state as INewWindowState).hasDefaultState = true; // flag as default state
1631

1632
		return state;
E
Erich Gamma 已提交
1633 1634
	}

J
Joao Moreno 已提交
1635
	private ensureNoOverlap(state: ISingleWindowState): ISingleWindowState {
1636
		if (WindowsMainService.WINDOWS.length === 0) {
E
Erich Gamma 已提交
1637 1638 1639
			return state;
		}

M
Matt Bierner 已提交
1640 1641 1642
		state.x = typeof state.x === 'number' ? state.x : 0;
		state.y = typeof state.y === 'number' ? state.y : 0;

1643
		const existingWindowBounds = WindowsMainService.WINDOWS.map(win => win.getBounds());
1644
		while (existingWindowBounds.some(b => b.x === state.x || b.y === state.y)) {
E
Erich Gamma 已提交
1645 1646 1647 1648 1649 1650 1651
			state.x += 30;
			state.y += 30;
		}

		return state;
	}

1652 1653 1654 1655 1656 1657 1658 1659 1660 1661
	private onWindowClosed(win: ICodeWindow): void {

		// Remove from our list so that Electron can clean it up
		const index = WindowsMainService.WINDOWS.indexOf(win);
		WindowsMainService.WINDOWS.splice(index, 1);

		// Emit
		this._onWindowsCountChanged.fire({ oldCount: WindowsMainService.WINDOWS.length + 1, newCount: WindowsMainService.WINDOWS.length });
	}

1662 1663 1664 1665 1666 1667 1668 1669 1670
	getFocusedWindow(): ICodeWindow | undefined {
		const win = BrowserWindow.getFocusedWindow();
		if (win) {
			return this.getWindowById(win.id);
		}

		return undefined;
	}

M
Matt Bierner 已提交
1671
	getLastActiveWindow(): ICodeWindow | undefined {
1672
		return getLastActiveWindow(WindowsMainService.WINDOWS);
E
Erich Gamma 已提交
1673 1674
	}

1675
	private getLastActiveWindowForAuthority(remoteAuthority: string | undefined): ICodeWindow | undefined {
1676
		return getLastActiveWindow(WindowsMainService.WINDOWS.filter(window => window.remoteAuthority === remoteAuthority));
M
Martin Aeschlimann 已提交
1677 1678
	}

B
Benjamin Pasero 已提交
1679
	sendToFocused(channel: string, ...args: any[]): void {
E
Erich Gamma 已提交
1680 1681 1682
		const focusedWindow = this.getFocusedWindow() || this.getLastActiveWindow();

		if (focusedWindow) {
1683
			focusedWindow.sendWhenReady(channel, ...args);
E
Erich Gamma 已提交
1684 1685 1686
		}
	}

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

B
Benjamin Pasero 已提交
1693 1694
			window.sendWhenReady(channel, payload);
		}
E
Erich Gamma 已提交
1695 1696
	}

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

M
Matt Bierner 已提交
1700
		return arrays.firstOrDefault(res);
E
Erich Gamma 已提交
1701 1702
	}

B
Benjamin Pasero 已提交
1703
	getWindows(): ICodeWindow[] {
1704
		return WindowsMainService.WINDOWS;
E
Erich Gamma 已提交
1705 1706
	}

B
Benjamin Pasero 已提交
1707
	getWindowCount(): number {
1708
		return WindowsMainService.WINDOWS.length;
E
Erich Gamma 已提交
1709
	}
B
Benjamin Pasero 已提交
1710
}