Merge commit 'be3e8236086165e5e45a5a10783823874b3f3ebd' as 'lib/vscode'

This commit is contained in:
Joe Previte
2020-12-15 15:52:33 -07:00
4649 changed files with 1311795 additions and 0 deletions

View File

@@ -0,0 +1,56 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { Event } from 'vs/base/common/event';
import { Disposable, DisposableStore } from 'vs/base/common/lifecycle';
import { CancelablePromise, createCancelablePromise } from 'vs/base/common/async';
export class ActiveWindowManager extends Disposable {
private readonly disposables = this._register(new DisposableStore());
private firstActiveWindowIdPromise: CancelablePromise<number | undefined> | undefined;
private activeWindowId: number | undefined;
constructor({ onDidOpenWindow, onDidFocusWindow, getActiveWindowId }: {
onDidOpenWindow: Event<number>,
onDidFocusWindow: Event<number>,
getActiveWindowId(): Promise<number | undefined>
}) {
super();
// remember last active window id upon events
const onActiveWindowChange = Event.latch(Event.any(onDidOpenWindow, onDidFocusWindow));
onActiveWindowChange(this.setActiveWindow, this, this.disposables);
// resolve current active window
this.firstActiveWindowIdPromise = createCancelablePromise(() => getActiveWindowId());
(async () => {
try {
const windowId = await this.firstActiveWindowIdPromise;
this.activeWindowId = (typeof this.activeWindowId === 'number') ? this.activeWindowId : windowId;
} catch (error) {
// ignore
} finally {
this.firstActiveWindowIdPromise = undefined;
}
})();
}
private setActiveWindow(windowId: number | undefined) {
if (this.firstActiveWindowIdPromise) {
this.firstActiveWindowIdPromise.cancel();
this.firstActiveWindowIdPromise = undefined;
}
this.activeWindowId = windowId;
}
async getActiveClientId(): Promise<string | undefined> {
const id = this.firstActiveWindowIdPromise ? (await this.firstActiveWindowIdPromise) : this.activeWindowId;
return `window:${id}`;
}
}

View File

