diff --git a/src/vs/vscode.proposed.d.ts b/src/vs/vscode.proposed.d.ts index 65ddf419bb9..a80a3db0b2a 100644 --- a/src/vs/vscode.proposed.d.ts +++ b/src/vs/vscode.proposed.d.ts @@ -32,7 +32,19 @@ declare module 'vscode' { } + // todo@joh discover files etc + export interface FileSystemProvider { + // todo@joh -> added, deleted, renamed, changed + onDidChange: Event; + + resolveContents(resource: Uri): string | Thenable; + writeContents(resource: Uri, contents: string): void | Thenable; + } + export namespace workspace { + + export function registerFileSystemProvider(authority: string, provider: FileSystemProvider): Disposable; + /** * Get a configuration object. * diff --git a/src/vs/workbench/api/electron-browser/mainThreadWorkspace.ts b/src/vs/workbench/api/electron-browser/mainThreadWorkspace.ts index 3a4fef5dd89..93cb1b2ce7a 100644 --- a/src/vs/workbench/api/electron-browser/mainThreadWorkspace.ts +++ b/src/vs/workbench/api/electron-browser/mainThreadWorkspace.ts @@ -18,6 +18,8 @@ import { ITextModelService } from 'vs/editor/common/services/resolverService'; import { IFileService } from 'vs/platform/files/common/files'; import { IThreadService } from 'vs/workbench/services/thread/common/threadService'; import { IDisposable } from 'vs/base/common/lifecycle'; +import { RemoteFileService, IRemoteFileSystemProvider } from 'vs/workbench/services/files/electron-browser/remoteFileService'; +import { Emitter } from 'vs/base/common/event'; export class MainThreadWorkspace extends MainThreadWorkspaceShape { @@ -108,4 +110,32 @@ export class MainThreadWorkspace extends MainThreadWorkspaceShape { return bulkEdit(this._textModelResolverService, codeEditor, edits, this._fileService) .then(() => true); } + + // --- EXPERIMENT: workspace provider + + private _provider = new Map]>(); + + $registerFileSystemProvider(handle: number, authority: string): void { + if (!(this._fileService instanceof RemoteFileService)) { + throw new Error(); + } + const emitter = new Emitter(); + const provider = { + onDidChange: emitter.event, + resolve: (resource) => { + return this._proxy.$resolveFile(handle, resource); + }, + update: (resource, value) => { + return this._proxy.$storeFile(handle, resource, value); + } + }; + this._provider.set(handle, [provider, emitter]); + this._fileService.registerProvider(authority, provider); + } + + $onFileSystemChange(handle: number, resource: URI) { + const [, emitter] = this._provider.get(handle); + emitter.fire(resource); + }; } + diff --git a/src/vs/workbench/api/node/extHost.api.impl.ts b/src/vs/workbench/api/node/extHost.api.impl.ts index 2155bc87c32..e043eaccc5b 100644 --- a/src/vs/workbench/api/node/extHost.api.impl.ts +++ b/src/vs/workbench/api/node/extHost.api.impl.ts @@ -442,7 +442,10 @@ export function createApiFactory( }), registerTaskProvider: (type: string, provider: vscode.TaskProvider) => { return extHostTask.registerTaskProvider(extension, provider); - } + }, + registerFileSystemProvider: proposedApiFunction(extension, (authority, provider) => { + return extHostWorkspace.registerFileSystemProvider(authority, provider); + }) }; // namespace: scm diff --git a/src/vs/workbench/api/node/extHost.protocol.ts b/src/vs/workbench/api/node/extHost.protocol.ts index 4d0d6607be6..9950f8eda23 100644 --- a/src/vs/workbench/api/node/extHost.protocol.ts +++ b/src/vs/workbench/api/node/extHost.protocol.ts @@ -295,6 +295,8 @@ export abstract class MainThreadWorkspaceShape { $cancelSearch(requestId: number): Thenable { throw ni(); } $saveAll(includeUntitled?: boolean): Thenable { throw ni(); } $applyWorkspaceEdit(edits: IResourceEdit[]): TPromise { throw ni(); } + $registerFileSystemProvider(handle: number, authority: string): void { throw ni(); } + $onFileSystemChange(handle: number, resource: URI): void { throw ni(); } } export abstract class MainThreadTaskShape { @@ -420,6 +422,8 @@ export abstract class ExtHostTreeViewsShape { export abstract class ExtHostWorkspaceShape { $acceptWorkspaceData(workspace: IWorkspaceData): void { throw ni(); } + $resolveFile(handle: number, resource: URI): TPromise { throw ni(); } + $storeFile(handle: number, resource: URI, content: string): TPromise { throw ni(); } } export abstract class ExtHostExtensionServiceShape { diff --git a/src/vs/workbench/api/node/extHostWorkspace.ts b/src/vs/workbench/api/node/extHostWorkspace.ts index 09a239b0c61..2d9fbd7ab70 100644 --- a/src/vs/workbench/api/node/extHostWorkspace.ts +++ b/src/vs/workbench/api/node/extHostWorkspace.ts @@ -17,6 +17,8 @@ import { fromRange, EndOfLine } from 'vs/workbench/api/node/extHostTypeConverter import { IWorkspaceData, ExtHostWorkspaceShape, MainContext, MainThreadWorkspaceShape } from './extHost.protocol'; import * as vscode from 'vscode'; import { compare } from "vs/base/common/strings"; +import { asWinJsPromise } from 'vs/base/common/async'; +import { Disposable } from 'vs/workbench/api/node/extHostTypes'; export class ExtHostWorkspace extends ExtHostWorkspaceShape { @@ -151,4 +153,30 @@ export class ExtHostWorkspace extends ExtHostWorkspaceShape { return this._proxy.$applyWorkspaceEdit(resourceEdits); } + + // --- EXPERIMENT: workspace resolver + + private readonly _provider = new Map(); + + public registerFileSystemProvider(authority: string, provider: vscode.FileSystemProvider): vscode.Disposable { + + const handle = this._provider.size; + this._provider.set(handle, provider); + const reg = provider.onDidChange(e => this._proxy.$onFileSystemChange(handle, e)); + this._proxy.$registerFileSystemProvider(handle, authority); + return new Disposable(() => { + this._provider.delete(handle); + reg.dispose(); + }); + } + + $resolveFile(handle: number, resource: URI): TPromise { + const provider = this._provider.get(handle); + return asWinJsPromise(token => provider.resolveContents(resource)); + } + + $storeFile(handle: number, resource: URI, content: string): TPromise { + const provider = this._provider.get(handle); + return asWinJsPromise(token => provider.writeContents(resource, content)); + } } diff --git a/src/vs/workbench/services/files/electron-browser/remoteFileService.ts b/src/vs/workbench/services/files/electron-browser/remoteFileService.ts index 1fc46d3d38f..ec5433ab58f 100644 --- a/src/vs/workbench/services/files/electron-browser/remoteFileService.ts +++ b/src/vs/workbench/services/files/electron-browser/remoteFileService.ts @@ -21,7 +21,7 @@ import { EventEmitter } from "events"; import { basename } from "path"; import { IDisposable } from "vs/base/common/lifecycle"; -export interface IRemoteFileProvider { +export interface IRemoteFileSystemProvider { onDidChange: Event; resolve(resource: URI): TPromise; update(resource: URI, content: string): TPromise; @@ -29,8 +29,8 @@ export interface IRemoteFileProvider { export class RemoteFileService extends FileService { + private _provider: IRemoteFileSystemProvider; private readonly _remoteAuthority: string; - private _provider: IRemoteFileProvider; constructor( @IConfigurationService configurationService: IConfigurationService, @@ -43,25 +43,18 @@ export class RemoteFileService extends FileService { @IStorageService storageService: IStorageService ) { super(configurationService, contextService, editorService, environmentService, editorGroupService, lifecycleService, messageService, storageService); - this._remoteAuthority = environmentService.args['remote']; - - this.registerProvider(new class implements IRemoteFileProvider { - onDidChange: Event = Event.None; - resolve(resource: URI): TPromise { - return TPromise.as(JSON.stringify(resource, undefined, 4)); - } - update(resource: URI, content: string): TPromise { - return TPromise.as(undefined); - } - }); } private _shouldIntercept(resource: URI): boolean { return this._provider && resource.authority === this._remoteAuthority; } - registerProvider(provider: IRemoteFileProvider): IDisposable { + registerProvider(authority: string, provider: IRemoteFileSystemProvider): IDisposable { + // todo@joh make this actually work for N provider + if (authority !== this._remoteAuthority) { + throw new Error(); + } this._provider = provider; const reg = this._provider.onDidChange(e => { // forward change events