diff --git a/src/vs/workbench/api/browser/mainThreadFileSystem.ts b/src/vs/workbench/api/browser/mainThreadFileSystem.ts index 9c6e733d8ff..dc0aa294b70 100644 --- a/src/vs/workbench/api/browser/mainThreadFileSystem.ts +++ b/src/vs/workbench/api/browser/mainThreadFileSystem.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { Emitter, Event } from 'vs/base/common/event'; -import { IDisposable, dispose, toDisposable } from 'vs/base/common/lifecycle'; +import { IDisposable, dispose, toDisposable, DisposableStore } from 'vs/base/common/lifecycle'; import { URI, UriComponents } from 'vs/base/common/uri'; import { FileWriteOptions, FileSystemProviderCapabilities, IFileChange, IFileService, IStat, IWatchOptions, FileType, FileOverwriteOptions, FileDeleteOptions, FileOpenOptions, IFileStat, FileOperationError, FileOperationResult, FileSystemProviderErrorCode, IFileSystemProviderWithOpenReadWriteCloseCapability, IFileSystemProviderWithFileReadWriteCapability, IFileSystemProviderWithFileFolderCopyCapability } from 'vs/platform/files/common/files'; import { extHostNamedCustomer } from 'vs/workbench/api/common/extHostCustomers'; @@ -16,15 +16,22 @@ export class MainThreadFileSystem implements MainThreadFileSystemShape { private readonly _proxy: ExtHostFileSystemShape; private readonly _fileProvider = new Map(); + private readonly _disposables = new DisposableStore(); constructor( extHostContext: IExtHostContext, @IFileService private readonly _fileService: IFileService, ) { this._proxy = extHostContext.getProxy(ExtHostContext.ExtHostFileSystem); + + const infoProxy = extHostContext.getProxy(ExtHostContext.ExtHostFileSystemInfo); + + this._disposables.add(_fileService.onDidChangeFileSystemProviderRegistrations(e => infoProxy.$acceptProviderInfos(e.scheme, e.provider?.capabilities ?? null))); + this._disposables.add(_fileService.onDidChangeFileSystemProviderCapabilities(e => infoProxy.$acceptProviderInfos(e.scheme, e.provider.capabilities))); } dispose(): void { + this._disposables.dispose(); dispose(this._fileProvider.values()); this._fileProvider.clear(); } diff --git a/src/vs/workbench/api/common/extHost.api.impl.ts b/src/vs/workbench/api/common/extHost.api.impl.ts index f4d07676ed2..0d62aba5cec 100644 --- a/src/vs/workbench/api/common/extHost.api.impl.ts +++ b/src/vs/workbench/api/common/extHost.api.impl.ts @@ -80,6 +80,7 @@ import { ExtHostWebviewViews } from 'vs/workbench/api/common/extHostWebviewView' import { ExtHostCustomEditors } from 'vs/workbench/api/common/extHostCustomEditors'; import { ExtHostWebviewPanels } from 'vs/workbench/api/common/extHostWebviewPanels'; import { ExtHostBulkEdits } from 'vs/workbench/api/common/extHostBulkEdits'; +import { IExtHostFileSystemInfo } from 'vs/workbench/api/common/extHostFileSystemInfo'; export interface IExtensionApiFactory { (extension: IExtensionDescription, registry: ExtensionDescriptionRegistry, configProvider: ExtHostConfigProvider): typeof vscode; @@ -92,6 +93,7 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I // services const initData = accessor.get(IExtHostInitDataService); + const extHostFileSystemInfo = accessor.get(IExtHostFileSystemInfo); const extHostConsumerFileSystem = accessor.get(IExtHostConsumerFileSystem); const extensionService = accessor.get(IExtHostExtensionService); const extHostWorkspace = accessor.get(IExtHostWorkspace); @@ -106,6 +108,7 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I const extHostWindow = accessor.get(IExtHostWindow); // register addressable instances + rpcProtocol.set(ExtHostContext.ExtHostFileSystemInfo, extHostFileSystemInfo); rpcProtocol.set(ExtHostContext.ExtHostLogService, extHostLogService); rpcProtocol.set(ExtHostContext.ExtHostWorkspace, extHostWorkspace); rpcProtocol.set(ExtHostContext.ExtHostConfiguration, extHostConfiguration); @@ -135,7 +138,7 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I const extHostEditorInsets = rpcProtocol.set(ExtHostContext.ExtHostEditorInsets, new ExtHostEditorInsets(rpcProtocol.getProxy(MainContext.MainThreadEditorInsets), extHostEditors, initData.environment)); const extHostDiagnostics = rpcProtocol.set(ExtHostContext.ExtHostDiagnostics, new ExtHostDiagnostics(rpcProtocol, extHostLogService)); const extHostLanguageFeatures = rpcProtocol.set(ExtHostContext.ExtHostLanguageFeatures, new ExtHostLanguageFeatures(rpcProtocol, uriTransformer, extHostDocuments, extHostCommands, extHostDiagnostics, extHostLogService, extHostApiDeprecation)); - const extHostFileSystem = rpcProtocol.set(ExtHostContext.ExtHostFileSystem, new ExtHostFileSystem(rpcProtocol, extHostLanguageFeatures, extHostConsumerFileSystem)); + const extHostFileSystem = rpcProtocol.set(ExtHostContext.ExtHostFileSystem, new ExtHostFileSystem(rpcProtocol, extHostLanguageFeatures, extHostFileSystemInfo)); const extHostFileSystemEvent = rpcProtocol.set(ExtHostContext.ExtHostFileSystemEventService, new ExtHostFileSystemEventService(rpcProtocol, extHostLogService, extHostDocumentsAndEditors)); const extHostQuickOpen = rpcProtocol.set(ExtHostContext.ExtHostQuickOpen, new ExtHostQuickOpen(rpcProtocol, extHostWorkspace, extHostCommands)); const extHostSCM = rpcProtocol.set(ExtHostContext.ExtHostSCM, new ExtHostSCM(rpcProtocol, extHostCommands, extHostLogService)); diff --git a/src/vs/workbench/api/common/extHost.common.services.ts b/src/vs/workbench/api/common/extHost.common.services.ts index 7d25f421282..6c1a02f447c 100644 --- a/src/vs/workbench/api/common/extHost.common.services.ts +++ b/src/vs/workbench/api/common/extHost.common.services.ts @@ -19,7 +19,8 @@ import { IExtHostStorage, ExtHostStorage } from 'vs/workbench/api/common/extHost import { IExtHostTunnelService, ExtHostTunnelService } from 'vs/workbench/api/common/extHostTunnelService'; import { IExtHostApiDeprecationService, ExtHostApiDeprecationService, } from 'vs/workbench/api/common/extHostApiDeprecationService'; import { IExtHostWindow, ExtHostWindow } from 'vs/workbench/api/common/extHostWindow'; -import { ExtHostConsumerFileSystem, IExtHostConsumerFileSystem } from 'vs/workbench/api/common/extHostFileSystemConsumer'; +import { IExtHostConsumerFileSystem, ExtHostConsumerFileSystem } from 'vs/workbench/api/common/extHostFileSystemConsumer'; +import { IExtHostFileSystemInfo, ExtHostFileSystemInfo } from 'vs/workbench/api/common/extHostFileSystemInfo'; registerSingleton(IExtensionStoragePaths, ExtensionStoragePaths); registerSingleton(IExtHostApiDeprecationService, ExtHostApiDeprecationService); @@ -29,6 +30,7 @@ registerSingleton(IExtHostConsumerFileSystem, ExtHostConsumerFileSystem); registerSingleton(IExtHostDebugService, WorkerExtHostDebugService); registerSingleton(IExtHostDecorations, ExtHostDecorations); registerSingleton(IExtHostDocumentsAndEditors, ExtHostDocumentsAndEditors); +registerSingleton(IExtHostFileSystemInfo, ExtHostFileSystemInfo); registerSingleton(IExtHostOutputService, ExtHostOutputService); registerSingleton(IExtHostSearch, ExtHostSearch); registerSingleton(IExtHostStorage, ExtHostStorage); diff --git a/src/vs/workbench/api/common/extHost.protocol.ts b/src/vs/workbench/api/common/extHost.protocol.ts index ba114412a84..fe67d8b2e72 100644 --- a/src/vs/workbench/api/common/extHost.protocol.ts +++ b/src/vs/workbench/api/common/extHost.protocol.ts @@ -1036,6 +1036,10 @@ export interface ExtHostWorkspaceShape { $handleTextSearchResult(result: search.IRawFileMatch2, requestId: number): void; } +export interface ExtHostFileSystemInfoShape { + $acceptProviderInfos(scheme: string, capabilities: number | null): void; +} + export interface ExtHostFileSystemShape { $stat(handle: number, resource: UriComponents): Promise; $readdir(handle: number, resource: UriComponents): Promise<[string, files.FileType][]>; @@ -1787,6 +1791,7 @@ export const ExtHostContext = { ExtHostEditors: createExtId('ExtHostEditors'), ExtHostTreeViews: createExtId('ExtHostTreeViews'), ExtHostFileSystem: createExtId('ExtHostFileSystem'), + ExtHostFileSystemInfo: createExtId('ExtHostFileSystemInfo'), ExtHostFileSystemEventService: createExtId('ExtHostFileSystemEventService'), ExtHostLanguageFeatures: createExtId('ExtHostLanguageFeatures'), ExtHostQuickOpen: createExtId('ExtHostQuickOpen'), diff --git a/src/vs/workbench/api/common/extHostFileSystem.ts b/src/vs/workbench/api/common/extHostFileSystem.ts index c6330dd6e8b..a854c16f5c0 100644 --- a/src/vs/workbench/api/common/extHostFileSystem.ts +++ b/src/vs/workbench/api/common/extHostFileSystem.ts @@ -11,12 +11,11 @@ import { IDisposable, toDisposable } from 'vs/base/common/lifecycle'; import { FileChangeType } from 'vs/workbench/api/common/extHostTypes'; import * as typeConverter from 'vs/workbench/api/common/extHostTypeConverters'; import { ExtHostLanguageFeatures } from 'vs/workbench/api/common/extHostLanguageFeatures'; -import { Schemas } from 'vs/base/common/network'; import { State, StateMachine, LinkComputer, Edge } from 'vs/editor/common/modes/linkComputer'; import { commonPrefixLength } from 'vs/base/common/strings'; import { CharCode } from 'vs/base/common/charCode'; import { VSBuffer } from 'vs/base/common/buffer'; -import { IExtHostConsumerFileSystem } from 'vs/workbench/api/common/extHostFileSystemConsumer'; +import { IExtHostFileSystemInfo } from 'vs/workbench/api/common/extHostFileSystemInfo'; class FsLinkProvider { @@ -114,17 +113,14 @@ export class ExtHostFileSystem implements ExtHostFileSystemShape { private readonly _proxy: MainThreadFileSystemShape; private readonly _linkProvider = new FsLinkProvider(); private readonly _fsProvider = new Map(); - private readonly _usedSchemes = new Set(); + private readonly _registeredSchemes = new Set(); private readonly _watches = new Map(); private _linkProviderRegistration?: IDisposable; private _handlePool: number = 0; - constructor(mainContext: IMainContext, private _extHostLanguageFeatures: ExtHostLanguageFeatures, private readonly _fileSystemConsumer: IExtHostConsumerFileSystem) { + constructor(mainContext: IMainContext, private _extHostLanguageFeatures: ExtHostLanguageFeatures, private _extHostFileSystemInfo: IExtHostFileSystemInfo) { this._proxy = mainContext.getProxy(MainContext.MainThreadFileSystem); - - // register used schemes - Object.keys(Schemas).forEach(scheme => this._usedSchemes.add(scheme)); } dispose(): void { @@ -139,18 +135,16 @@ export class ExtHostFileSystem implements ExtHostFileSystemShape { registerFileSystemProvider(scheme: string, provider: vscode.FileSystemProvider, options: { isCaseSensitive?: boolean, isReadonly?: boolean } = {}) { - if (this._usedSchemes.has(scheme)) { + if (this._registeredSchemes.has(scheme) || !this._extHostFileSystemInfo.isFreeScheme(scheme)) { throw new Error(`a provider for the scheme '${scheme}' is already registered`); } - const schemeRegistration = this._fileSystemConsumer._registerScheme(scheme, options); - // this._registerLinkProviderIfNotYetRegistered(); const handle = this._handlePool++; this._linkProvider.add(scheme); - this._usedSchemes.add(scheme); + this._registeredSchemes.add(scheme); this._fsProvider.set(handle, provider); let capabilities = files.FileSystemProviderCapabilities.FileReadWrite; @@ -200,9 +194,8 @@ export class ExtHostFileSystem implements ExtHostFileSystemShape { return toDisposable(() => { subscription.dispose(); - schemeRegistration.dispose(); this._linkProvider.delete(scheme); - this._usedSchemes.delete(scheme); + this._registeredSchemes.delete(scheme); this._fsProvider.delete(handle); this._proxy.$unregisterProvider(handle); }); diff --git a/src/vs/workbench/api/common/extHostFileSystemConsumer.ts b/src/vs/workbench/api/common/extHostFileSystemConsumer.ts index cf3d309dbfb..5bbd185c775 100644 --- a/src/vs/workbench/api/common/extHostFileSystemConsumer.ts +++ b/src/vs/workbench/api/common/extHostFileSystemConsumer.ts @@ -10,7 +10,7 @@ import { FileSystemError } from 'vs/workbench/api/common/extHostTypes'; import { VSBuffer } from 'vs/base/common/buffer'; import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; import { IExtHostRpcService } from 'vs/workbench/api/common/extHostRpcService'; -import { IDisposable, toDisposable } from 'vs/base/common/lifecycle'; +import { IExtHostFileSystemInfo } from 'vs/workbench/api/common/extHostFileSystemInfo'; export class ExtHostConsumerFileSystem implements vscode.FileSystem { @@ -18,9 +18,10 @@ export class ExtHostConsumerFileSystem implements vscode.FileSystem { private readonly _proxy: MainThreadFileSystemShape; - private readonly _schemes = new Map(); - - constructor(@IExtHostRpcService extHostRpc: IExtHostRpcService) { + constructor( + @IExtHostRpcService extHostRpc: IExtHostRpcService, + @IExtHostFileSystemInfo private readonly _fileSystemInfo: IExtHostFileSystemInfo, + ) { this._proxy = extHostRpc.getProxy(MainContext.MainThreadFileSystem); } @@ -49,9 +50,9 @@ export class ExtHostConsumerFileSystem implements vscode.FileSystem { return this._proxy.$copy(source, destination, { ...{ overwrite: false }, ...options }).catch(ExtHostConsumerFileSystem._handleError); } isWritableFileSystem(scheme: string): boolean | undefined { - const entry = this._schemes.get(scheme); - if (entry) { - return !entry.isReadonly; + const capabilities = this._fileSystemInfo.getCapabilities(scheme); + if (typeof capabilities === 'number') { + return !(capabilities & files.FileSystemProviderCapabilities.Readonly); } return undefined; } @@ -79,14 +80,6 @@ export class ExtHostConsumerFileSystem implements vscode.FileSystem { default: throw new FileSystemError(err.message, err.name as files.FileSystemProviderErrorCode); } } - - /* internal */ _registerScheme(scheme: string, options: { readonly isReadonly?: boolean }): IDisposable { - this._schemes.set(scheme, options); - - return toDisposable(() => { - return this._schemes.delete(scheme); - }); - } } export interface IExtHostConsumerFileSystem extends ExtHostConsumerFileSystem { } diff --git a/src/vs/workbench/api/common/extHostFileSystemInfo.ts b/src/vs/workbench/api/common/extHostFileSystemInfo.ts new file mode 100644 index 00000000000..500a9184cd5 --- /dev/null +++ b/src/vs/workbench/api/common/extHostFileSystemInfo.ts @@ -0,0 +1,35 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { Schemas } from 'vs/base/common/network'; +import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; +import { ExtHostFileSystemInfoShape } from 'vs/workbench/api/common/extHost.protocol'; + +export class ExtHostFileSystemInfo implements ExtHostFileSystemInfoShape { + + declare readonly _serviceBrand: undefined; + + private readonly _systemSchemes = new Set(Object.keys(Schemas)); + private readonly _providerInfo = new Map(); + + $acceptProviderInfos(scheme: string, capabilities: number | null): void { + if (capabilities === null) { + this._providerInfo.delete(scheme); + } else { + this._providerInfo.set(scheme, capabilities); + } + } + + isFreeScheme(scheme: string): boolean { + return !this._providerInfo.has(scheme) && !this._systemSchemes.has(scheme); + } + + getCapabilities(scheme: string): number | undefined { + return this._providerInfo.get(scheme); + } +} + +export interface IExtHostFileSystemInfo extends ExtHostFileSystemInfo { } +export const IExtHostFileSystemInfo = createDecorator('IExtHostFileSystemInfo');