From b45f8c9f2aefcab19220e9b667ba5c99052bca6e Mon Sep 17 00:00:00 2001 From: Florent Benoit Date: Tue, 28 Jan 2020 09:17:03 +0100 Subject: [PATCH] feat(vscode): Implements readFile/writeFile for workspace.fs Change-Id: I1626547e72b8230bbc0a1832f682ebc18f2b22dc Signed-off-by: Florent Benoit --- .../plugin-ext/src/common/plugin-api-rpc.ts | 2 + .../src/main/browser/file-system-main.ts | 20 +++++- packages/plugin-ext/src/plugin/file-system.ts | 10 ++- .../src/plugin/in-plugin-filesystem-proxy.ts | 61 +++++++++++++++++++ .../plugin-ext/src/plugin/plugin-context.ts | 5 ++ packages/plugin-ext/src/plugin/types-impl.ts | 21 +++++++ packages/plugin/src/theia.d.ts | 35 +++++++++++ 7 files changed, 152 insertions(+), 2 deletions(-) create mode 100644 packages/plugin-ext/src/plugin/in-plugin-filesystem-proxy.ts diff --git a/packages/plugin-ext/src/common/plugin-api-rpc.ts b/packages/plugin-ext/src/common/plugin-api-rpc.ts index 1622b83fc3580..6d438e80840ae 100644 --- a/packages/plugin-ext/src/common/plugin-api-rpc.ts +++ b/packages/plugin-ext/src/common/plugin-api-rpc.ts @@ -1322,6 +1322,8 @@ export interface FileSystemExt { } export interface FileSystemMain { + $readFile(uri: UriComponents): Promise; + $writeFile(uri: UriComponents, content: string): Promise; $registerFileSystemProvider(handle: number, scheme: string): void; $unregisterProvider(handle: number): void; } diff --git a/packages/plugin-ext/src/main/browser/file-system-main.ts b/packages/plugin-ext/src/main/browser/file-system-main.ts index 3ecdbd3d6875f..7f5a18827642b 100644 --- a/packages/plugin-ext/src/main/browser/file-system-main.ts +++ b/packages/plugin-ext/src/main/browser/file-system-main.ts @@ -17,21 +17,24 @@ import { interfaces, injectable } from 'inversify'; import Uri from 'vscode-uri'; import { Disposable, ResourceResolver, DisposableCollection } from '@theia/core'; -import { Resource } from '@theia/core/lib/common/resource'; +import { Resource, ResourceProvider } from '@theia/core/lib/common/resource'; import URI from '@theia/core/lib/common/uri'; import { MAIN_RPC_CONTEXT, FileSystemMain, FileSystemExt } from '../../common/plugin-api-rpc'; import { RPCProtocol } from '../../common/rpc-protocol'; +import { UriComponents } from '../../common/uri-components'; export class FileSystemMainImpl implements FileSystemMain, Disposable { private readonly proxy: FileSystemExt; private readonly resourceResolver: FSResourceResolver; + private readonly resourceProvider: ResourceProvider; private readonly providers = new Map(); private readonly toDispose = new DisposableCollection(); constructor(rpc: RPCProtocol, container: interfaces.Container) { this.proxy = rpc.getProxy(MAIN_RPC_CONTEXT.FILE_SYSTEM_EXT); this.resourceResolver = container.get(FSResourceResolver); + this.resourceProvider = container.get(ResourceProvider); } dispose(): void { @@ -54,6 +57,21 @@ export class FileSystemMainImpl implements FileSystemMain, Disposable { } } + async $readFile(uriComponents: UriComponents): Promise { + const uri = Uri.revive(uriComponents); + const resource = await this.resourceProvider(new URI(uri)); + return resource.readContents(); + } + + async $writeFile(uriComponents: UriComponents, content: string): Promise { + const uri = Uri.revive(uriComponents); + const resource = await this.resourceProvider(new URI(uri)); + if (!resource.saveContents) { + throw new Error(`'No write operation available on the resource for URI ${uriComponents}`); + } + return resource.saveContents(content); + } + } @injectable() diff --git a/packages/plugin-ext/src/plugin/file-system.ts b/packages/plugin-ext/src/plugin/file-system.ts index 52e9fd44b149b..a79822306deb7 100644 --- a/packages/plugin-ext/src/plugin/file-system.ts +++ b/packages/plugin-ext/src/plugin/file-system.ts @@ -19,13 +19,15 @@ import * as theia from '@theia/plugin'; import { PLUGIN_RPC_CONTEXT, FileSystemExt, FileSystemMain } from '../common/plugin-api-rpc'; import { RPCProtocol } from '../common/rpc-protocol'; import { UriComponents, Schemes } from '../common/uri-components'; -import { Disposable } from './types-impl'; +import { Disposable, FileSystem } from './types-impl'; +import { InPluginFileSystemProxy } from './in-plugin-filesystem-proxy'; export class FileSystemExtImpl implements FileSystemExt { private readonly proxy: FileSystemMain; private readonly usedSchemes = new Set(); private readonly fsProviders = new Map(); + private fileSystem: InPluginFileSystemProxy; private handlePool: number = 0; @@ -41,6 +43,12 @@ export class FileSystemExtImpl implements FileSystemExt { this.usedSchemes.add(Schemes.MAILTO); this.usedSchemes.add(Schemes.DATA); this.usedSchemes.add(Schemes.COMMAND); + this.fileSystem = new InPluginFileSystemProxy(this.proxy); + + } + + get fs(): FileSystem { + return this.fileSystem; } registerFileSystemProvider(scheme: string, provider: theia.FileSystemProvider): theia.Disposable { diff --git a/packages/plugin-ext/src/plugin/in-plugin-filesystem-proxy.ts b/packages/plugin-ext/src/plugin/in-plugin-filesystem-proxy.ts new file mode 100644 index 0000000000000..f0b5b68f3b624 --- /dev/null +++ b/packages/plugin-ext/src/plugin/in-plugin-filesystem-proxy.ts @@ -0,0 +1,61 @@ +/******************************************************************************** + * Copyright (C) 2020 Red Hat, Inc. and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ + +import { TextEncoder, TextDecoder } from 'util'; +import { FileSystemMain } from '../common/plugin-api-rpc'; +import { UriComponents } from '../common/uri-components'; +import { FileSystem, FileSystemError } from './types-impl'; + +/** + * This class is managing FileSystem proxy + */ +export class InPluginFileSystemProxy implements FileSystem { + + private proxy: FileSystemMain; + + constructor(proxy: FileSystemMain) { + this.proxy = proxy; + } + + async readFile(uri: UriComponents): Promise { + try { + const val = await this.proxy.$readFile(uri); + return new TextEncoder().encode(val); + } catch (error) { + throw this.handleError(error); + } + } + async writeFile(uri: UriComponents, content: Uint8Array): Promise { + const encoded = new TextDecoder().decode(content); + + try { + await this.proxy.$writeFile(uri, encoded); + } catch (error) { + throw this.handleError(error); + } + } + + // eslint-disable-next-line @typescript-eslint/no-explicit-any + handleError(error: any): Error { + if (!(error instanceof Error)) { + return new FileSystemError(String(error)); + } + + // file system error + return new FileSystemError(error.message, error.name); + } + +} diff --git a/packages/plugin-ext/src/plugin/plugin-context.ts b/packages/plugin-ext/src/plugin/plugin-context.ts index f393b5905474e..72e1549a5895b 100644 --- a/packages/plugin-ext/src/plugin/plugin-context.ts +++ b/packages/plugin-ext/src/plugin/plugin-context.ts @@ -387,6 +387,11 @@ export function createAPIFactory( }; const workspace: typeof theia.workspace = { + + get fs(): theia.FileSystem { + return fileSystemExt.fs; + }, + get rootPath(): string | undefined { return workspaceExt.rootPath; }, diff --git a/packages/plugin-ext/src/plugin/types-impl.ts b/packages/plugin-ext/src/plugin/types-impl.ts index b194c6b86021a..6a9a13e003f1e 100644 --- a/packages/plugin-ext/src/plugin/types-impl.ts +++ b/packages/plugin-ext/src/plugin/types-impl.ts @@ -30,6 +30,7 @@ import { relative } from '../common/paths-util'; import { startsWithIgnoreCase } from '@theia/languages/lib/common/language-selector/strings'; import { MarkdownString, isMarkdownString } from './markdown-string'; import { SymbolKind } from '../common/plugin-api-rpc-model'; +import { UriComponents } from '../common/uri-components'; export class Disposable { private disposable: undefined | (() => void); @@ -1321,6 +1322,26 @@ export enum FileType { SymbolicLink = 64 } +export interface FileSystem { + + /** + * Read the entire contents of a file. + * + * @param uri The uri of the file. + * @return An array of bytes or a thenable that resolves to such. + */ + readFile(uri: UriComponents): Promise; + + /** + * Write data to a file, replacing its entire contents. + * + * @param uri The uri of the file. + * @param content The new content of the file. + */ + writeFile(uri: UriComponents, content: Uint8Array): Promise; + +} + export class ProgressOptions { /** * The location at which progress should show. diff --git a/packages/plugin/src/theia.d.ts b/packages/plugin/src/theia.d.ts index 840eee32a5f16..ac60cf43f6172 100644 --- a/packages/plugin/src/theia.d.ts +++ b/packages/plugin/src/theia.d.ts @@ -4505,6 +4505,33 @@ declare module '@theia/plugin' { copy?(source: Uri, destination: Uri, options: { overwrite: boolean }): void | PromiseLike; } + /** + * The file system interface exposes the editor's built-in and contributed + * [file system providers](#FileSystemProvider). It allows extensions to work + * with files from the local disk as well as files from remote places, like the + * remote extension host or ftp-servers. + * + * *Note* that an instance of this interface is avaiable as [`workspace.fs`](#workspace.fs). + */ + export interface FileSystem { + + /** + * Read the entire contents of a file. + * + * @param uri The uri of the file. + * @return An array of bytes or a PromiseLike that resolves to such. + */ + readFile(uri: Uri): PromiseLike; + + /** + * Write data to a file, replacing its entire contents. + * + * @param uri The uri of the file. + * @param content The new content of the file. + */ + writeFile(uri: Uri, content: Uint8Array): PromiseLike; + } + /** * Namespace for dealing with the current workspace. A workspace is the representation * of the folder that has been opened. There is no workspace when just a file but not a @@ -4516,6 +4543,14 @@ declare module '@theia/plugin' { */ export namespace workspace { + /** + * A [file system](#FileSystem) instance that allows to interact with local and remote + * files, e.g. `workspace.fs.readDirectory(someUri)` allows to retrieve all entries + * of a directory or `workspace.fs.stat(anotherUri)` returns the meta data for a + * file. + */ + export const fs: FileSystem; + /** * ~~The folder that is open in the editor. `undefined` when no folder * has been opened.~~