@@ -0,0 +1,267 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { isMacintosh, isLinux, isWeb, IProcessEnvironment } from 'vs/base/common/platform';
import { IEnvironmentService } from 'vs/platform/environment/common/environment';
import { URI, UriComponents } from 'vs/base/common/uri';
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
import { IWorkspaceIdentifier, ISingleFolderWorkspaceIdentifier } from 'vs/platform/workspaces/common/workspaces';
import { NativeParsedArgs } from 'vs/platform/environment/common/argv';
import { LogLevel } from 'vs/platform/log/common/log';
import { ExportData } from 'vs/base/common/performance';
export const WindowMinimumSize = {
WIDTH: 400,
WIDTH_WITH_VERTICAL_PANEL: 600,
HEIGHT: 270
};
export interface IBaseOpenWindowsOptions {
forceReuseWindow?: boolean;
}
export interface IOpenWindowOptions extends IBaseOpenWindowsOptions {
forceNewWindow?: boolean;
preferNewWindow?: boolean;
noRecentEntry?: boolean;
addMode?: boolean;
diffMode?: boolean;
gotoLineMode?: boolean;
waitMarkerFileURI?: URI;
}
export interface IAddFoldersRequest {
foldersToAdd: UriComponents[];
}
export interface IOpenedWindow {
id: number;
workspace?: IWorkspaceIdentifier;
folderUri?: ISingleFolderWorkspaceIdentifier;
title: string;
filename?: string;
dirty: boolean;
}
export interface IOpenEmptyWindowOptions extends IBaseOpenWindowsOptions {
remoteAuthority?: string;
}
export type IWindowOpenable = IWorkspaceToOpen | IFolderToOpen | IFileToOpen;
export interface IBaseWindowOpenable {
label?: string;
}
export interface IWorkspaceToOpen extends IBaseWindowOpenable {
workspaceUri: URI;
}
export interface IFolderToOpen extends IBaseWindowOpenable {
folderUri: URI;
}
export interface IFileToOpen extends IBaseWindowOpenable {
fileUri: URI;
}
export function isWorkspaceToOpen(uriToOpen: IWindowOpenable): uriToOpen is IWorkspaceToOpen {
return !!(uriToOpen as IWorkspaceToOpen).workspaceUri;
}
export function isFolderToOpen(uriToOpen: IWindowOpenable): uriToOpen is IFolderToOpen {
return !!(uriToOpen as IFolderToOpen).folderUri;
}
export function isFileToOpen(uriToOpen: IWindowOpenable): uriToOpen is IFileToOpen {
return !!(uriToOpen as IFileToOpen).fileUri;
}
export type MenuBarVisibility = 'default' | 'visible' | 'toggle' | 'hidden' | 'compact';
export function getMenuBarVisibility(configurationService: IConfigurationService, environment: IEnvironmentService, isExtensionDevelopment = environment.isExtensionDevelopment): MenuBarVisibility {
const titleBarStyle = getTitleBarStyle(configurationService, environment, isExtensionDevelopment);
const menuBarVisibility = configurationService.getValue<MenuBarVisibility>('window.menuBarVisibility');
if (titleBarStyle === 'native' && menuBarVisibility === 'compact') {
return 'default';
} else {
return menuBarVisibility;
}
}
export interface IWindowsConfiguration {
window: IWindowSettings;
}
export interface IWindowSettings {
openFilesInNewWindow: 'on' | 'off' | 'default';
openFoldersInNewWindow: 'on' | 'off' | 'default';
openWithoutArgumentsInNewWindow: 'on' | 'off';
restoreWindows: '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;
enableExperimentalProxyLoginDialog: boolean;
}
export function getTitleBarStyle(configurationService: IConfigurationService, environment: IEnvironmentService, isExtensionDevelopment = environment.isExtensionDevelopment): 'native' | 'custom' {
if (isWeb) {
return 'custom';
}
const configuration = configurationService.getValue<IWindowSettings>('window');
const isDev = !environment.isBuilt || isExtensionDevelopment;
if (isMacintosh && isDev) {
return 'native'; // not enabled when developing due to https://github.com/electron/electron/issues/3647
}
if (configuration) {
const useNativeTabs = isMacintosh && configuration.nativeTabs === true;
if (useNativeTabs) {
return 'native'; // native tabs on sierra do not work with custom title style
}
const useSimpleFullScreen = isMacintosh && configuration.nativeFullScreen === false;
if (useSimpleFullScreen) {
return 'native'; // simple fullscreen does not work well with custom title style (https://github.com/microsoft/vscode/issues/63291)
}
const style = configuration.titleBarStyle;
if (style === 'native' || style === 'custom') {
return style;
}
}
return isLinux ? 'native' : 'custom'; // default to custom on all macOS and Windows
}
export interface IPath extends IPathData {
// the file path to open within the instance
fileUri?: URI;
}
export interface IPathData {
// the file path to open within the instance
fileUri?: UriComponents;
// the line number in the file path to open
lineNumber?: number;
// the column number in the file path to open
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;
// Specifies if the file should be only be opened if it exists
openOnlyIfExists?: boolean;
// Specifies an optional id to override the editor used to edit the resource, e.g. custom editor.
overrideId?: string;
}
export interface IPathsToWaitFor extends IPathsToWaitForData {
paths: IPath[];
waitMarkerFileUri: URI;
}
interface IPathsToWaitForData {
paths: IPathData[];
waitMarkerFileUri: UriComponents;
}
export interface IOpenFileRequest {
filesToOpenOrCreate?: IPathData[];
filesToDiff?: IPathData[];
}
/**
* Additional context for the request on native only.
*/
export interface INativeOpenFileRequest extends IOpenFileRequest {
termProgram?: string;
filesToWait?: IPathsToWaitForData;
}
export interface INativeRunActionInWindowRequest {
id: string;
from: 'menu' | 'touchbar' | 'mouse';
args?: any[];
}
export interface INativeRunKeybindingInWindowRequest {
userSettingsLabel: string;
}
export interface IColorScheme {
dark: boolean;
highContrast: boolean;
}
export interface IWindowConfiguration {
sessionId: string;
remoteAuthority?: string;
colorScheme: IColorScheme;
autoDetectHighContrast?: boolean;
filesToOpenOrCreate?: IPath[];
filesToDiff?: IPath[];
}
export interface INativeWindowConfiguration extends IWindowConfiguration, NativeParsedArgs {
mainPid: number;
windowId: number;
machineId: string;
appRoot: string;
execPath: string;
backupPath?: string;
nodeCachedDataDir?: string;
partsSplashPath: string;
workspace?: IWorkspaceIdentifier;
folderUri?: ISingleFolderWorkspaceIdentifier;
isInitialStartup?: boolean;
logLevel: LogLevel;
zoomLevel?: number;
fullscreen?: boolean;
maximized?: boolean;
accessibilitySupport?: boolean;
perfEntries: ExportData;
userEnv: IProcessEnvironment;
filesToWait?: IPathsToWaitFor;
}
/**
* According to Electron docs: `scale := 1.2 ^ level`.
* https://github.com/electron/electron/blob/master/docs/api/web-contents.md#contentssetzoomlevellevel
*/
export function zoomLevelToZoomFactor(zoomLevel = 0): number {
return Math.pow(1.2, zoomLevel);
}

