Skip to content

Commit

Permalink
Merge pull request #30337 from Microsoft/joh/remote
Browse files Browse the repository at this point in the history
Experiment: remote file system
  • Loading branch information
jrieken authored Jul 21, 2017
2 parents 0b24103 + a04d53f commit 1369a43
Show file tree
Hide file tree
Showing 9 changed files with 200 additions and 8 deletions.
8 changes: 6 additions & 2 deletions src/vs/vscode.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3857,10 +3857,14 @@ declare module 'vscode' {
provideTasks(token?: CancellationToken): ProviderResult<Task[]>;

/**
* Resolves a task the has no execution set.
* Resolves a task that has no [`execution`](#Task.execution) set. Tasks are
* often created from information found in the `task.json`-file. Such tasks miss
* the information on how to execute them and a task provider must fill in
* the missing information in the `resolveTask`-method.
*
* @param task The task to resolve.
* @param token A cancellation token.
* @return the resolved task
* @return The resolved task
*/
resolveTask(task: Task, token?: CancellationToken): ProviderResult<Task>;
}
Expand Down
12 changes: 12 additions & 0 deletions src/vs/vscode.proposed.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,19 @@ declare module 'vscode' {

}

// todo@joh discover files etc
export interface FileSystemProvider {
// todo@joh -> added, deleted, renamed, changed
onDidChange: Event<Uri>;

resolveContents(resource: Uri): string | Thenable<string>;
writeContents(resource: Uri, contents: string): void | Thenable<void>;
}

