Skip to content

Commit

Permalink
feat(vscode): Implements readFile/writeFile for workspace.fs
Browse files Browse the repository at this point in the history
Change-Id: I1626547e72b8230bbc0a1832f682ebc18f2b22dc
Signed-off-by: Florent Benoit <fbenoit@redhat.com>
  • Loading branch information
benoitf committed Feb 3, 2020
1 parent 86704a9 commit b45f8c9
Show file tree
Hide file tree
Showing 7 changed files with 152 additions and 2 deletions.
2 changes: 2 additions & 0 deletions packages/plugin-ext/src/common/plugin-api-rpc.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1322,6 +1322,8 @@ export interface FileSystemExt {
}

export interface FileSystemMain {
$readFile(uri: UriComponents): Promise<string>;
$writeFile(uri: UriComponents, content: string): Promise<void>;
$registerFileSystemProvider(handle: number, scheme: string): void;
$unregisterProvider(handle: number): void;
}
Expand Down
20 changes: 19 additions & 1 deletion packages/plugin-ext/src/main/browser/file-system-main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<number, Disposable>();
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 {
Expand All @@ -54,6 +57,21 @@ export class FileSystemMainImpl implements FileSystemMain, Disposable {
}
}

async $readFile(uriComponents: UriComponents): Promise<string> {
const uri = Uri.revive(uriComponents);
const resource = await this.resourceProvider(new URI(uri));
return resource.readContents();
}

async $writeFile(uriComponents: UriComponents, content: string): Promise<void> {
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()
Expand Down
10 changes: 9 additions & 1 deletion packages/plugin-ext/src/plugin/file-system.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<string>();
private readonly fsProviders = new Map<number, theia.FileSystemProvider>();
private fileSystem: InPluginFileSystemProxy;

private handlePool: number = 0;

Expand All @@ -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 {
Expand Down
61 changes: 61 additions & 0 deletions packages/plugin-ext/src/plugin/in-plugin-filesystem-proxy.ts
Original file line number Diff line number Diff line change
@@ -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<Uint8Array> {
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<void> {
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);
}

}
5 changes: 5 additions & 0 deletions packages/plugin-ext/src/plugin/plugin-context.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
},
Expand Down
21 changes: 21 additions & 0 deletions packages/plugin-ext/src/plugin/types-impl.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down Expand Up @@ -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<Uint8Array>;

/**
* 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<void>;

}

export class ProgressOptions {
/**
* The location at which progress should show.
Expand Down
35 changes: 35 additions & 0 deletions packages/plugin/src/theia.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4505,6 +4505,33 @@ declare module '@theia/plugin' {
copy?(source: Uri, destination: Uri, options: { overwrite: boolean }): void | PromiseLike<void>;
}

/**
* 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<Uint8Array>;

/**
* 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<void>;
}

/**
* 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
Expand All @@ -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.~~
Expand Down

0 comments on commit b45f8c9

Please sign in to comment.