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 <[email protected]>
  • Loading branch information
benoitf committed Jan 28, 2020
1 parent 1ca3ec3 commit d0b7536
Show file tree
Hide file tree
Showing 7 changed files with 188 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 @@ -1317,6 +1317,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
51 changes: 50 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 @@ -14,13 +14,16 @@
* SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
********************************************************************************/

import { interfaces, injectable } from 'inversify';
import { interfaces, injectable, inject } from 'inversify';
import Uri from 'vscode-uri';
import { Disposable, ResourceResolver, DisposableCollection } from '@theia/core';
import { Resource } 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 { FileSystemError } from '../../plugin/types-impl';
import { UriComponents, Schemes } from '../../common/uri-components';
import { FileSystem } from '@theia/filesystem/lib/common';

export class FileSystemMainImpl implements FileSystemMain, Disposable {

Expand Down Expand Up @@ -54,6 +57,21 @@ export class FileSystemMainImpl implements FileSystemMain, Disposable {
}
}

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

async $writeFile(uriComponents: UriComponents, content: string): Promise<void> {
const uri = Uri.revive(uriComponents);
const resource = this.resourceResolver.resolve(new URI(uri));
if (!resource.saveContents) {
throw new FileSystemError(`'No write operation available on the resource for URI ${uriComponents}`);
}
return resource.saveContents(content);
}

}

@injectable()
Expand All @@ -63,7 +81,13 @@ export class FSResourceResolver implements ResourceResolver, Disposable {
private providers = new Map<string, FSResourceProvider>();
private toDispose = new DisposableCollection();

@inject(FileSystem)
private fileSystem: FileSystem;

resolve(uri: URI): Resource {
if (uri.scheme === Schemes.FILE) {
return new FileResource(this.fileSystem, uri);
}
const provider = this.providers.get(uri.scheme);
if (provider) {
return provider.get(uri);
Expand Down Expand Up @@ -127,3 +151,28 @@ export class FSResource implements Resource {

dispose(): void { }
}

/** Resource that delegates to theia FileSystem */
export class FileResource implements Resource {

constructor(readonly fileSystem: FileSystem, readonly uri: URI) { }

async readContents(options?: { encoding?: string }): Promise<string> {
const file = await this.fileSystem.resolveContent(this.uri.toString(), options);
return file.content;
}

async saveContents(content: string, options?: { encoding?: string }): Promise<void> {
const uriStr = this.uri.toString();
const exist = await this.fileSystem.exists(uriStr);
let stat;
if (exist) {
stat = await this.fileSystem.getFileStat(uriStr);
} else {
stat = await this.fileSystem.createFile(uriStr);
}
await this.fileSystem.setContent(stat!, content, options);
}

dispose(): void { }
}
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
43 changes: 43 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,43 @@
/********************************************************************************
* 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 } 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> {
const val = await this.proxy.$readFile(uri);
return new TextEncoder().encode(val);

}
async writeFile(uri: UriComponents, content: Uint8Array): Promise<void> {
const encoded = new TextDecoder().decode(content);
return this.proxy.$writeFile(uri, encoded);
}

}
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 @@ -383,6 +383,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
44 changes: 44 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 '../common/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,49 @@ export enum FileType {
SymbolicLink = 64
}

/**
* The `FileStat`-type represents metadata about a file
*/
export interface FileStat {
/**
* The type of the file, e.g. is a regular file, a directory, or symbolic link
* to a file.
*/
type: FileType;
/**
* The creation timestamp in milliseconds elapsed since January 1, 1970 00:00:00 UTC.
*/
ctime: number;
/**
* The modification timestamp in milliseconds elapsed since January 1, 1970 00:00:00 UTC.
*/
mtime: number;
/**
* The size in bytes.
*/
size: number;
}

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 d0b7536

Please sign in to comment.