diff --git a/src/vs/workbench/api/browser/mainThreadFileSystem.ts b/src/vs/workbench/api/browser/mainThreadFileSystem.ts index 152b6751fb1fc..042e2264f5f0d 100644 --- a/src/vs/workbench/api/browser/mainThreadFileSystem.ts +++ b/src/vs/workbench/api/browser/mainThreadFileSystem.ts @@ -149,6 +149,10 @@ export class MainThreadFileSystem implements MainThreadFileSystemShape { throw err; } + + $ensureActivation(scheme: string): Promise { + return this._fileService.activateProvider(scheme); + } } class RemoteFileSystemProvider implements IFileSystemProviderWithFileReadWriteCapability, IFileSystemProviderWithOpenReadWriteCloseCapability, IFileSystemProviderWithFileFolderCopyCapability { diff --git a/src/vs/workbench/api/common/extHost.api.impl.ts b/src/vs/workbench/api/common/extHost.api.impl.ts index 8d170f3ebeeac..ed882131258b1 100644 --- a/src/vs/workbench/api/common/extHost.api.impl.ts +++ b/src/vs/workbench/api/common/extHost.api.impl.ts @@ -90,6 +90,7 @@ import { matchesScheme } from 'vs/platform/opener/common/opener'; import { ExtHostNotebookEditors } from 'vs/workbench/api/common/extHostNotebookEditors'; import { ExtHostNotebookDocuments } from 'vs/workbench/api/common/extHostNotebookDocuments'; import { ExtHostInteractive } from 'vs/workbench/api/common/extHostInteractive'; +import { combinedDisposable } from 'vs/base/common/lifecycle'; import { checkProposedApiEnabled, isProposedApiEnabled } from 'vs/workbench/services/extensions/common/extensions'; export interface IExtensionApiFactory { @@ -916,7 +917,10 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I return extHostTask.registerTaskProvider(extension, type, provider); }, registerFileSystemProvider(scheme, provider, options) { - return extHostFileSystem.registerFileSystemProvider(extension, scheme, provider, options); + return combinedDisposable( + extHostFileSystem.registerFileSystemProvider(extension, scheme, provider, options), + extHostConsumerFileSystem.addFileSystemProvider(scheme, provider) + ); }, get fs() { return extHostConsumerFileSystem.value; diff --git a/src/vs/workbench/api/common/extHost.protocol.ts b/src/vs/workbench/api/common/extHost.protocol.ts index bba94a57a8f6d..c9f87013bb4b1 100644 --- a/src/vs/workbench/api/common/extHost.protocol.ts +++ b/src/vs/workbench/api/common/extHost.protocol.ts @@ -1016,6 +1016,8 @@ export interface MainThreadFileSystemShape extends IDisposable { $copy(resource: UriComponents, target: UriComponents, opts: files.FileOverwriteOptions): Promise; $mkdir(resource: UriComponents): Promise; $delete(resource: UriComponents, opts: files.FileDeleteOptions): Promise; + + $ensureActivation(scheme: string): Promise; } export interface MainThreadLabelServiceShape extends IDisposable { diff --git a/src/vs/workbench/api/common/extHostFileSystemConsumer.ts b/src/vs/workbench/api/common/extHostFileSystemConsumer.ts index 46e331f4c8313..cc8805a700b84 100644 --- a/src/vs/workbench/api/common/extHostFileSystemConsumer.ts +++ b/src/vs/workbench/api/common/extHostFileSystemConsumer.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { MainContext } from './extHost.protocol'; +import { MainContext, MainThreadFileSystemShape } from './extHost.protocol'; import * as vscode from 'vscode'; import * as files from 'vs/platform/files/common/files'; import { FileSystemError } from 'vs/workbench/api/common/extHostTypes'; @@ -11,6 +11,7 @@ import { VSBuffer } from 'vs/base/common/buffer'; import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; import { IExtHostRpcService } from 'vs/workbench/api/common/extHostRpcService'; import { IExtHostFileSystemInfo } from 'vs/workbench/api/common/extHostFileSystemInfo'; +import { IDisposable, toDisposable } from 'vs/base/common/lifecycle'; export class ExtHostConsumerFileSystem { @@ -18,36 +19,110 @@ export class ExtHostConsumerFileSystem { readonly value: vscode.FileSystem; + private readonly _proxy: MainThreadFileSystemShape; + private readonly _fileSystemProvider = new Map(); + constructor( @IExtHostRpcService extHostRpc: IExtHostRpcService, @IExtHostFileSystemInfo fileSystemInfo: IExtHostFileSystemInfo, ) { - const proxy = extHostRpc.getProxy(MainContext.MainThreadFileSystem); + this._proxy = extHostRpc.getProxy(MainContext.MainThreadFileSystem); + const that = this; this.value = Object.freeze({ - stat(uri: vscode.Uri): Promise { - return proxy.$stat(uri).catch(ExtHostConsumerFileSystem._handleError); + async stat(uri: vscode.Uri): Promise { + try { + const provider = that._fileSystemProvider.get(uri.scheme); + if (!provider) { + return await that._proxy.$stat(uri); + } + // use shortcut + await that._proxy.$ensureActivation(uri.scheme); + const stat = await provider.stat(uri); + return { + type: stat.type, + ctime: stat.ctime, + mtime: stat.mtime, + size: stat.size, + permissions: stat.permissions + }; + } catch (err) { + ExtHostConsumerFileSystem._handleError(err); + } }, - readDirectory(uri: vscode.Uri): Promise<[string, vscode.FileType][]> { - return proxy.$readdir(uri).catch(ExtHostConsumerFileSystem._handleError); + async readDirectory(uri: vscode.Uri): Promise<[string, vscode.FileType][]> { + try { + const provider = that._fileSystemProvider.get(uri.scheme); + if (provider) { + // use shortcut + await that._proxy.$ensureActivation(uri.scheme); + return (await provider.readDirectory(uri)).slice(); // safe-copy + } else { + return await that._proxy.$readdir(uri); + } + } catch (err) { + return ExtHostConsumerFileSystem._handleError(err); + } }, - createDirectory(uri: vscode.Uri): Promise { - return proxy.$mkdir(uri).catch(ExtHostConsumerFileSystem._handleError); + async createDirectory(uri: vscode.Uri): Promise { + try { + // no shortcut: does mkdirp + return await that._proxy.$mkdir(uri); + } catch (err) { + return ExtHostConsumerFileSystem._handleError(err); + } }, async readFile(uri: vscode.Uri): Promise { - return proxy.$readFile(uri).then(buff => buff.buffer).catch(ExtHostConsumerFileSystem._handleError); + try { + const provider = that._fileSystemProvider.get(uri.scheme); + if (provider) { + // use shortcut + await that._proxy.$ensureActivation(uri.scheme); + return (await provider.readFile(uri)).slice(); // safe-copy + } else { + return that._proxy.$readFile(uri).then(buff => buff.buffer); + } + } catch (err) { + return ExtHostConsumerFileSystem._handleError(err); + } }, - writeFile(uri: vscode.Uri, content: Uint8Array): Promise { - return proxy.$writeFile(uri, VSBuffer.wrap(content)).catch(ExtHostConsumerFileSystem._handleError); + async writeFile(uri: vscode.Uri, content: Uint8Array): Promise { + try { + // no shortcut: does mkdirp + return await that._proxy.$writeFile(uri, VSBuffer.wrap(content)); + } catch (err) { + return ExtHostConsumerFileSystem._handleError(err); + } }, - delete(uri: vscode.Uri, options?: { recursive?: boolean; useTrash?: boolean; }): Promise { - return proxy.$delete(uri, { ...{ recursive: false, useTrash: false }, ...options }).catch(ExtHostConsumerFileSystem._handleError); + async delete(uri: vscode.Uri, options?: { recursive?: boolean; useTrash?: boolean; }): Promise { + try { + const provider = that._fileSystemProvider.get(uri.scheme); + if (provider) { + // use shortcut + await that._proxy.$ensureActivation(uri.scheme); + return await provider.delete(uri, { recursive: false, ...options }); + } else { + return await that._proxy.$delete(uri, { recursive: false, useTrash: false, ...options }); + } + } catch (err) { + return ExtHostConsumerFileSystem._handleError(err); + } }, - rename(oldUri: vscode.Uri, newUri: vscode.Uri, options?: { overwrite?: boolean; }): Promise { - return proxy.$rename(oldUri, newUri, { ...{ overwrite: false }, ...options }).catch(ExtHostConsumerFileSystem._handleError); + async rename(oldUri: vscode.Uri, newUri: vscode.Uri, options?: { overwrite?: boolean; }): Promise { + try { + // no shortcut: potentially involves different schemes, does mkdirp + return await that._proxy.$rename(oldUri, newUri, { ...{ overwrite: false }, ...options }); + } catch (err) { + return ExtHostConsumerFileSystem._handleError(err); + } }, - copy(source: vscode.Uri, destination: vscode.Uri, options?: { overwrite?: boolean; }): Promise { - return proxy.$copy(source, destination, { ...{ overwrite: false }, ...options }).catch(ExtHostConsumerFileSystem._handleError); + async copy(source: vscode.Uri, destination: vscode.Uri, options?: { overwrite?: boolean; }): Promise { + try { + // no shortcut: potentially involves different schemes, does mkdirp + return await that._proxy.$copy(source, destination, { ...{ overwrite: false }, ...options }); + } catch (err) { + return ExtHostConsumerFileSystem._handleError(err); + } }, isWritableFileSystem(scheme: string): boolean | undefined { const capabilities = fileSystemInfo.getCapabilities(scheme); @@ -60,6 +135,11 @@ export class ExtHostConsumerFileSystem { } private static _handleError(err: any): never { + // desired error type + if (err instanceof FileSystemError) { + throw err; + } + // generic error if (!(err instanceof Error)) { throw new FileSystemError(String(err)); @@ -82,6 +162,13 @@ export class ExtHostConsumerFileSystem { default: throw new FileSystemError(err.message, err.name as files.FileSystemProviderErrorCode); } } + + // --- + + addFileSystemProvider(scheme: string, provider: vscode.FileSystemProvider): IDisposable { + this._fileSystemProvider.set(scheme, provider); + return toDisposable(() => this._fileSystemProvider.delete(scheme)); + } } export interface IExtHostConsumerFileSystem extends ExtHostConsumerFileSystem { }