View File

@@ -0,0 +1,142 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { IWindowOpenable, IOpenEmptyWindowOptions, INativeWindowConfiguration } from 'vs/platform/windows/common/windows';
import { OpenContext } from 'vs/platform/windows/node/window';
import { NativeParsedArgs } from 'vs/platform/environment/common/argv';
import { Event } from 'vs/base/common/event';
import { createDecorator } from 'vs/platform/instantiation/common/instantiation';
import { IProcessEnvironment } from 'vs/base/common/platform';
import { IWorkspaceIdentifier } from 'vs/platform/workspaces/common/workspaces';
import { ISerializableCommandAction } from 'vs/platform/actions/common/actions';
import { URI } from 'vs/base/common/uri';
import { Rectangle, BrowserWindow } from 'electron';
import { IDisposable } from 'vs/base/common/lifecycle';
export interface IWindowState {
width?: number;
height?: number;
x?: number;
y?: number;
mode?: WindowMode;
display?: number;
}
export const enum WindowMode {
Maximized,
Normal,
Minimized, // not used anymore, but also cannot remove due to existing stored UI state (needs migration)
Fullscreen
}
export interface ICodeWindow extends IDisposable {
readonly whenClosedOrLoaded: Promise<void>;
readonly id: number;
readonly win: BrowserWindow;
readonly config: INativeWindowConfiguration | undefined;
readonly openedFolderUri?: URI;
readonly openedWorkspace?: IWorkspaceIdentifier;
readonly backupPath?: string;
readonly remoteAuthority?: string;
readonly isExtensionDevelopmentHost: boolean;
readonly isExtensionTestHost: boolean;
readonly lastFocusTime: number;
readonly isReady: boolean;
ready(): Promise<ICodeWindow>;
setReady(): void;
readonly hasHiddenTitleBarStyle: boolean;
addTabbedWindow(window: ICodeWindow): void;
load(config: INativeWindowConfiguration, isReload?: boolean): void;
reload(configuration?: INativeWindowConfiguration, cli?: NativeParsedArgs): void;
focus(options?: { force: boolean }): void;
close(): void;
getBounds(): Rectangle;
send(channel: string, ...args: any[]): void;
sendWhenReady(channel: string, ...args: any[]): void;
readonly isFullScreen: boolean;
toggleFullScreen(): void;
isMinimized(): boolean;
setRepresentedFilename(name: string): void;
getRepresentedFilename(): string | undefined;
setDocumentEdited(edited: boolean): void;
isDocumentEdited(): boolean;
handleTitleDoubleClick(): void;
updateTouchBar(items: ISerializableCommandAction[][]): void;
serializeWindowState(): IWindowState;
}
export const IWindowsMainService = createDecorator<IWindowsMainService>('windowsMainService');
export interface IWindowsCountChangedEvent {
readonly oldCount: number;
readonly newCount: number;
}
export interface IWindowsMainService {
readonly _serviceBrand: undefined;
readonly onWindowOpened: Event<ICodeWindow>;
readonly onWindowReady: Event<ICodeWindow>;
readonly onWindowsCountChanged: Event<IWindowsCountChangedEvent>;
open(openConfig: IOpenConfiguration): ICodeWindow[];
openEmptyWindow(openConfig: IOpenEmptyConfiguration, options?: IOpenEmptyWindowOptions): ICodeWindow[];
openExtensionDevelopmentHostWindow(extensionDevelopmentPath: string[], openConfig: IOpenConfiguration): ICodeWindow[];
sendToFocused(channel: string, ...args: any[]): void;
sendToAll(channel: string, payload?: any, windowIdsToIgnore?: number[]): void;
getFocusedWindow(): ICodeWindow | undefined;
getLastActiveWindow(): ICodeWindow | undefined;
getWindowById(windowId: number): ICodeWindow | undefined;
getWindows(): ICodeWindow[];
getWindowCount(): number;
}
export interface IBaseOpenConfiguration {
readonly context: OpenContext;
readonly contextWindowId?: number;
}
export interface IOpenConfiguration extends IBaseOpenConfiguration {
readonly cli: NativeParsedArgs;
readonly userEnv?: IProcessEnvironment;
readonly urisToOpen?: IWindowOpenable[];
readonly waitMarkerFileURI?: URI;
readonly preferNewWindow?: boolean;
readonly forceNewWindow?: boolean;
readonly forceNewTabbedWindow?: boolean;
readonly forceReuseWindow?: boolean;
readonly forceEmpty?: boolean;
readonly diffMode?: boolean;
addMode?: boolean;
readonly gotoLineMode?: boolean;
readonly initialStartup?: boolean;
readonly noRecentEntry?: boolean;
}
export interface IOpenEmptyConfiguration extends IBaseOpenConfiguration { }

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,93 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { URI, UriComponents } from 'vs/base/common/uri';
import { IWindowState as IWindowUIState } from 'vs/platform/windows/electron-main/windows';
import { IWindowState, IWindowsState } from 'vs/platform/windows/electron-main/windowsMainService';
export type WindowsStateStorageData = object;
interface ISerializedWindowsState {
lastActiveWindow?: ISerializedWindowState;
lastPluginDevelopmentHostWindow?: ISerializedWindowState;
openedWindows: ISerializedWindowState[];
}
interface ISerializedWindowState {
workspaceIdentifier?: { id: string; configURIPath: string };
folder?: string;
backupPath?: string;
remoteAuthority?: string;
uiState: IWindowUIState;
// deprecated
folderUri?: UriComponents;
folderPath?: string;
workspace?: { id: string; configPath: string };
}
export function restoreWindowsState(data: WindowsStateStorageData | undefined): IWindowsState {
const result: IWindowsState = { openedWindows: [] };
const windowsState = data as ISerializedWindowsState || { openedWindows: [] };
if (windowsState.lastActiveWindow) {
result.lastActiveWindow = restoreWindowState(windowsState.lastActiveWindow);
}
if (windowsState.lastPluginDevelopmentHostWindow) {
result.lastPluginDevelopmentHostWindow = restoreWindowState(windowsState.lastPluginDevelopmentHostWindow);
}
if (Array.isArray(windowsState.openedWindows)) {
result.openedWindows = windowsState.openedWindows.map(windowState => restoreWindowState(windowState));
}
return result;
}
function restoreWindowState(windowState: ISerializedWindowState): IWindowState {
const result: IWindowState = { uiState: windowState.uiState };
if (windowState.backupPath) {
result.backupPath = windowState.backupPath;
}
if (windowState.remoteAuthority) {
result.remoteAuthority = windowState.remoteAuthority;
}
if (windowState.folder) {
result.folderUri = URI.parse(windowState.folder);
} else if (windowState.folderUri) {
result.folderUri = URI.revive(windowState.folderUri);
} else if (windowState.folderPath) {
result.folderUri = URI.file(windowState.folderPath);
}
if (windowState.workspaceIdentifier) {
result.workspace = { id: windowState.workspaceIdentifier.id, configPath: URI.parse(windowState.workspaceIdentifier.configURIPath) };
} else if (windowState.workspace) {
result.workspace = { id: windowState.workspace.id, configPath: URI.file(windowState.workspace.configPath) };
}
return result;
}
export function getWindowsStateStoreData(windowsState: IWindowsState): WindowsStateStorageData {
return {
lastActiveWindow: windowsState.lastActiveWindow && serializeWindowState(windowsState.lastActiveWindow),
lastPluginDevelopmentHostWindow: windowsState.lastPluginDevelopmentHostWindow && serializeWindowState(windowsState.lastPluginDevelopmentHostWindow),
openedWindows: windowsState.openedWindows.map(ws => serializeWindowState(ws))
};
}
function serializeWindowState(windowState: IWindowState): ISerializedWindowState {
return {
workspaceIdentifier: windowState.workspace && { id: windowState.workspace.id, configURIPath: windowState.workspace.configPath.toString() },
folder: windowState.folderUri && windowState.folderUri.toString(),
backupPath: windowState.backupPath,
remoteAuthority: windowState.remoteAuthority,
uiState: windowState.uiState
};
}