export namespace workspace {

export function registerFileSystemProvider(authority: string, provider: FileSystemProvider): Disposable;

/**
* Get a configuration object.
*
Expand Down
30 changes: 30 additions & 0 deletions src/vs/workbench/api/electron-browser/mainThreadWorkspace.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -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<number, [IRemoteFileSystemProvider, Emitter<URI>]>();

$registerFileSystemProvider(handle: number, authority: string): void {
if (!(this._fileService instanceof RemoteFileService)) {
throw new Error();
}
const emitter = new Emitter<URI>();
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);
};
}

5 changes: 4 additions & 1 deletion src/vs/workbench/api/node/extHost.api.impl.ts
Original file line number Diff line number Diff line change
Expand Up @@ -445,7 +445,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
Expand Down
4 changes: 4 additions & 0 deletions src/vs/workbench/api/node/extHost.protocol.ts
Original file line number Diff line number Diff line change
Expand Up @@ -294,6 +294,8 @@ export abstract class MainThreadWorkspaceShape {
$cancelSearch(requestId: number): Thenable<boolean> { throw ni(); }
$saveAll(includeUntitled?: boolean): Thenable<boolean> { throw ni(); }
$applyWorkspaceEdit(edits: IResourceEdit[]): TPromise<boolean> { throw ni(); }
$registerFileSystemProvider(handle: number, authority: string): void { throw ni(); }
$onFileSystemChange(handle: number, resource: URI): void { throw ni(); }
}

export abstract class MainThreadTaskShape {
Expand Down Expand Up @@ -426,6 +428,8 @@ export abstract class ExtHostTreeViewsShape {

export abstract class ExtHostWorkspaceShape {
$acceptWorkspaceData(workspace: IWorkspaceData): void { throw ni(); }
$resolveFile(handle: number, resource: URI): TPromise<string> { throw ni(); }
$storeFile(handle: number, resource: URI, content: string): TPromise<any> { throw ni(); }
}

export abstract class ExtHostExtensionServiceShape {
Expand Down
30 changes: 29 additions & 1 deletion src/vs/workbench/api/node/extHostWorkspace.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,9 @@ import { TPromise } from 'vs/base/common/winjs.base';
import { fromRange, EndOfLine } from 'vs/workbench/api/node/extHostTypeConverters';
import { IWorkspaceData, ExtHostWorkspaceShape, MainContext, MainThreadWorkspaceShape } from './extHost.protocol';
import * as vscode from 'vscode';
import { compare } from 'vs/base/common/strings';
import { compare } from "vs/base/common/strings";
import { asWinJsPromise } from 'vs/base/common/async';
import { Disposable } from 'vs/workbench/api/node/extHostTypes';
import { TrieMap } from 'vs/base/common/map';

class Workspace2 extends Workspace {
Expand Down Expand Up @@ -200,4 +202,30 @@ export class ExtHostWorkspace extends ExtHostWorkspaceShape {

return this._proxy.$applyWorkspaceEdit(resourceEdits);
}

// --- EXPERIMENT: workspace resolver

private readonly _provider = new Map<number, vscode.FileSystemProvider>();

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, <URI>e));
this._proxy.$registerFileSystemProvider(handle, authority);
return new Disposable(() => {
this._provider.delete(handle);
reg.dispose();
});
}

$resolveFile(handle: number, resource: URI): TPromise<string> {
const provider = this._provider.get(handle);
return asWinJsPromise(token => provider.resolveContents(resource));
}

$storeFile(handle: number, resource: URI, content: string): TPromise<any> {
const provider = this._provider.get(handle);
return asWinJsPromise(token => provider.writeContents(resource, content));
}
}
5 changes: 3 additions & 2 deletions src/vs/workbench/electron-browser/workbench.ts
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,8 @@ import { ContextKeyExpr, RawContextKey, IContextKeyService, IContextKey } from '
import { IActivityBarService } from 'vs/workbench/services/activity/common/activityBarService';
import { IViewletService } from 'vs/workbench/services/viewlet/browser/viewlet';
import { ViewletService } from 'vs/workbench/services/viewlet/browser/viewletService';
import { FileService } from 'vs/workbench/services/files/electron-browser/fileService';
// import { FileService } from 'vs/workbench/services/files/electron-browser/fileService';
import { RemoteFileService } from "vs/workbench/services/files/electron-browser/remoteFileService";
import { IFileService } from 'vs/platform/files/common/files';
import { IListService, ListService } from 'vs/platform/list/browser/listService';
import { IConfigurationResolverService } from 'vs/workbench/services/configurationResolver/common/configurationResolver';
Expand Down Expand Up @@ -588,7 +589,7 @@ export class Workbench implements IPartService {
serviceCollection.set(ITitleService, this.titlebarPart);

// File Service
const fileService = this.instantiationService.createInstance(FileService);
const fileService = this.instantiationService.createInstance(RemoteFileService);
serviceCollection.set(IFileService, fileService);
this.toDispose.push(fileService.onFileChanges(e => this.configurationService.handleWorkspaceFileEvents(e)));

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ export class FileService implements IFileService {
private toUnbind: IDisposable[];
private activeOutOfWorkspaceWatchers: ResourceMap<uri>;

private _onFileChanges: Emitter<FileChangesEvent>;
protected _onFileChanges: Emitter<FileChangesEvent>;
private _onAfterOperation: Emitter<FileOperationEvent>;

constructor(
Expand Down Expand Up @@ -310,4 +310,4 @@ export class FileService implements IFileService {
// Dispose service
this.raw.dispose();
}
}
}
110 changes: 110 additions & 0 deletions src/vs/workbench/services/files/electron-browser/remoteFileService.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
'use strict';

import URI from 'vs/base/common/uri';
import { FileService } from 'vs/workbench/services/files/electron-browser/fileService';
import { IContent, IStreamContent, IFileStat, IResolveContentOptions, IUpdateContentOptions, FileChangesEvent, FileChangeType } from "vs/platform/files/common/files";
import { TPromise } from "vs/base/common/winjs.base";
import Event from "vs/base/common/event";
import { EventEmitter } from "events";
import { basename } from "path";
import { IDisposable } from "vs/base/common/lifecycle";

export interface IRemoteFileSystemProvider {
onDidChange: Event<URI>;
resolve(resource: URI): TPromise<string>;
update(resource: URI, content: string): TPromise<any>;
}

export class RemoteFileService extends FileService {

private readonly _provider = new Map<string, IRemoteFileSystemProvider>();

registerProvider(authority: string, provider: IRemoteFileSystemProvider): IDisposable {
if (this._provider.has(authority)) {
throw new Error();
}

this._provider.set(authority, provider);
const reg = provider.onDidChange(e => {
// forward change events
this._onFileChanges.fire(new FileChangesEvent([{ resource: e, type: FileChangeType.UPDATED }]));
});
return {
dispose: () => {
this._provider.delete(authority);
reg.dispose();
}
};
}

// --- resolve

resolveContent(resource: URI, options?: IResolveContentOptions): TPromise<IContent> {
if (this._provider.has(resource.authority)) {
return this._doResolveContent(resource);
}

return super.resolveContent(resource, options);
}

resolveStreamContent(resource: URI, options?: IResolveContentOptions): TPromise<IStreamContent> {
if (this._provider.has(resource.authority)) {
return this._doResolveContent(resource).then(RemoteFileService._asStreamContent);
}

return super.resolveStreamContent(resource, options);
}

private async _doResolveContent(resource: URI): TPromise<IContent> {

const stat = RemoteFileService._createFakeStat(resource);
const value = await this._provider.get(resource.authority).resolve(resource);
return <any>{ ...stat, value };
}

// --- saving

updateContent(resource: URI, value: string, options?: IUpdateContentOptions): TPromise<IFileStat> {
if (this._provider.has(resource.authority)) {
return this._doUpdateContent(resource, value).then(RemoteFileService._createFakeStat);
}

return super.updateContent(resource, value, options);
}

private async _doUpdateContent(resource: URI, content: string): TPromise<URI> {
await this._provider.get(resource.authority).update(resource, content);
return resource;
}

// --- util

private static _createFakeStat(resource: URI): IFileStat {

return <IFileStat>{
resource,
name: basename(resource.path),
encoding: 'utf8',
mtime: Date.now(),
etag: Date.now().toString(16),
isDirectory: false,
hasChildren: false
};
}

private static _asStreamContent(content: IContent): IStreamContent {
const emitter = new EventEmitter();
const { value } = content;
const result = <IStreamContent><any>content;
result.value = emitter;
setTimeout(() => {
emitter.emit('data', value);
emitter.emit('end');
}, 0);
return result;
}
}

0 comments on commit 1369a43

Please sign in to comment.