Skip to content

Commit

Permalink
[vscode] Support all vscode.workspace.fs APIs for registered fs provi…
Browse files Browse the repository at this point in the history
…ders

For this phase only support registered file system providers (and not real file system)

Signed-off-by: Amiram Wingarten <[email protected]>
  • Loading branch information
amiramw committed May 26, 2020
1 parent 5de8eee commit 84408de
Show file tree
Hide file tree
Showing 7 changed files with 235 additions and 12 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

## v1.2.0

- [plugin] support all vscode.workspace.fs APIs for registered fs providers only (not yet real file system)
- [application-manager] enabled clients to add `windowOptions` using an IPC-Event [#7803](https://github.com/eclipse-theia/theia/pull/7803)
- [application-package] enabled client to change default `windowOptions` [#7803](https://github.com/eclipse-theia/theia/pull/7803)

Expand Down
14 changes: 13 additions & 1 deletion packages/plugin-ext/src/common/plugin-api-rpc.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ import {
QuickInputButton
} from '../plugin/types-impl';
import { UriComponents } from './uri-components';
import { ConfigurationTarget } from '../plugin/types-impl';
import { ConfigurationTarget, FileType, FileStat } from '../plugin/types-impl';
import {
SerializedDocumentFilter,
CompletionContext,
Expand Down Expand Up @@ -1347,13 +1347,25 @@ export interface DebugMain {
}

export interface FileSystemExt {
$stat(handle: number, resource: UriComponents): Promise<FileStat>;
$readDirectory(handle: number, resource: UriComponents): Promise<[string, FileType][]>;
$createDirectory(handle: number, uri: UriComponents): Promise<void>;
$readFile(handle: number, resource: UriComponents, options?: { encoding?: string }): Promise<string>;
$writeFile(handle: number, resource: UriComponents, content: string, options?: { encoding?: string }): Promise<void>;
$delete(handle: number, resource: UriComponents, options: { recursive: boolean }): Promise<void>;
$rename(handle: number, source: UriComponents, target: UriComponents, options: { overwrite: boolean }): Promise<void>;
$copy(handle: number, source: UriComponents, target: UriComponents, options: { overwrite: boolean }): Promise<void>;
}

export interface FileSystemMain {
$stat(uri: UriComponents): Promise<FileStat>
$readDirectory(uri: UriComponents): Promise<[string, FileType][]>;
$createDirectory(uri: UriComponents): Promise<void>
$readFile(uri: UriComponents): Promise<string>;
$writeFile(uri: UriComponents, content: string): Promise<void>;
$delete(uri: UriComponents, options: { recursive: boolean }): Promise<void>;
$rename(source: UriComponents, target: UriComponents, options: { overwrite: boolean }): Promise<void>;
$copy(source: UriComponents, target: UriComponents, options: { overwrite: boolean }): Promise<void>;
$registerFileSystemProvider(handle: number, scheme: string): void;
$unregisterProvider(handle: number): void;
}
Expand Down
68 changes: 67 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 @@ -22,13 +22,15 @@ 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';
import { FileStat, FileType } from '../../plugin/types-impl';

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 providersBySchema = new Map<string, number>();
private readonly toDispose = new DisposableCollection();

constructor(rpc: RPCProtocol, container: interfaces.Container) {
Expand All @@ -44,9 +46,16 @@ export class FileSystemMainImpl implements FileSystemMain, Disposable {
async $registerFileSystemProvider(handle: number, scheme: string): Promise<void> {
const toDispose = new DisposableCollection(
this.resourceResolver.registerResourceProvider(handle, scheme, this.proxy),
Disposable.create(() => this.providers.delete(handle))
Disposable.create(() => {
this.providers.delete(handle);
this.providersBySchema.delete(scheme);
})
);
this.providers.set(handle, toDispose);
if (this.providersBySchema.has(scheme)) {
throw new Error(`Resource Provider for scheme '${scheme}' is already registered`);
}
this.providersBySchema.set(scheme, handle);
this.toDispose.push(toDispose);
}

Expand All @@ -57,6 +66,35 @@ export class FileSystemMainImpl implements FileSystemMain, Disposable {
}
}

private getHandle(uri: UriComponents): number {
const handle = this.providersBySchema.get(uri.scheme);
if (handle === undefined) {
throw new Error(`'No available file system provider for schema ${uri.scheme}`);
}
return handle;
}

// currently only support registered file system providers (and not real file system)
async $stat(uriComponents: UriComponents): Promise<FileStat> {
const uri = Uri.revive(uriComponents);
const handle = this.getHandle(uri);
return this.proxy.$stat(handle, uri);
}

// currently only support registered file system providers (and not real file system)
async $readDirectory(uriComponents: UriComponents): Promise<[string, FileType][]> {
const uri = Uri.revive(uriComponents);
const handle = this.getHandle(uri);
return this.proxy.$readDirectory(handle, uri);
}

// currently only support registered file system providers (and not real file system)
async $createDirectory(uriComponents: UriComponents): Promise<void> {
const uri = Uri.revive(uriComponents);
const handle = this.getHandle(uri);
return this.proxy.$createDirectory(handle, uri);
}

async $readFile(uriComponents: UriComponents): Promise<string> {
const uri = Uri.revive(uriComponents);
const resource = await this.resourceProvider(new URI(uri));
Expand All @@ -72,6 +110,34 @@ export class FileSystemMainImpl implements FileSystemMain, Disposable {
return resource.saveContents(content);
}

// currently only support registered file system providers (and not real file system)
async $delete(uriComponents: UriComponents, options: { recursive: boolean }): Promise<void> {
const uri = Uri.revive(uriComponents);
const handle = this.getHandle(uri);
return this.proxy.$delete(handle, uri, options);
}

// currently only support registered file system providers (and not real file system)
async $rename(source: UriComponents, target: UriComponents, options: { overwrite: boolean }): Promise<void> {
const sourceUri = Uri.revive(source);
const targetUri = Uri.revive(target);
const sourceHandle = this.getHandle(sourceUri);
if (sourceHandle !== this.getHandle(targetUri)) {
throw new Error(`'No matching file system provider for ${sourceUri} and ${targetUri}`);
}
return this.proxy.$rename(sourceHandle, sourceUri, targetUri, options);
}

// currently only support registered file system providers (and not real file system)
async $copy(source: UriComponents, target: UriComponents, options: { overwrite: boolean }): Promise<void> {
const sourceUri = Uri.revive(source);
const targetUri = Uri.revive(target);
const sourceHandle = this.getHandle(sourceUri);
if (sourceHandle !== this.getHandle(targetUri)) {
throw new Error(`'No matching file system provider for ${sourceUri} and ${targetUri}`);
}
return this.proxy.$copy(sourceHandle, sourceUri, targetUri, options);
}
}

@injectable()
Expand Down
57 changes: 49 additions & 8 deletions packages/plugin-ext/src/plugin/file-system.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,15 +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, FileStat, FileType } 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 readonly fileSystem: InPluginFileSystemProxy;

private handlePool: number = 0;

Expand Down Expand Up @@ -68,21 +68,41 @@ export class FileSystemExtImpl implements FileSystemExt {
});
}

private checkProviderExists(handle: number): void {
if (!this.fsProviders.has(handle)) {
private safeGetProvider(handle: number): theia.FileSystemProvider {
const provider = this.fsProviders.get(handle);
if (!provider) {
const err = new Error();
err.name = 'ENOPRO';
err.message = 'no provider';
throw err;
}
return provider;
}

// forwarding calls

$stat(handle: number, resource: UriComponents): Promise<FileStat> {
const fileSystemProvider = this.safeGetProvider(handle);
const uri = URI.revive(resource);
return Promise.resolve(fileSystemProvider.stat(uri));
}

$readDirectory(handle: number, resource: UriComponents): Promise<[string, FileType][]> {
const fileSystemProvider = this.safeGetProvider(handle);
const uri = URI.revive(resource);
return Promise.resolve(fileSystemProvider.readDirectory(uri));
}

$createDirectory(handle: number, resource: UriComponents): Promise<void> {
const fileSystemProvider = this.safeGetProvider(handle);
const uri = URI.revive(resource);
return Promise.resolve(fileSystemProvider.createDirectory(uri));
}

$readFile(handle: number, resource: UriComponents, options?: { encoding?: string }): Promise<string> {
this.checkProviderExists(handle);
const fileSystemProvider = this.safeGetProvider(handle);

return Promise.resolve(this.fsProviders.get(handle)!.readFile(URI.revive(resource))).then(data => {
return Promise.resolve(fileSystemProvider.readFile(URI.revive(resource))).then(data => {
const buffer = Buffer.isBuffer(data) ? data : Buffer.from(data.buffer, data.byteOffset, data.byteLength);
const encoding = options === null ? undefined : options && options.encoding;
return buffer.toString(encoding);
Expand All @@ -91,11 +111,32 @@ export class FileSystemExtImpl implements FileSystemExt {
}

$writeFile(handle: number, resource: UriComponents, content: string, options?: { encoding?: string }): Promise<void> {
this.checkProviderExists(handle);
const fileSystemProvider = this.safeGetProvider(handle);
const uri = URI.revive(resource);
const encoding = options === null ? undefined : options && options.encoding;
const buffer = Buffer.from(content, encoding);
const opts = { create: true, overwrite: true };
return Promise.resolve(this.fsProviders.get(handle)!.writeFile(uri, buffer, opts));
return Promise.resolve(fileSystemProvider.writeFile(uri, buffer, opts));
}

$delete(handle: number, resource: UriComponents, options: { recursive: boolean }): Promise<void> {
const fileSystemProvider = this.safeGetProvider(handle);
const uri = URI.revive(resource);
return Promise.resolve(fileSystemProvider.delete(uri, options));
}

$rename(handle: number, source: UriComponents, target: UriComponents, options: { overwrite: boolean }): Promise<void> {
const fileSystemProvider = this.safeGetProvider(handle);
const sourceUri = URI.revive(source);
const targetUri = URI.revive(target);
return Promise.resolve(fileSystemProvider.rename(sourceUri, targetUri, options));
}

$copy(handle: number, source: UriComponents, target: UriComponents, options: { overwrite: boolean }): Promise<void> {
const fileSystemProvider = this.safeGetProvider(handle);
const sourceUri = URI.revive(source);
const targetUri = URI.revive(target);
return Promise.resolve(fileSystemProvider.copy && fileSystemProvider.copy(sourceUri, targetUri, options));
}

}
48 changes: 46 additions & 2 deletions packages/plugin-ext/src/plugin/in-plugin-filesystem-proxy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,8 @@ import * as theia from '@theia/plugin';
import { TextEncoder, TextDecoder } from 'util';
import { FileSystemMain } from '../common/plugin-api-rpc';
import { UriComponents } from '../common/uri-components';
import { FileSystemError } from './types-impl';
import { FileSystemError, FileType } from './types-impl';
import { FileStat, Uri } from '@theia/plugin';

/**
* This class is managing FileSystem proxy
Expand All @@ -31,6 +32,28 @@ export class InPluginFileSystemProxy implements theia.FileSystem {
this.proxy = proxy;
}

async stat(uri: Uri): Promise<FileStat> {
try {
return this.proxy.$stat(uri);
} catch (error) {
throw this.handleError(error);
}
}
async readDirectory(uri: UriComponents): Promise<[string, FileType][]> {
try {
return this.proxy.$readDirectory(uri);
} catch (error) {
throw this.handleError(error);
}
}
async createDirectory(uri: Uri): Promise<void> {
try {
return this.proxy.$createDirectory(uri);
} catch (error) {
throw this.handleError(error);
}

}
async readFile(uri: UriComponents): Promise<Uint8Array> {
try {
const val = await this.proxy.$readFile(uri);
Expand All @@ -43,7 +66,28 @@ export class InPluginFileSystemProxy implements theia.FileSystem {
const encoded = new TextDecoder().decode(content);

try {
await this.proxy.$writeFile(uri, encoded);
return this.proxy.$writeFile(uri, encoded);
} catch (error) {
throw this.handleError(error);
}
}
async delete(uri: Uri, options?: { recursive?: boolean, useTrash?: boolean }): Promise<void> {
try {
return this.proxy.$delete(uri, { ...{ recursive: false }, ...options });
} catch (error) {
throw this.handleError(error);
}
}
async rename(source: Uri, target: Uri, options?: { overwrite?: boolean }): Promise<void> {
try {
return this.proxy.$rename(source, target, { ...{ overwrite: false }, ...options });
} catch (error) {
throw this.handleError(error);
}
}
async copy(source: Uri, target: Uri, options?: { overwrite?: boolean }): Promise<void> {
try {
return this.proxy.$copy(source, target, { ...{ overwrite: false }, ...options });
} catch (error) {
throw this.handleError(error);
}
Expand Down
7 changes: 7 additions & 0 deletions packages/plugin-ext/src/plugin/types-impl.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1328,6 +1328,13 @@ export enum FileType {
SymbolicLink = 64
}

export interface FileStat {
readonly type: FileType;
readonly ctime: number;
readonly mtime: number;
readonly size: number;
}

export class ProgressOptions {
/**
* The location at which progress should show.
Expand Down
52 changes: 52 additions & 0 deletions packages/plugin/src/theia.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4527,6 +4527,32 @@ declare module '@theia/plugin' {
*/
export interface FileSystem {

/**
* Retrieve metadata about a file.
*
* @param uri The uri of the file to retrieve metadata about.
* @return The file metadata about the file.
*/
stat(uri: Uri): PromiseLike<FileStat>;

/**
* Retrieve all entries of a [directory](#FileType.Directory).
*
* @param uri The uri of the folder.
* @return An array of name/type-tuples or a PromiseLike that resolves to such.
*/
readDirectory(uri: Uri): PromiseLike<[string, FileType][]>;

/**
* Create a new directory (Note, that new files are created via `write`-calls).
*
* *Note* that missing directories are created automatically, e.g this call has
* `mkdirp` semantics.
*
* @param uri The uri of the new folder.
*/
createDirectory(uri: Uri): PromiseLike<void>;

/**
* Read the entire contents of a file.
*
Expand All @@ -4542,6 +4568,32 @@ declare module '@theia/plugin' {
* @param content The new content of the file.
*/
writeFile(uri: Uri, content: Uint8Array): PromiseLike<void>;

/**
* Delete a file.
*
* @param uri The resource that is to be deleted.
* @param options Defines if trash can should be used and if deletion of folders is recursive
*/
delete(uri: Uri, options?: { recursive?: boolean, useTrash?: boolean }): PromiseLike<void>;

/**
* Rename a file or folder.
*
* @param source The existing file.
* @param target The new location.
* @param options Defines if existing files should be overwritten.
*/
rename(source: Uri, target: Uri, options?: { overwrite?: boolean }): PromiseLike<void>;

/**
* Copy files or folders.
*
* @param source The existing file.
* @param target The destination location.
* @param options Defines if existing files should be overwritten.
*/
copy(source: Uri, target: Uri, options?: { overwrite?: boolean }): PromiseLike<void>;
}

/**
Expand Down

0 comments on commit 84408de

Please sign in to comment.