mirror of
https://github.com/microsoft/vscode.git
synced 2026-09-02 14:25:42 +01:00
windows - first cut cleanup of windows finding logic
This commit is contained in:
@@ -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';
|
||||
|
||||
@@ -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';
|
||||
|
||||
@@ -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
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user