View File

@@ -0,0 +1,29 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { webFrame } from 'vs/base/parts/sandbox/electron-sandbox/globals';
import { zoomLevelToZoomFactor } from 'vs/platform/windows/common/windows';
import { setZoomFactor, setZoomLevel, getZoomLevel } from 'vs/base/browser/browser';
/**
* Apply a zoom level to the window. Also sets it in our in-memory
* browser helper so that it can be accessed in non-electron layers.
*/
export function applyZoom(zoomLevel: number): void {
webFrame.setZoomLevel(zoomLevel);
setZoomFactor(zoomLevelToZoomFactor(zoomLevel));
// Cannot be trusted because the webFrame might take some time
// until it really applies the new zoom level
// See https://github.com/microsoft/vscode/issues/26151
setZoomLevel(zoomLevel, false /* isTrusted */);
}
export function zoomIn(): void {
applyZoom(getZoomLevel() + 1);
}
export function zoomOut(): void {
applyZoom(getZoomLevel() - 1);
}

View File

@@ -0,0 +1,150 @@
/*---------------------------------------------------------------------------------------------
* 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;
}

View File

@@ -0,0 +1,127 @@
/*---------------------------------------------------------------------------------------------
* 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/node/window';
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';
const fixturesFolder = getPathFromAmdModule(require, './fixtures');
const testWorkspace: IWorkspaceIdentifier = {
id: Date.now().toString(),
configPath: URI.file(path.join(fixturesFolder, 'workspaces.json'))
};
const testWorkspaceFolders = toWorkspaceFolders([{ path: path.join(fixturesFolder, 'vscode_workspace_1_folder') }, { path: path.join(fixturesFolder, 'vscode_workspace_2_folder') }], testWorkspace.configPath);
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
};
}
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[] = [
vscodeFolderWindow,
lastActiveWindow,
noVscodeFolderWindow,
];
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);
});
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);
});
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);
});
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);
});
});