windows - first cut cleanup of windows finding logic

This commit is contained in:
Benjamin Pasero
2020-12-14 08:23:30 +01:00
parent 1a1db8f00e
commit d8a4891005
15 changed files with 299 additions and 359 deletions
@@ -67,7 +67,7 @@ import { ExtensionTipsService } from 'vs/platform/extensionManagement/electron-s
import { UserDataSyncMachinesService, IUserDataSyncMachinesService } from 'vs/platform/userDataSync/common/userDataSyncMachines';
import { IExtensionRecommendationNotificationService } from 'vs/platform/extensionRecommendations/common/extensionRecommendations';
import { ExtensionRecommendationNotificationServiceChannelClient } from 'vs/platform/extensionRecommendations/electron-sandbox/extensionRecommendationsIpc';
import { ActiveWindowManager } from 'vs/platform/windows/common/windowTracker';
import { ActiveWindowManager } from 'vs/platform/windows/node/windowTracker';
import { TelemetryLogAppender } from 'vs/platform/telemetry/common/telemetryLogAppender';
import { UserDataAutoSyncEnablementService } from 'vs/platform/userDataSync/common/userDataAutoSyncService';
import { IgnoredExtensionsManagementService, IIgnoredExtensionsManagementService } from 'vs/platform/userDataSync/common/ignoredExtensions';
+18 -6
View File
@@ -7,7 +7,6 @@ import { app, ipcMain, systemPreferences, contentTracing, protocol, BrowserWindo
import { IProcessEnvironment, isWindows, isMacintosh, isLinux } from 'vs/base/common/platform';
import { WindowsMainService } from 'vs/platform/windows/electron-main/windowsMainService';
import { IWindowOpenable } from 'vs/platform/windows/common/windows';
import { OpenContext } from 'vs/platform/windows/electron-main/window';
import { ILifecycleMainService, LifecycleMainPhase } from 'vs/platform/lifecycle/electron-main/lifecycleMainService';
import { resolveShellEnv } from 'vs/code/node/shellEnv';
import { IUpdateService } from 'vs/platform/update/common/update';
@@ -36,7 +35,7 @@ import product from 'vs/platform/product/common/product';
import { ProxyAuthHandler } from 'vs/code/electron-main/auth';
import { FileProtocolHandler } from 'vs/code/electron-main/protocol';
import { Disposable } from 'vs/base/common/lifecycle';
import { IWindowsMainService, ICodeWindow } from 'vs/platform/windows/electron-main/windows';
import { IWindowsMainService, ICodeWindow, OpenContext } from 'vs/platform/windows/electron-main/windows';
import { URI } from 'vs/base/common/uri';
import { hasWorkspaceFileExtension, IWorkspacesService } from 'vs/platform/workspaces/common/workspaces';
import { WorkspacesService } from 'vs/platform/workspaces/electron-main/workspacesService';
@@ -80,7 +79,7 @@ import { stripComments } from 'vs/base/common/json';
import { generateUuid } from 'vs/base/common/uuid';
import { VSBuffer } from 'vs/base/common/buffer';
import { EncryptionMainService, IEncryptionMainService } from 'vs/platform/encryption/electron-main/encryptionMainService';
import { ActiveWindowManager } from 'vs/platform/windows/common/windowTracker';
import { ActiveWindowManager } from 'vs/platform/windows/node/windowTracker';
import { IKeyboardLayoutMainService, KeyboardLayoutMainService } from 'vs/platform/keyboardLayout/electron-main/keyboardLayoutMainService';
import { NativeParsedArgs } from 'vs/platform/environment/common/argv';
import { DisplayMainService, IDisplayMainService } from 'vs/platform/display/electron-main/displayMainService';
@@ -89,6 +88,7 @@ import { isEqualOrParent } from 'vs/base/common/extpath';
import { CancellationToken, CancellationTokenSource } from 'vs/base/common/cancellation';
import { IExtensionUrlTrustService } from 'vs/platform/extensionManagement/common/extensionUrlTrust';
import { ExtensionUrlTrustService } from 'vs/platform/extensionManagement/node/extensionUrlTrustService';
import { once } from 'vs/base/common/functional';
export class CodeApplication extends Disposable {
private windowsMainService: IWindowsMainService | undefined;
@@ -320,10 +320,10 @@ export class CodeApplication extends Disposable {
acceptShellEnv(shellEnv);
});
ipcMain.handle('vscode:writeNlsFile', (event, path: unknown, data: unknown) => {
ipcMain.handle('vscode:writeNlsFile', async (event, path: unknown, data: unknown) => {
const uri = this.validateNlsPath([path]);
if (!uri || typeof data !== 'string') {
return Promise.reject('Invalid operation (vscode:writeNlsFile)');
throw new Error('Invalid operation (vscode:writeNlsFile)');
}
return this.fileService.writeFile(uri, VSBuffer.fromString(data));
@@ -332,7 +332,7 @@ export class CodeApplication extends Disposable {
ipcMain.handle('vscode:readNlsFile', async (event, ...paths: unknown[]) => {
const uri = this.validateNlsPath(paths);
if (!uri) {
return Promise.reject('Invalid operation (vscode:readNlsFile)');
throw new Error('Invalid operation (vscode:readNlsFile)');
}
return (await this.fileService.readFile(uri)).value.toString();
@@ -896,6 +896,18 @@ export class CodeApplication extends Disposable {
// Signal phase: after window open
this.lifecycleMainService.phase = LifecycleMainPhase.AfterWindowOpen;
// Windows: install mutex
const win32MutexName = product.win32MutexName;
if (isWindows && win32MutexName) {
try {
const WindowsMutex = (require.__$__nodeRequire('windows-mutex') as typeof import('windows-mutex')).Mutex;
const mutex = new WindowsMutex(win32MutexName);
once(this.lifecycleMainService.onWillShutdown)(() => mutex.release());
} catch (e) {
this.logService.error(e);
}
}
// Remote Authorities
protocol.registerHttpProtocol(Schemas.vscodeRemoteResource, (request, callback) => {
callback({
@@ -8,8 +8,7 @@ import { IProcessEnvironment } from 'vs/base/common/platform';
import { parseArgs, OPTIONS } from 'vs/platform/environment/node/argv';
import { createServer, AddressInfo } from 'net';
import { ExtensionHostDebugBroadcastChannel } from 'vs/platform/debug/common/extensionHostDebugIpc';
import { IWindowsMainService } from 'vs/platform/windows/electron-main/windows';
import { OpenContext } from 'vs/platform/windows/electron-main/window';
import { IWindowsMainService, OpenContext } from 'vs/platform/windows/electron-main/windows';
export class ElectronExtensionHostDebugBroadcastChannel<TContext> extends ExtensionHostDebugBroadcastChannel<TContext> {
@@ -9,8 +9,7 @@ import { IProcessEnvironment, isMacintosh } from 'vs/base/common/platform';
import { NativeParsedArgs } from 'vs/platform/environment/common/argv';
import { createDecorator } from 'vs/platform/instantiation/common/instantiation';
import { IWindowSettings } from 'vs/platform/windows/common/windows';
import { OpenContext } from 'vs/platform/windows/electron-main/window';
import { IWindowsMainService, ICodeWindow } from 'vs/platform/windows/electron-main/windows';
import { IWindowsMainService, ICodeWindow, OpenContext } from 'vs/platform/windows/electron-main/windows';
import { whenDeleted } from 'vs/base/node/pfs';
import { IWorkspacesMainService } from 'vs/platform/workspaces/electron-main/workspacesMainService';
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
@@ -8,7 +8,6 @@ import { isMacintosh, language } from 'vs/base/common/platform';
import { IEnvironmentMainService } from 'vs/platform/environment/electron-main/environmentMainService';
import { app, Menu, MenuItem, BrowserWindow, MenuItemConstructorOptions, WebContents, KeyboardEvent } from 'electron';
import { getTitleBarStyle, INativeRunActionInWindowRequest, INativeRunKeybindingInWindowRequest, IWindowOpenable } from 'vs/platform/windows/common/windows';
import { OpenContext } from 'vs/platform/windows/electron-main/window';
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
import { IUpdateService, StateType } from 'vs/platform/update/common/update';
@@ -16,7 +15,7 @@ import product from 'vs/platform/product/common/product';
import { RunOnceScheduler } from 'vs/base/common/async';
import { ILogService } from 'vs/platform/log/common/log';
import { mnemonicMenuLabel } from 'vs/base/common/labels';
import { IWindowsMainService, IWindowsCountChangedEvent } from 'vs/platform/windows/electron-main/windows';
import { IWindowsMainService, IWindowsCountChangedEvent, OpenContext } from 'vs/platform/windows/electron-main/windows';
import { IWorkspacesHistoryMainService } from 'vs/platform/workspaces/electron-main/workspacesHistoryMainService';
import { IMenubarData, IMenubarKeybinding, MenubarMenuItem, isMenubarMenuItemSeparator, isMenubarMenuItemSubmenu, isMenubarMenuItemAction, IMenubarMenu, isMenubarMenuItemUriAction } from 'vs/platform/menubar/common/menubar';
import { URI } from 'vs/base/common/uri';
@@ -4,9 +4,8 @@
*--------------------------------------------------------------------------------------------*/
import { Emitter, Event } from 'vs/base/common/event';
import { IWindowsMainService, ICodeWindow } from 'vs/platform/windows/electron-main/windows';
import { IWindowsMainService, ICodeWindow, OpenContext } from 'vs/platform/windows/electron-main/windows';
import { MessageBoxOptions, MessageBoxReturnValue, shell, OpenDevToolsOptions, SaveDialogOptions, SaveDialogReturnValue, OpenDialogOptions, OpenDialogReturnValue, Menu, BrowserWindow, app, clipboard, powerMonitor, nativeTheme } from 'electron';
import { OpenContext } from 'vs/platform/windows/electron-main/window';
import { ILifecycleMainService } from 'vs/platform/lifecycle/electron-main/lifecycleMainService';
import { IOpenedWindow, IOpenWindowOptions, IWindowOpenable, IOpenEmptyWindowOptions, IColorScheme } from 'vs/platform/windows/common/windows';
import { INativeOpenDialogOptions } from 'vs/platform/dialogs/common/dialogs';
+54 -54
View File
@@ -18,38 +18,38 @@ export const WindowMinimumSize = {
};
export interface IBaseOpenWindowsOptions {
forceReuseWindow?: boolean;
readonly forceReuseWindow?: boolean;
}
export interface IOpenWindowOptions extends IBaseOpenWindowsOptions {
forceNewWindow?: boolean;
preferNewWindow?: boolean;
readonly forceNewWindow?: boolean;
readonly preferNewWindow?: boolean;
noRecentEntry?: boolean;
readonly noRecentEntry?: boolean;
addMode?: boolean;
readonly addMode?: boolean;
diffMode?: boolean;
gotoLineMode?: boolean;
readonly diffMode?: boolean;
readonly gotoLineMode?: boolean;
waitMarkerFileURI?: URI;
readonly waitMarkerFileURI?: URI;
}
export interface IAddFoldersRequest {
foldersToAdd: UriComponents[];
readonly foldersToAdd: UriComponents[];
}
export interface IOpenedWindow {
id: number;
workspace?: IWorkspaceIdentifier;
folderUri?: ISingleFolderWorkspaceIdentifier;
title: string;
filename?: string;
dirty: boolean;
readonly id: number;
readonly workspace?: IWorkspaceIdentifier;
readonly folderUri?: ISingleFolderWorkspaceIdentifier;
readonly title: string;
readonly filename?: string;
readonly dirty: boolean;
}
export interface IOpenEmptyWindowOptions extends IBaseOpenWindowsOptions {
remoteAuthority?: string;
readonly remoteAuthority?: string;
}
export type IWindowOpenable = IWorkspaceToOpen | IFolderToOpen | IFileToOpen;
@@ -59,15 +59,15 @@ export interface IBaseWindowOpenable {
}
export interface IWorkspaceToOpen extends IBaseWindowOpenable {
workspaceUri: URI;
readonly workspaceUri: URI;
}
export interface IFolderToOpen extends IBaseWindowOpenable {
folderUri: URI;
readonly folderUri: URI;
}
export interface IFileToOpen extends IBaseWindowOpenable {
fileUri: URI;
readonly fileUri: URI;
}
export function isWorkspaceToOpen(uriToOpen: IWindowOpenable): uriToOpen is IWorkspaceToOpen {
@@ -96,25 +96,25 @@ export function getMenuBarVisibility(configurationService: IConfigurationService
}
export interface IWindowsConfiguration {
window: IWindowSettings;
readonly window: IWindowSettings;
}
export interface IWindowSettings {
openFilesInNewWindow: 'on' | 'off' | 'default';
openFoldersInNewWindow: 'on' | 'off' | 'default';
openWithoutArgumentsInNewWindow: 'on' | 'off';
restoreWindows: 'preserve' | 'all' | 'folders' | 'one' | 'none';
restoreFullscreen: boolean;
zoomLevel: number;
titleBarStyle: 'native' | 'custom';
autoDetectHighContrast: boolean;
menuBarVisibility: MenuBarVisibility;
newWindowDimensions: 'default' | 'inherit' | 'offset' | 'maximized' | 'fullscreen';
nativeTabs: boolean;
nativeFullScreen: boolean;
enableMenuBarMnemonics: boolean;
closeWhenEmpty: boolean;
clickThroughInactive: boolean;
readonly openFilesInNewWindow: 'on' | 'off' | 'default';
readonly openFoldersInNewWindow: 'on' | 'off' | 'default';
readonly openWithoutArgumentsInNewWindow: 'on' | 'off';
readonly restoreWindows: 'preserve' | 'all' | 'folders' | 'one' | 'none';
readonly restoreFullscreen: boolean;
readonly zoomLevel: number;
readonly titleBarStyle: 'native' | 'custom';
readonly autoDetectHighContrast: boolean;
readonly menuBarVisibility: MenuBarVisibility;
readonly newWindowDimensions: 'default' | 'inherit' | 'offset' | 'maximized' | 'fullscreen';
readonly nativeTabs: boolean;
readonly nativeFullScreen: boolean;
readonly enableMenuBarMnemonics: boolean;
readonly closeWhenEmpty: boolean;
readonly clickThroughInactive: boolean;
}
export function getTitleBarStyle(configurationService: IConfigurationService): 'native' | 'custom' {
@@ -153,24 +153,24 @@ export interface IPath extends IPathData {
export interface IPathData {
// the file path to open within the instance
fileUri?: UriComponents;
readonly fileUri?: UriComponents;
// the line number in the file path to open
lineNumber?: number;
readonly lineNumber?: number;
// the column number in the file path to open
columnNumber?: number;
readonly columnNumber?: number;
// a hint that the file exists. if true, the
// file exists, if false it does not. with
// undefined the state is unknown.
exists?: boolean;
readonly exists?: boolean;
// Specifies if the file should be only be opened if it exists
openOnlyIfExists?: boolean;
readonly openOnlyIfExists?: boolean;
// Specifies an optional id to override the editor used to edit the resource, e.g. custom editor.
overrideId?: string;
readonly overrideId?: string;
}
export interface IPathsToWaitFor extends IPathsToWaitForData {
@@ -179,36 +179,36 @@ export interface IPathsToWaitFor extends IPathsToWaitForData {
}
interface IPathsToWaitForData {
paths: IPathData[];
waitMarkerFileUri: UriComponents;
readonly paths: IPathData[];
readonly waitMarkerFileUri: UriComponents;
}
export interface IOpenFileRequest {
filesToOpenOrCreate?: IPathData[];
filesToDiff?: IPathData[];
readonly filesToOpenOrCreate?: IPathData[];
readonly filesToDiff?: IPathData[];
}
/**
* Additional context for the request on native only.
*/
export interface INativeOpenFileRequest extends IOpenFileRequest {
termProgram?: string;
filesToWait?: IPathsToWaitForData;
readonly termProgram?: string;
readonly filesToWait?: IPathsToWaitForData;
}
export interface INativeRunActionInWindowRequest {
id: string;
from: 'menu' | 'touchbar' | 'mouse';
args?: any[];
readonly id: string;
readonly from: 'menu' | 'touchbar' | 'mouse';
readonly args?: any[];
}
export interface INativeRunKeybindingInWindowRequest {
userSettingsLabel: string;
readonly userSettingsLabel: string;
}
export interface IColorScheme {
dark: boolean;
highContrast: boolean;
readonly dark: boolean;
readonly highContrast: boolean;
}
export interface IWindowConfiguration {
@@ -224,7 +224,7 @@ export interface IWindowConfiguration {
}
export interface IOSConfiguration {
release: string;
readonly release: string;
}
export interface INativeWindowConfiguration extends IWindowConfiguration, NativeParsedArgs {
@@ -1,150 +0,0 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { URI } from 'vs/base/common/uri';
import * as platform from 'vs/base/common/platform';
import * as extpath from 'vs/base/common/extpath';
import { IWorkspaceIdentifier, IResolvedWorkspace, ISingleFolderWorkspaceIdentifier, isSingleFolderWorkspaceIdentifier, isWorkspaceIdentifier } from 'vs/platform/workspaces/common/workspaces';
import { extUriBiasedIgnorePathCase } from 'vs/base/common/resources';
export const enum OpenContext {
// opening when running from the command line
CLI,
// macOS only: opening from the dock (also when opening files to a running instance from desktop)
DOCK,
// opening from the main application window
MENU,
// opening from a file or folder dialog
DIALOG,
// opening from the OS's UI
DESKTOP,
// opening through the API
API
}
export interface IWindowContext {
openedWorkspace?: IWorkspaceIdentifier;
openedFolderUri?: URI;
extensionDevelopmentPath?: string[];
lastFocusTime: number;
}
export interface IBestWindowOrFolderOptions<W extends IWindowContext> {
windows: W[];
newWindow: boolean;
context: OpenContext;
fileUri?: URI;
codeSettingsFolder?: string;
localWorkspaceResolver: (workspace: IWorkspaceIdentifier) => IResolvedWorkspace | null;
}
export function findBestWindowOrFolderForFile<W extends IWindowContext>({ windows, newWindow, context, fileUri, localWorkspaceResolver: workspaceResolver }: IBestWindowOrFolderOptions<W>): W | undefined {
if (!newWindow && fileUri && (context === OpenContext.DESKTOP || context === OpenContext.CLI || context === OpenContext.DOCK)) {
const windowOnFilePath = findWindowOnFilePath(windows, fileUri, workspaceResolver);
if (windowOnFilePath) {
return windowOnFilePath;
}
}
return !newWindow ? getLastActiveWindow(windows) : undefined;
}
function findWindowOnFilePath<W extends IWindowContext>(windows: W[], fileUri: URI, localWorkspaceResolver: (workspace: IWorkspaceIdentifier) => IResolvedWorkspace | null): W | null {
// First check for windows with workspaces that have a parent folder of the provided path opened
for (const window of windows) {
const workspace = window.openedWorkspace;
if (workspace) {
const resolvedWorkspace = localWorkspaceResolver(workspace);
if (resolvedWorkspace) {
// workspace could be resolved: It's in the local file system
if (resolvedWorkspace.folders.some(folder => extUriBiasedIgnorePathCase.isEqualOrParent(fileUri, folder.uri))) {
return window;
}
} else {
// use the config path instead
if (extUriBiasedIgnorePathCase.isEqualOrParent(fileUri, workspace.configPath)) {
return window;
}
}
}
}
// Then go with single folder windows that are parent of the provided file path
const singleFolderWindowsOnFilePath = windows.filter(window => window.openedFolderUri && extUriBiasedIgnorePathCase.isEqualOrParent(fileUri, window.openedFolderUri));
if (singleFolderWindowsOnFilePath.length) {
return singleFolderWindowsOnFilePath.sort((a, b) => -(a.openedFolderUri!.path.length - b.openedFolderUri!.path.length))[0];
}
return null;
}
export function getLastActiveWindow<W extends IWindowContext>(windows: W[]): W | undefined {
const lastFocusedDate = Math.max.apply(Math, windows.map(window => window.lastFocusTime));
return windows.find(window => window.lastFocusTime === lastFocusedDate);
}
export function findWindowOnWorkspace<W extends IWindowContext>(windows: W[], workspace: (IWorkspaceIdentifier | ISingleFolderWorkspaceIdentifier)): W | null {
if (isSingleFolderWorkspaceIdentifier(workspace)) {
for (const window of windows) {
// match on folder
if (isSingleFolderWorkspaceIdentifier(workspace)) {
if (window.openedFolderUri && extUriBiasedIgnorePathCase.isEqual(window.openedFolderUri, workspace)) {
return window;
}
}
}
} else if (isWorkspaceIdentifier(workspace)) {
for (const window of windows) {
// match on workspace
if (window.openedWorkspace && window.openedWorkspace.id === workspace.id) {
return window;
}
}
}
return null;
}
export function findWindowOnExtensionDevelopmentPath<W extends IWindowContext>(windows: W[], extensionDevelopmentPaths: string[]): W | null {
const matches = (uriString: string): boolean => {
return extensionDevelopmentPaths.some(p => extpath.isEqual(p, uriString, !platform.isLinux /* ignorecase */));
};
for (const window of windows) {
// match on extension development path. The path can be one or more paths or uri strings, using paths.isEqual is not 100% correct but good enough
const currPaths = window.extensionDevelopmentPath;
if (currPaths?.some(p => matches(p))) {
return window;
}
}
return null;
}
export function findWindowOnWorkspaceOrFolderUri<W extends IWindowContext>(windows: W[], uri: URI | undefined): W | null {
if (!uri) {
return null;
}
for (const window of windows) {
// check for workspace config path
if (window.openedWorkspace && extUriBiasedIgnorePathCase.isEqual(window.openedWorkspace.configPath, uri)) {
return window;
}
// check for folder path
if (window.openedFolderUri && extUriBiasedIgnorePathCase.isEqual(window.openedFolderUri, uri)) {
return window;
}
}
return null;
}
@@ -4,7 +4,6 @@
*--------------------------------------------------------------------------------------------*/
import { IWindowOpenable, IOpenEmptyWindowOptions, INativeWindowConfiguration } from 'vs/platform/windows/common/windows';
import { OpenContext } from 'vs/platform/windows/electron-main/window';
import { NativeParsedArgs } from 'vs/platform/environment/common/argv';
import { Event } from 'vs/base/common/event';
import { createDecorator } from 'vs/platform/instantiation/common/instantiation';
@@ -16,6 +15,27 @@ import { Rectangle, BrowserWindow, WebContents } from 'electron';
import { IDisposable } from 'vs/base/common/lifecycle';
import { CancellationToken } from 'vs/base/common/cancellation';
export const enum OpenContext {
// opening when running from the command line
CLI,
// macOS only: opening from the dock (also when opening files to a running instance from desktop)
DOCK,
// opening from the main application window
MENU,
// opening from a file or folder dialog
DIALOG,
// opening from the OS's UI
DESKTOP,
// opening through the API
API
}
export interface IWindowState {
width?: number;
height?: number;
@@ -0,0 +1,79 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { URI } from 'vs/base/common/uri';
import { IWorkspaceIdentifier, IResolvedWorkspace } from 'vs/platform/workspaces/common/workspaces';
import { extUriBiasedIgnorePathCase } from 'vs/base/common/resources';
import { ICodeWindow } from 'vs/platform/windows/electron-main/windows';
export function findWindowOnFile(windows: ICodeWindow[], fileUri: URI, localWorkspaceResolver: (workspace: IWorkspaceIdentifier) => IResolvedWorkspace | null): ICodeWindow | undefined {
// First check for windows with workspaces that have a parent folder of the provided path opened
for (const window of windows) {
const workspace = window.openedWorkspace;
if (workspace) {
const resolvedWorkspace = localWorkspaceResolver(workspace);
// resolved workspace: folders are known and can be compared with
if (resolvedWorkspace) {
if (resolvedWorkspace.folders.some(folder => extUriBiasedIgnorePathCase.isEqualOrParent(fileUri, folder.uri))) {
return window;
}
}
// unresolved: can only compare with workspace location
else {
if (extUriBiasedIgnorePathCase.isEqualOrParent(fileUri, workspace.configPath)) {
return window;
}
}
}
}
// Then go with single folder windows that are parent of the provided file path
const singleFolderWindowsOnFilePath = windows.filter(window => window.openedFolderUri && extUriBiasedIgnorePathCase.isEqualOrParent(fileUri, window.openedFolderUri));
if (singleFolderWindowsOnFilePath.length) {
return singleFolderWindowsOnFilePath.sort((windowA, windowB) => -(windowA.openedFolderUri!.path.length - windowB.openedFolderUri!.path.length))[0];
}
return undefined;
}
export function findWindowOnWorkspaceOrFolder(windows: ICodeWindow[], folderOrWorkspaceConfigUri: URI): ICodeWindow | undefined {
for (const window of windows) {
// check for workspace config path
if (window.openedWorkspace && extUriBiasedIgnorePathCase.isEqual(window.openedWorkspace.configPath, folderOrWorkspaceConfigUri)) {
return window;
}
// check for folder path
if (window.openedFolderUri && extUriBiasedIgnorePathCase.isEqual(window.openedFolderUri, folderOrWorkspaceConfigUri)) {
return window;
}
}
return undefined;
}
export function findWindowOnExtensionDevelopmentPath(windows: ICodeWindow[], extensionDevelopmentPaths: string[]): ICodeWindow | undefined {
const matches = (uriString: string): boolean => {
return extensionDevelopmentPaths.some(path => extUriBiasedIgnorePathCase.isEqual(URI.file(path), URI.file(uriString)));
};
for (const window of windows) {
// match on extension development path. the path can be one or more paths
// so we check if any of the paths match on any of the provided ones
if (window.config?.extensionDevelopmentPath?.some(path => matches(path))) {
return window;
}
}
return undefined;
}
@@ -18,12 +18,12 @@ import { ILifecycleMainService, UnloadReason, LifecycleMainService, LifecycleMai
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
import { ILogService } from 'vs/platform/log/common/log';
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/electron-main/window';
import { findWindowOnFile, findWindowOnWorkspaceOrFolder, findWindowOnExtensionDevelopmentPath } from 'vs/platform/windows/electron-main/windowsFinder';
import { Emitter } from 'vs/base/common/event';
import product from 'vs/platform/product/common/product';
import { IWindowsMainService, IOpenConfiguration, IWindowsCountChangedEvent, ICodeWindow, IWindowState as ISingleWindowState, WindowMode, IOpenEmptyConfiguration } from 'vs/platform/windows/electron-main/windows';
import { IWindowsMainService, IOpenConfiguration, IWindowsCountChangedEvent, ICodeWindow, IWindowState as ISingleWindowState, WindowMode, IOpenEmptyConfiguration, OpenContext } from 'vs/platform/windows/electron-main/windows';
import { IWorkspacesHistoryMainService } from 'vs/platform/workspaces/electron-main/workspacesHistoryMainService';
import { IProcessEnvironment, isMacintosh, isWindows } from 'vs/base/common/platform';
import { IProcessEnvironment, isMacintosh } from 'vs/base/common/platform';
import { IWorkspaceIdentifier, isSingleFolderWorkspaceIdentifier, hasWorkspaceFileExtension, IRecent } from 'vs/platform/workspaces/common/workspaces';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { Schemas } from 'vs/base/common/network';
@@ -194,20 +194,6 @@ export class WindowsMainService extends Disposable implements IWindowsMainServic
}
this.lifecycleMainService.when(LifecycleMainPhase.Ready).then(() => this.registerListeners());
this.lifecycleMainService.when(LifecycleMainPhase.AfterWindowOpen).then(() => this.installWindowsMutex());
}
private installWindowsMutex(): void {
const win32MutexName = product.win32MutexName;
if (isWindows && win32MutexName) {
try {
const WindowsMutex = (require.__$__nodeRequire('windows-mutex') as typeof import('windows-mutex')).Mutex;
const mutex = new WindowsMutex(win32MutexName);
once(this.lifecycleMainService.onWillShutdown)(() => mutex.release());
} catch (e) {
this.logService.error(e);
}
}
}
private registerListeners(): void {
@@ -572,32 +558,40 @@ export class WindowsMainService extends Disposable implements IWindowsMainServic
// only look at the windows with correct authority
const windows = WindowsMainService.WINDOWS.filter(window => filesToOpen && window.remoteAuthority === filesToOpen.remoteAuthority);
const bestWindowOrFolder = findBestWindowOrFolderForFile({
windows,
newWindow: openFilesInNewWindow,
context: openConfig.context,
fileUri: fileToCheck?.fileUri,
localWorkspaceResolver: workspace => workspace.configPath.scheme === Schemas.file ? this.workspacesMainService.resolveLocalWorkspaceSync(workspace.configPath) : null
});
// figure out a good window to open the files in if any
// with a fallback to the last active window.
//
// in case `openFilesInNewWindow` is enforced, we skip
// this step.
let windowToUseForFiles: ICodeWindow | undefined = undefined;
if (fileToCheck?.fileUri && !openFilesInNewWindow) {
if (openConfig.context === OpenContext.DESKTOP || openConfig.context === OpenContext.CLI || openConfig.context === OpenContext.DOCK) {
windowToUseForFiles = findWindowOnFile(windows, fileToCheck.fileUri, workspace => workspace.configPath.scheme === Schemas.file ? this.workspacesMainService.resolveLocalWorkspaceSync(workspace.configPath) : null);
}
if (!windowToUseForFiles) {
windowToUseForFiles = this.doGetLastActiveWindow(windows);
}
}
// We found a window to open the files in
if (bestWindowOrFolder instanceof CodeWindow) {
if (windowToUseForFiles) {
// Window is workspace
if (bestWindowOrFolder.openedWorkspace) {
workspacesToOpen.push({ workspace: bestWindowOrFolder.openedWorkspace, remoteAuthority: bestWindowOrFolder.remoteAuthority });
if (windowToUseForFiles.openedWorkspace) {
workspacesToOpen.push({ workspace: windowToUseForFiles.openedWorkspace, remoteAuthority: windowToUseForFiles.remoteAuthority });
}
// Window is single folder
else if (bestWindowOrFolder.openedFolderUri) {
foldersToOpen.push({ folderUri: bestWindowOrFolder.openedFolderUri, remoteAuthority: bestWindowOrFolder.remoteAuthority });
else if (windowToUseForFiles.openedFolderUri) {
foldersToOpen.push({ folderUri: windowToUseForFiles.openedFolderUri, remoteAuthority: windowToUseForFiles.remoteAuthority });
}
// Window is empty
else {
// Do open files
const window = this.doOpenFilesInExistingWindow(openConfig, bestWindowOrFolder, filesToOpen);
const window = this.doOpenFilesInExistingWindow(openConfig, windowToUseForFiles, filesToOpen);
usedWindows.push(window);
// Reset `filesToOpen` because we handled them and also remember window we used
@@ -630,7 +624,7 @@ export class WindowsMainService extends Disposable implements IWindowsMainServic
if (allWorkspacesToOpen.length > 0) {
// Check for existing instances
const windowsOnWorkspace = coalesce(allWorkspacesToOpen.map(workspaceToOpen => findWindowOnWorkspace(WindowsMainService.WINDOWS, workspaceToOpen.workspace)));
const windowsOnWorkspace = coalesce(allWorkspacesToOpen.map(workspaceToOpen => findWindowOnWorkspaceOrFolder(WindowsMainService.WINDOWS, workspaceToOpen.workspace.configPath)));
if (windowsOnWorkspace.length > 0) {
const windowOnWorkspace = windowsOnWorkspace[0];
const filesToOpenInWindow = (filesToOpen?.remoteAuthority === windowOnWorkspace.remoteAuthority) ? filesToOpen : undefined;
@@ -676,7 +670,7 @@ export class WindowsMainService extends Disposable implements IWindowsMainServic
if (allFoldersToOpen.length > 0) {
// Check for existing instances
const windowsOnFolderPath = coalesce(allFoldersToOpen.map(folderToOpen => findWindowOnWorkspace(WindowsMainService.WINDOWS, folderToOpen.folderUri)));
const windowsOnFolderPath = coalesce(allFoldersToOpen.map(folderToOpen => findWindowOnWorkspaceOrFolder(WindowsMainService.WINDOWS, folderToOpen.folderUri)));
if (windowsOnFolderPath.length > 0) {
const windowOnFolderPath = windowsOnFolderPath[0];
const filesToOpenInWindow = filesToOpen?.remoteAuthority === windowOnFolderPath.remoteAuthority ? filesToOpen : undefined;
@@ -1347,7 +1341,7 @@ export class WindowsMainService extends Disposable implements IWindowsMainServic
cliArgs = cliArgs.filter(path => {
const uri = URI.file(path);
if (!!findWindowOnWorkspaceOrFolderUri(WindowsMainService.WINDOWS, uri)) {
if (!!findWindowOnWorkspaceOrFolder(WindowsMainService.WINDOWS, uri)) {
return false;
}
@@ -1356,7 +1350,7 @@ export class WindowsMainService extends Disposable implements IWindowsMainServic
folderUris = folderUris.filter(folderUriStr => {
const folderUri = this.argToUri(folderUriStr);
if (!!findWindowOnWorkspaceOrFolderUri(WindowsMainService.WINDOWS, folderUri)) {
if (folderUri && !!findWindowOnWorkspaceOrFolder(WindowsMainService.WINDOWS, folderUri)) {
return false;
}
@@ -1365,7 +1359,7 @@ export class WindowsMainService extends Disposable implements IWindowsMainServic
fileUris = fileUris.filter(fileUriStr => {
const fileUri = this.argToUri(fileUriStr);
if (!!findWindowOnWorkspaceOrFolderUri(WindowsMainService.WINDOWS, fileUri)) {
if (fileUri && !!findWindowOnWorkspaceOrFolder(WindowsMainService.WINDOWS, fileUri)) {
return false;
}
@@ -1699,11 +1693,17 @@ export class WindowsMainService extends Disposable implements IWindowsMainServic
}
getLastActiveWindow(): ICodeWindow | undefined {
return getLastActiveWindow(WindowsMainService.WINDOWS);
return this.doGetLastActiveWindow(WindowsMainService.WINDOWS);
}
private getLastActiveWindowForAuthority(remoteAuthority: string | undefined): ICodeWindow | undefined {
return getLastActiveWindow(WindowsMainService.WINDOWS.filter(window => window.remoteAuthority === remoteAuthority));
return this.doGetLastActiveWindow(WindowsMainService.WINDOWS.filter(window => window.remoteAuthority === remoteAuthority));
}
private doGetLastActiveWindow(windows: ICodeWindow[]): ICodeWindow | undefined {
const lastFocusedDate = Math.max.apply(Math, windows.map(window => window.lastFocusTime));
return windows.find(window => window.lastFocusTime === lastFocusedDate);
}
sendToFocused(channel: string, ...args: any[]): void {
@@ -10,22 +10,22 @@ import { IWindowState, IWindowsState } from 'vs/platform/windows/electron-main/w
export type WindowsStateStorageData = object;
interface ISerializedWindowsState {
lastActiveWindow?: ISerializedWindowState;
lastPluginDevelopmentHostWindow?: ISerializedWindowState;
openedWindows: ISerializedWindowState[];
readonly lastActiveWindow?: ISerializedWindowState;
readonly lastPluginDevelopmentHostWindow?: ISerializedWindowState;
readonly openedWindows: ISerializedWindowState[];
}
interface ISerializedWindowState {
workspaceIdentifier?: { id: string; configURIPath: string };
folder?: string;
backupPath?: string;
remoteAuthority?: string;
uiState: IWindowUIState;
readonly workspaceIdentifier?: { id: string; configURIPath: string };
readonly folder?: string;
readonly backupPath?: string;
readonly remoteAuthority?: string;
readonly uiState: IWindowUIState;
// deprecated
folderUri?: UriComponents;
folderPath?: string;
workspace?: { id: string; configPath: string };
readonly folderUri?: UriComponents;
readonly folderPath?: string;
readonly workspace?: { id: string; configPath: string };
}
export function restoreWindowsState(data: WindowsStateStorageData | undefined): IWindowsState {
@@ -2,14 +2,22 @@
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as assert from 'assert';
import * as path from 'vs/base/common/path';
import { IBestWindowOrFolderOptions, IWindowContext, findBestWindowOrFolderForFile, OpenContext } from 'vs/platform/windows/electron-main/window';
import { findWindowOnFile } from 'vs/platform/windows/electron-main/windowsFinder';
import { ICodeWindow, IWindowState } from 'vs/platform/windows/electron-main/windows';
import { IWorkspaceIdentifier } from 'vs/platform/workspaces/common/workspaces';
import { toWorkspaceFolders } from 'vs/platform/workspace/common/workspace';
import { URI } from 'vs/base/common/uri';
import { getPathFromAmdModule } from 'vs/base/common/amd';
import { extUriBiasedIgnorePathCase } from 'vs/base/common/resources';
import { CancellationToken } from 'vs/base/common/cancellation';
import { Event } from 'vs/base/common/event';
import { UriDto } from 'vs/base/common/types';
import { ICommandAction } from 'vs/platform/actions/common/actions';
import { NativeParsedArgs } from 'vs/platform/environment/common/argv';
import { INativeWindowConfiguration } from 'vs/platform/windows/common/windows';
const fixturesFolder = getPathFromAmdModule(require, './fixtures');
@@ -19,22 +27,56 @@ const testWorkspace: IWorkspaceIdentifier = {
};
const testWorkspaceFolders = toWorkspaceFolders([{ path: path.join(fixturesFolder, 'vscode_workspace_1_folder') }, { path: path.join(fixturesFolder, 'vscode_workspace_2_folder') }], testWorkspace.configPath, extUriBiasedIgnorePathCase);
const localWorkspaceResolver = (workspace: any) => { return workspace === testWorkspace ? { id: testWorkspace.id, configPath: workspace.configPath, folders: testWorkspaceFolders } : null; };
function options(custom?: Partial<IBestWindowOrFolderOptions<IWindowContext>>): IBestWindowOrFolderOptions<IWindowContext> {
return {
windows: [],
newWindow: false,
context: OpenContext.CLI,
codeSettingsFolder: '_vscode',
localWorkspaceResolver: workspace => { return workspace === testWorkspace ? { id: testWorkspace.id, configPath: workspace.configPath, folders: testWorkspaceFolders } : null; },
...custom
function createTestCodeWindow(options: { lastFocusTime: number, openedFolderUri?: URI, openedWorkspace?: IWorkspaceIdentifier }): ICodeWindow {
return new class implements ICodeWindow {
onLoad: Event<void> = Event.None;
onReady: Event<void> = Event.None;
onClose: Event<void> = Event.None;
onDestroy: Event<void> = Event.None;
whenClosedOrLoaded: Promise<void> = Promise.resolve();
id: number = -1;
win: Electron.BrowserWindow = undefined!;
config: INativeWindowConfiguration | undefined;
openedFolderUri = options.openedFolderUri;
openedWorkspace = options.openedWorkspace;
backupPath?: string | undefined;
remoteAuthority?: string | undefined;
isExtensionDevelopmentHost = false;
isExtensionTestHost = false;
lastFocusTime = options.lastFocusTime;
isFullScreen = false;
isReady = true;
hasHiddenTitleBarStyle = false;
ready(): Promise<ICodeWindow> { throw new Error('Method not implemented.'); }
setReady(): void { throw new Error('Method not implemented.'); }
addTabbedWindow(window: ICodeWindow): void { throw new Error('Method not implemented.'); }
load(config: INativeWindowConfiguration, isReload?: boolean): void { throw new Error('Method not implemented.'); }
reload(cli?: NativeParsedArgs): void { throw new Error('Method not implemented.'); }
focus(options?: { force: boolean; }): void { throw new Error('Method not implemented.'); }
close(): void { throw new Error('Method not implemented.'); }
getBounds(): Electron.Rectangle { throw new Error('Method not implemented.'); }
send(channel: string, ...args: any[]): void { throw new Error('Method not implemented.'); }
sendWhenReady(channel: string, token: CancellationToken, ...args: any[]): void { throw new Error('Method not implemented.'); }
toggleFullScreen(): void { throw new Error('Method not implemented.'); }
isMinimized(): boolean { throw new Error('Method not implemented.'); }
setRepresentedFilename(name: string): void { throw new Error('Method not implemented.'); }
getRepresentedFilename(): string | undefined { throw new Error('Method not implemented.'); }
setDocumentEdited(edited: boolean): void { throw new Error('Method not implemented.'); }
isDocumentEdited(): boolean { throw new Error('Method not implemented.'); }
handleTitleDoubleClick(): void { throw new Error('Method not implemented.'); }
updateTouchBar(items: UriDto<ICommandAction>[][]): void { throw new Error('Method not implemented.'); }
serializeWindowState(): IWindowState { throw new Error('Method not implemented'); }
dispose(): void { }
};
}
const vscodeFolderWindow: IWindowContext = { lastFocusTime: 1, openedFolderUri: URI.file(path.join(fixturesFolder, 'vscode_folder')) };
const lastActiveWindow: IWindowContext = { lastFocusTime: 3, openedFolderUri: undefined };
const noVscodeFolderWindow: IWindowContext = { lastFocusTime: 2, openedFolderUri: URI.file(path.join(fixturesFolder, 'no_vscode_folder')) };
const windows: IWindowContext[] = [
const vscodeFolderWindow: ICodeWindow = createTestCodeWindow({ lastFocusTime: 1, openedFolderUri: URI.file(path.join(fixturesFolder, 'vscode_folder')) });
const lastActiveWindow: ICodeWindow = createTestCodeWindow({ lastFocusTime: 3, openedFolderUri: undefined });
const noVscodeFolderWindow: ICodeWindow = createTestCodeWindow({ lastFocusTime: 2, openedFolderUri: URI.file(path.join(fixturesFolder, 'no_vscode_folder')) });
const windows: ICodeWindow[] = [
vscodeFolderWindow,
lastActiveWindow,
noVscodeFolderWindow,
@@ -43,86 +85,27 @@ const windows: IWindowContext[] = [
suite('WindowsFinder', () => {
test('New window without folder when no windows exist', () => {
assert.equal(findBestWindowOrFolderForFile(options()), null);
assert.equal(findBestWindowOrFolderForFile(options({
fileUri: URI.file(path.join(fixturesFolder, 'no_vscode_folder', 'file.txt'))
})), null);
assert.equal(findBestWindowOrFolderForFile(options({
fileUri: URI.file(path.join(fixturesFolder, 'vscode_folder', 'file.txt')),
newWindow: true
})), null);
assert.equal(findBestWindowOrFolderForFile(options({
fileUri: URI.file(path.join(fixturesFolder, 'vscode_folder', 'file.txt')),
})), null);
assert.equal(findBestWindowOrFolderForFile(options({
fileUri: URI.file(path.join(fixturesFolder, 'vscode_folder', 'file.txt')),
context: OpenContext.API
})), null);
assert.equal(findBestWindowOrFolderForFile(options({
fileUri: URI.file(path.join(fixturesFolder, 'vscode_folder', 'file.txt'))
})), null);
assert.equal(findBestWindowOrFolderForFile(options({
fileUri: URI.file(path.join(fixturesFolder, 'vscode_folder', 'new_folder', 'new_file.txt'))
})), null);
});
test('New window without folder when windows exist', () => {
assert.equal(findBestWindowOrFolderForFile(options({
windows,
fileUri: URI.file(path.join(fixturesFolder, 'no_vscode_folder', 'file.txt')),
newWindow: true
})), null);
});
test('Last active window', () => {
assert.equal(findBestWindowOrFolderForFile(options({
windows
})), lastActiveWindow);
assert.equal(findBestWindowOrFolderForFile(options({
windows,
fileUri: URI.file(path.join(fixturesFolder, 'no_vscode_folder2', 'file.txt'))
})), lastActiveWindow);
assert.equal(findBestWindowOrFolderForFile(options({
windows: [lastActiveWindow, noVscodeFolderWindow],
fileUri: URI.file(path.join(fixturesFolder, 'vscode_folder', 'file.txt')),
})), lastActiveWindow);
assert.equal(findBestWindowOrFolderForFile(options({
windows,
fileUri: URI.file(path.join(fixturesFolder, 'no_vscode_folder', 'file.txt')),
context: OpenContext.API
})), lastActiveWindow);
assert.equal(findWindowOnFile([], URI.file('nonexisting'), localWorkspaceResolver), null);
assert.equal(findWindowOnFile([], URI.file(path.join(fixturesFolder, 'no_vscode_folder', 'file.txt')), localWorkspaceResolver), null);
});
test('Existing window with folder', () => {
assert.equal(findBestWindowOrFolderForFile(options({
windows,
fileUri: URI.file(path.join(fixturesFolder, 'no_vscode_folder', 'file.txt'))
})), noVscodeFolderWindow);
assert.equal(findBestWindowOrFolderForFile(options({
windows,
fileUri: URI.file(path.join(fixturesFolder, 'vscode_folder', 'file.txt'))
})), vscodeFolderWindow);
const window: IWindowContext = { lastFocusTime: 1, openedFolderUri: URI.file(path.join(fixturesFolder, 'vscode_folder', 'nested_folder')) };
assert.equal(findBestWindowOrFolderForFile(options({
windows: [window],
fileUri: URI.file(path.join(fixturesFolder, 'vscode_folder', 'nested_folder', 'subfolder', 'file.txt'))
})), window);
assert.equal(findWindowOnFile(windows, URI.file(path.join(fixturesFolder, 'no_vscode_folder', 'file.txt')), localWorkspaceResolver), noVscodeFolderWindow);
assert.equal(findWindowOnFile(windows, URI.file(path.join(fixturesFolder, 'vscode_folder', 'file.txt')), localWorkspaceResolver), vscodeFolderWindow);
const window: ICodeWindow = createTestCodeWindow({ lastFocusTime: 1, openedFolderUri: URI.file(path.join(fixturesFolder, 'vscode_folder', 'nested_folder')) });
assert.equal(findWindowOnFile([window], URI.file(path.join(fixturesFolder, 'vscode_folder', 'nested_folder', 'subfolder', 'file.txt')), localWorkspaceResolver), window);
});
test('More specific existing window wins', () => {
const window: IWindowContext = { lastFocusTime: 2, openedFolderUri: URI.file(path.join(fixturesFolder, 'no_vscode_folder')) };
const nestedFolderWindow: IWindowContext = { lastFocusTime: 1, openedFolderUri: URI.file(path.join(fixturesFolder, 'no_vscode_folder', 'nested_folder')) };
assert.equal(findBestWindowOrFolderForFile(options({
windows: [window, nestedFolderWindow],
fileUri: URI.file(path.join(fixturesFolder, 'no_vscode_folder', 'nested_folder', 'subfolder', 'file.txt'))
})), nestedFolderWindow);
const window: ICodeWindow = createTestCodeWindow({ lastFocusTime: 2, openedFolderUri: URI.file(path.join(fixturesFolder, 'no_vscode_folder')) });
const nestedFolderWindow: ICodeWindow = createTestCodeWindow({ lastFocusTime: 1, openedFolderUri: URI.file(path.join(fixturesFolder, 'no_vscode_folder', 'nested_folder')) });
assert.equal(findWindowOnFile([window, nestedFolderWindow], URI.file(path.join(fixturesFolder, 'no_vscode_folder', 'nested_folder', 'subfolder', 'file.txt')), localWorkspaceResolver), nestedFolderWindow);
});
test('Workspace folder wins', () => {
const window: IWindowContext = { lastFocusTime: 1, openedWorkspace: testWorkspace };
assert.equal(findBestWindowOrFolderForFile(options({
windows: [window],
fileUri: URI.file(path.join(fixturesFolder, 'vscode_workspace_2_folder', 'nested_vscode_folder', 'subfolder', 'file.txt'))
})), window);
const window: ICodeWindow = createTestCodeWindow({ lastFocusTime: 1, openedWorkspace: testWorkspace });
assert.equal(findWindowOnFile([window], URI.file(path.join(fixturesFolder, 'vscode_workspace_2_folder', 'nested_vscode_folder', 'subfolder', 'file.txt')), localWorkspaceResolver), window);
});
});
@@ -26,7 +26,7 @@ import { MessageBoxOptions, BrowserWindow } from 'electron';
import { withNullAsUndefined } from 'vs/base/common/types';
import { IBackupMainService } from 'vs/platform/backup/electron-main/backup';
import { IDialogMainService } from 'vs/platform/dialogs/electron-main/dialogMainService';
import { findWindowOnWorkspace } from 'vs/platform/windows/electron-main/window';
import { findWindowOnWorkspaceOrFolder } from 'vs/platform/windows/electron-main/windowsFinder';
export const IWorkspacesMainService = createDecorator<IWorkspacesMainService>('workspacesMainService');
@@ -266,22 +266,22 @@ export class WorkspacesMainService extends Disposable implements IWorkspacesMain
return result;
}
private async isValidTargetWorkspacePath(window: ICodeWindow, windows: ICodeWindow[], path?: URI): Promise<boolean> {
if (!path) {
private async isValidTargetWorkspacePath(window: ICodeWindow, windows: ICodeWindow[], workspacePath?: URI): Promise<boolean> {
if (!workspacePath) {
return true;
}
if (window.openedWorkspace && extUriBiasedIgnorePathCase.isEqual(window.openedWorkspace.configPath, path)) {
if (window.openedWorkspace && extUriBiasedIgnorePathCase.isEqual(window.openedWorkspace.configPath, workspacePath)) {
return false; // window is already opened on a workspace with that path
}
// Prevent overwriting a workspace that is currently opened in another window
if (findWindowOnWorkspace(windows, getWorkspaceIdentifier(path))) {
if (findWindowOnWorkspaceOrFolder(windows, workspacePath)) {
const options: MessageBoxOptions = {
title: product.nameLong,
type: 'info',
buttons: [localize('ok', "OK")],
message: localize('workspaceOpenedMessage', "Unable to save workspace '{0}'", basename(path)),
message: localize('workspaceOpenedMessage', "Unable to save workspace '{0}'", basename(workspacePath)),
detail: localize('workspaceOpenedDetail', "The workspace is already opened in another window. Please close that window first and then try again."),
noLink: true
};