diff --git a/package.json b/package.json index 8107b39bc42..dba3a582d9e 100644 --- a/package.json +++ b/package.json @@ -33,7 +33,6 @@ "https-proxy-agent": "0.3.6", "iconv-lite": "0.4.15", "jschardet": "^1.5.1", - "jsftp": "^2.0.0", "keytar": "^4.0.3", "minimist": "1.2.0", "native-keymap": "1.2.5", diff --git a/src/vs/platform/files/common/files.ts b/src/vs/platform/files/common/files.ts index 0c78c12054c..6059ab7e0de 100644 --- a/src/vs/platform/files/common/files.ts +++ b/src/vs/platform/files/common/files.ts @@ -14,6 +14,7 @@ import { createDecorator } from 'vs/platform/instantiation/common/instantiation' import Event from 'vs/base/common/event'; import { beginsWithIgnoreCase } from 'vs/base/common/strings'; import { IProgress } from 'vs/platform/progress/common/progress'; +import { IDisposable } from 'vs/base/common/lifecycle'; export const IFileService = createDecorator('fileService'); @@ -31,6 +32,11 @@ export interface IFileService { */ onAfterOperation: Event; + /** + * + */ + registerProvider?(authority: string, provider: IFileSystemProvider): IDisposable; + /** * Resolve the properties of a file identified by the resource. * @@ -166,7 +172,7 @@ export interface IStat { export interface IFileSystemProvider { - onDidChange?: Event; + onDidChange?: Event; // more... // diff --git a/src/vs/vscode.proposed.d.ts b/src/vs/vscode.proposed.d.ts index 575bde2236b..d11c7f4f541 100644 --- a/src/vs/vscode.proposed.d.ts +++ b/src/vs/vscode.proposed.d.ts @@ -25,17 +25,48 @@ declare module 'vscode' { export function showSaveDialog(options: SaveDialogOptions): Thenable; } + export enum FileChangeType { + Updated = 0, + Added = 1, + Deleted = 2 + } + + export interface FileChange { + type: FileChangeType; + resource: Uri; + } + + export enum FileType { + File = 0, + Dir = 1, + Symlink = 2 + } + + export interface FileStat { + resource: Uri; + mtime: number; + size: number; + type: FileType; + } + // 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; + onDidChange?: Event; - // -- search - // todo@joh - extract into its own provider? - findFiles(query: string, progress: Progress, token?: CancellationToken): Thenable; + root: Uri; + + // more... + // + utimes(resource: Uri, mtime: number): Thenable; + stat(resource: Uri): Thenable; + read(resource: Uri, progress: Progress): Thenable; + write(resource: Uri, content: Uint8Array): Thenable; + unlink(resource: Uri): Thenable; + rename(resource: Uri, target: Uri): Thenable; + mkdir(resource: Uri): Thenable; + readdir(resource: Uri): Thenable; + rmdir(resource: Uri): Thenable; } export namespace workspace { diff --git a/src/vs/workbench/api/electron-browser/extensionHost.contribution.ts b/src/vs/workbench/api/electron-browser/extensionHost.contribution.ts index 746d242419b..be065d33308 100644 --- a/src/vs/workbench/api/electron-browser/extensionHost.contribution.ts +++ b/src/vs/workbench/api/electron-browser/extensionHost.contribution.ts @@ -28,6 +28,7 @@ import './mainThreadEditor'; import './mainThreadEditors'; import './mainThreadErrors'; import './mainThreadExtensionService'; +import './mainThreadFileSystem'; import './mainThreadFileSystemEventService'; import './mainThreadHeapService'; import './mainThreadLanguageFeatures'; diff --git a/src/vs/workbench/api/electron-browser/mainThreadFileSystem.ts b/src/vs/workbench/api/electron-browser/mainThreadFileSystem.ts new file mode 100644 index 00000000000..2699aee0f75 --- /dev/null +++ b/src/vs/workbench/api/electron-browser/mainThreadFileSystem.ts @@ -0,0 +1,127 @@ +/*--------------------------------------------------------------------------------------------- + * 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 { TPromise } from 'vs/base/common/winjs.base'; +import { ExtHostContext, MainContext, IExtHostContext, MainThreadFileSystemShape, ExtHostFileSystemShape } from '../node/extHost.protocol'; +import { IFileService, IFileSystemProvider, IStat, IFileChange } from 'vs/platform/files/common/files'; +import { IDisposable, dispose } from 'vs/base/common/lifecycle'; +import Event, { Emitter } from 'vs/base/common/event'; +import { extHostNamedCustomer } from 'vs/workbench/api/electron-browser/extHostCustomers'; +import { IProgress } from 'vs/platform/progress/common/progress'; +import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace'; + +@extHostNamedCustomer(MainContext.MainThreadFileSystem) +export class MainThreadFileSystem implements MainThreadFileSystemShape { + + private readonly _toDispose: IDisposable[] = []; + private readonly _proxy: ExtHostFileSystemShape; + private readonly _provider = new Map(); + + constructor( + extHostContext: IExtHostContext, + @IFileService private readonly _fileService: IFileService, + @IWorkspaceContextService private readonly _workspaceEditService: IWorkspaceContextService + ) { + this._proxy = extHostContext.get(ExtHostContext.ExtHostFileSystem); + } + + dispose(): void { + dispose(this._toDispose); + } + + $registerFileSystemProvider(handle: number, scheme: string): void { + this._provider.set(handle, new RemoteFileSystemProvider(this._fileService, scheme, handle, this._proxy)); + } + + $unregisterFileSystemProvider(handle: number): void { + dispose(this._provider.get(handle)); + this._provider.delete(handle); + } + + $onDidAddFileSystemRoot(uri: URI): void { + const folders = this._workspaceEditService.getWorkspace().folders.slice(0); + folders.push({ + uri, + name: uri.authority, + index: folders.length, + raw: null + }); + (this._workspaceEditService.getWorkspace()).folders = folders; + (this._workspaceEditService).onFoldersChanged(); + (this._workspaceEditService)._onDidChangeWorkspaceFolders.fire(null); + } + + $onFileSystemChange(handle: number, changes: IFileChange[]): void { + this._provider.get(handle).$onFileSystemChange(changes); + } + + $reportFileChunk(handle: number, resource: URI, chunk: number[]): void { + this._provider.get(handle).reportFileChunk(resource, chunk); + } +} + +class RemoteFileSystemProvider implements IFileSystemProvider { + + private readonly _onDidChange = new Emitter(); + private readonly _registration: IDisposable; + private readonly _reads = new Map>(); + + readonly onDidChange: Event = this._onDidChange.event; + + + constructor( + service: IFileService, + scheme: string, + private readonly _handle: number, + private readonly _proxy: ExtHostFileSystemShape + ) { + this._registration = service.registerProvider(scheme, this); + } + + dispose(): void { + this._registration.dispose(); + this._onDidChange.dispose(); + } + + $onFileSystemChange(changes: IFileChange[]): void { + this._onDidChange.fire(changes); + } + + // --- forwarding calls + + utimes(resource: URI, mtime: number): TPromise { + return this._proxy.$utimes(this._handle, resource, mtime); + } + stat(resource: URI): TPromise { + return this._proxy.$stat(this._handle, resource); + } + read(resource: URI, progress: IProgress): TPromise { + this._reads.set(resource.toString(), progress); + return this._proxy.$read(this._handle, resource); + } + reportFileChunk(resource: URI, chunk: number[]): void { + this._reads.get(resource.toString()).report(Buffer.from(chunk)); + } + write(resource: URI, content: Uint8Array): TPromise { + return this._proxy.$write(this._handle, resource, [].slice.call(content)); + } + unlink(resource: URI): TPromise { + return this._proxy.$unlink(this._handle, resource); + } + rename(resource: URI, target: URI): TPromise { + return this._proxy.$rename(this._handle, resource, target); + } + mkdir(resource: URI): TPromise { + return this._proxy.$mkdir(this._handle, resource); + } + readdir(resource: URI): TPromise { + return this._proxy.$readdir(this._handle, resource); + } + rmdir(resource: URI): TPromise { + return this._proxy.$rmdir(this._handle, resource); + } +} diff --git a/src/vs/workbench/api/electron-browser/mainThreadWorkspace.ts b/src/vs/workbench/api/electron-browser/mainThreadWorkspace.ts index 950cb025900..3b249dbc200 100644 --- a/src/vs/workbench/api/electron-browser/mainThreadWorkspace.ts +++ b/src/vs/workbench/api/electron-browser/mainThreadWorkspace.ts @@ -6,19 +6,17 @@ import { isPromiseCanceledError } from 'vs/base/common/errors'; import URI from 'vs/base/common/uri'; -import { ISearchService, QueryType, ISearchQuery, ISearchProgressItem, ISearchComplete } from 'vs/platform/search/common/search'; +import { ISearchService, QueryType, ISearchQuery } from 'vs/platform/search/common/search'; import { IWorkspaceContextService, WorkbenchState } from 'vs/platform/workspace/common/workspace'; import { IWorkbenchEditorService } from 'vs/workbench/services/editor/common/editorService'; import { ITextFileService } from 'vs/workbench/services/textfile/common/textfiles'; import { ICommonCodeEditor, isCommonCodeEditor } from 'vs/editor/common/editorCommon'; import { bulkEdit, IResourceEdit } from 'vs/editor/common/services/bulkEdit'; -import { TPromise, PPromise } from 'vs/base/common/winjs.base'; +import { TPromise } from 'vs/base/common/winjs.base'; import { MainThreadWorkspaceShape, ExtHostWorkspaceShape, ExtHostContext, MainContext, IExtHostContext } from '../node/extHost.protocol'; import { ITextModelService } from 'vs/editor/common/services/resolverService'; import { IFileService } from 'vs/platform/files/common/files'; -import { IDisposable, dispose, combinedDisposable } from 'vs/base/common/lifecycle'; -import { RemoteFileService } from 'vs/workbench/services/files/electron-browser/remoteFileService'; -import { Emitter } from 'vs/base/common/event'; +import { IDisposable, dispose } from 'vs/base/common/lifecycle'; import { extHostNamedCustomer } from 'vs/workbench/api/electron-browser/extHostCustomers'; import { IExperimentService } from 'vs/platform/telemetry/common/experiments'; @@ -36,7 +34,7 @@ export class MainThreadWorkspace implements MainThreadWorkspaceShape { @ITextFileService private readonly _textFileService: ITextFileService, @IWorkbenchEditorService private readonly _editorService: IWorkbenchEditorService, @ITextModelService private readonly _textModelResolverService: ITextModelService, - @IExperimentService private experimentService: IExperimentService, + @IExperimentService private _experimentService: IExperimentService, @IFileService private readonly _fileService: IFileService ) { this._proxy = extHostContext.get(ExtHostContext.ExtHostWorkspace); @@ -71,7 +69,7 @@ export class MainThreadWorkspace implements MainThreadWorkspaceShape { maxResults, includePattern: { [include]: true }, excludePattern: { [exclude]: true }, - useRipgrep: this.experimentService.getExperiments().ripgrepQuickSearch + useRipgrep: this._experimentService.getExperiments().ripgrepQuickSearch }; this._searchService.extendQuery(query); @@ -123,83 +121,5 @@ export class MainThreadWorkspace implements MainThreadWorkspaceShape { return bulkEdit(this._textModelResolverService, codeEditor, edits, this._fileService) .then(() => true); } - - // --- EXPERIMENT: workspace provider - - private _idPool: number = 0; - private readonly _provider = new Map]>(); - private readonly _searchSessions = new Map void, reject: Function, progress: (item: ISearchProgressItem) => void, matches: URI[] }>(); - - $registerFileSystemProvider(handle: number, authority: string): void { - if (!(this._fileService instanceof RemoteFileService)) { - throw new Error(); - } - const emitter = new Emitter(); - // const provider = { - // onDidChange: emitter.event, - // read: (resource: URI) => { - // return this._proxy.$resolveFile(handle, resource); - // }, - // write: (resource: URI, value: string) => { - // return this._proxy.$storeFile(handle, resource, value); - // }, - // stat: () => null, - // readdir: () => null - // }; - const searchProvider = { - search: (query: ISearchQuery) => { - if (query.type !== QueryType.File) { - return undefined; - } - const session = ++this._idPool; - return new PPromise((resolve, reject, progress) => { - this._searchSessions.set(session, { resolve, reject, progress, matches: [] }); - this._proxy.$startSearch(handle, session, query.filePattern); - }, () => { - this._proxy.$cancelSearch(handle, session); - }); - } - }; - const registrations = combinedDisposable([ - // this._fileService.registerProvider(authority, provider), - this._searchService.registerSearchResultProvider(searchProvider), - ]); - this._provider.set(handle, [registrations, emitter]); - } - - $unregisterFileSystemProvider(handle: number): void { - if (this._provider.has(handle)) { - dispose(this._provider.get(handle)[0]); - this._provider.delete(handle); - } - } - - $onFileSystemChange(handle: number, resource: URI) { - const [, emitter] = this._provider.get(handle); - emitter.fire(resource); - }; - - $updateSearchSession(session: number, data: URI): void { - if (this._searchSessions.has(session)) { - this._searchSessions.get(session).progress({ resource: data }); - this._searchSessions.get(session).matches.push(data); - } - } - - $finishSearchSession(session: number, err?: any): void { - if (this._searchSessions.has(session)) { - const { matches, resolve, reject } = this._searchSessions.get(session); - this._searchSessions.delete(session); - if (err) { - reject(err); - } else { - resolve({ - limitHit: false, - stats: undefined, - results: matches.map(resource => ({ resource })) - }); - } - } - } } diff --git a/src/vs/workbench/api/node/extHost.api.impl.ts b/src/vs/workbench/api/node/extHost.api.impl.ts index 4c9ed5a511f..8ca08b322f6 100644 --- a/src/vs/workbench/api/node/extHost.api.impl.ts +++ b/src/vs/workbench/api/node/extHost.api.impl.ts @@ -53,6 +53,8 @@ import { TextEditorCursorStyle } from 'vs/editor/common/config/editorOptions'; import { ExtHostThreadService } from 'vs/workbench/services/thread/node/extHostThreadService'; import { ProxyIdentifier } from 'vs/workbench/services/thread/common/threadService'; import { ExtHostDialogs } from 'vs/workbench/api/node/extHostDialogs'; +import { ExtHostFileSystem } from 'vs/workbench/api/node/extHostFileSystem'; +import { FileChangeType, FileType } from 'vs/platform/files/common/files'; export interface IExtensionApiFactory { (extension: IExtensionDescription): typeof vscode; @@ -93,6 +95,7 @@ export function createApiFactory( const extHostConfiguration = threadService.set(ExtHostContext.ExtHostConfiguration, new ExtHostConfiguration(threadService.get(MainContext.MainThreadConfiguration), extHostWorkspace, initData.configuration)); const extHostDiagnostics = threadService.set(ExtHostContext.ExtHostDiagnostics, new ExtHostDiagnostics(threadService)); const languageFeatures = threadService.set(ExtHostContext.ExtHostLanguageFeatures, new ExtHostLanguageFeatures(threadService, extHostDocuments, extHostCommands, extHostHeapService, extHostDiagnostics)); + const extHostFileSystem = threadService.set(ExtHostContext.ExtHostFileSystem, new ExtHostFileSystem(threadService)); const extHostFileSystemEvent = threadService.set(ExtHostContext.ExtHostFileSystemEventService, new ExtHostFileSystemEventService()); const extHostQuickOpen = threadService.set(ExtHostContext.ExtHostQuickOpen, new ExtHostQuickOpen(threadService)); const extHostTerminalService = threadService.set(ExtHostContext.ExtHostTerminalService, new ExtHostTerminalService(threadService)); @@ -477,7 +480,7 @@ export function createApiFactory( return extHostTask.registerTaskProvider(extension, provider); }, registerFileSystemProvider: proposedApiFunction(extension, (authority, provider) => { - return extHostWorkspace.registerFileSystemProvider(authority, provider); + return extHostFileSystem.registerFileSystemProvider(authority, provider); }) }; @@ -601,7 +604,11 @@ export function createApiFactory( ShellExecution: extHostTypes.ShellExecution, TaskScope: extHostTypes.TaskScope, Task: extHostTypes.Task, - ConfigurationTarget: extHostTypes.ConfigurationTarget + ConfigurationTarget: extHostTypes.ConfigurationTarget, + + // TODO@JOH + FileChangeType: FileChangeType, + FileType: FileType }; if (extension.enableProposedApi && extension.isBuiltin) { api['credentials'] = credentials; diff --git a/src/vs/workbench/api/node/extHost.protocol.ts b/src/vs/workbench/api/node/extHost.protocol.ts index 2a77558ba23..80e6c34dc05 100644 --- a/src/vs/workbench/api/node/extHost.protocol.ts +++ b/src/vs/workbench/api/node/extHost.protocol.ts @@ -49,6 +49,7 @@ import { ThemeColor } from 'vs/platform/theme/common/themeService'; import { IDisposable } from 'vs/base/common/lifecycle'; import { SerializedError } from 'vs/base/common/errors'; import { WorkspaceFolder } from 'vs/platform/workspace/common/workspace'; +import { IStat, IFileChange } from 'vs/platform/files/common/files'; export interface IEnvironment { isExtensionDevelopmentDebug: boolean; @@ -302,12 +303,15 @@ export interface MainThreadWorkspaceShape extends IDisposable { $cancelSearch(requestId: number): Thenable; $saveAll(includeUntitled?: boolean): Thenable; $applyWorkspaceEdit(edits: IResourceEdit[]): TPromise; +} - $registerFileSystemProvider(handle: number, authority: string): void; +export interface MainThreadFileSystemShape extends IDisposable { + $registerFileSystemProvider(handle: number, scheme: string): void; $unregisterFileSystemProvider(handle: number): void; - $onFileSystemChange(handle: number, resource: URI): void; - $updateSearchSession(session: number, data): void; - $finishSearchSession(session: number, err?: any): void; + + $onDidAddFileSystemRoot(root: URI): void; + $onFileSystemChange(handle: number, resource: IFileChange[]): void; + $reportFileChunk(handle: number, resource: URI, chunk: number[] | null): void; } export interface MainThreadTaskShape extends IDisposable { @@ -461,11 +465,18 @@ export interface ExtHostTreeViewsShape { export interface ExtHostWorkspaceShape { $acceptWorkspaceData(workspace: IWorkspaceData): void; +} - $resolveFile(handle: number, resource: URI): TPromise; - $storeFile(handle: number, resource: URI, content: string): TPromise; - $startSearch(handle: number, session: number, query: string): void; - $cancelSearch(handle: number, session: number): void; +export interface ExtHostFileSystemShape { + $utimes(handle: number, resource: URI, mtime: number): TPromise; + $stat(handle: number, resource: URI): TPromise; + $read(handle: number, resource: URI): TPromise; + $write(handle: number, resource: URI, content: number[]): TPromise; + $unlink(handle: number, resource: URI): TPromise; + $rename(handle: number, resource: URI, target: URI): TPromise; + $mkdir(handle: number, resource: URI): TPromise; + $readdir(handle: number, resource: URI): TPromise; + $rmdir(handle: number, resource: URI): TPromise; } export interface ExtHostExtensionServiceShape { @@ -602,6 +613,7 @@ export const MainContext = { MainThreadTelemetry: createMainId('MainThreadTelemetry'), MainThreadTerminalService: createMainId('MainThreadTerminalService'), MainThreadWorkspace: createMainId('MainThreadWorkspace'), + MainThreadFileSystem: createMainId('MainThreadFileSystem'), MainThreadExtensionService: createMainId('MainThreadExtensionService'), MainThreadSCM: createMainId('MainThreadSCM'), MainThreadTask: createMainId('MainThreadTask'), @@ -620,6 +632,7 @@ export const ExtHostContext = { ExtHostDocumentSaveParticipant: createExtId('ExtHostDocumentSaveParticipant'), ExtHostEditors: createExtId('ExtHostEditors'), ExtHostTreeViews: createExtId('ExtHostTreeViews'), + ExtHostFileSystem: createExtId('ExtHostFileSystem'), ExtHostFileSystemEventService: createExtId('ExtHostFileSystemEventService'), ExtHostHeapService: createExtId('ExtHostHeapMonitor'), ExtHostLanguageFeatures: createExtId('ExtHostLanguageFeatures'), diff --git a/src/vs/workbench/api/node/extHostFileSystem.ts b/src/vs/workbench/api/node/extHostFileSystem.ts new file mode 100644 index 00000000000..d46e8ac3f3a --- /dev/null +++ b/src/vs/workbench/api/node/extHostFileSystem.ts @@ -0,0 +1,75 @@ +/*--------------------------------------------------------------------------------------------- + * 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 { TPromise } from 'vs/base/common/winjs.base'; +import { MainContext, IMainContext, ExtHostFileSystemShape, MainThreadFileSystemShape } from './extHost.protocol'; +import * as vscode from 'vscode'; +import { IStat } from 'vs/platform/files/common/files'; +import { IDisposable } from 'vs/base/common/lifecycle'; + +export class ExtHostFileSystem implements ExtHostFileSystemShape { + + private readonly _proxy: MainThreadFileSystemShape; + private readonly _provider = new Map(); + private _handlePool: number = 0; + + constructor(mainContext: IMainContext) { + this._proxy = mainContext.get(MainContext.MainThreadFileSystem); + } + + registerFileSystemProvider(scheme: string, provider: vscode.FileSystemProvider) { + const handle = this._handlePool++; + this._provider.set(handle, provider); + this._proxy.$registerFileSystemProvider(handle, scheme); + this._proxy.$onDidAddFileSystemRoot(provider.root); + let reg: IDisposable; + if (provider.onDidChange) { + reg = provider.onDidChange(event => this._proxy.$onFileSystemChange(handle, event)); + } + return { + dispose: () => { + if (reg) { + reg.dispose(); + } + this._provider.delete(handle); + this._proxy.$unregisterFileSystemProvider(handle); + } + }; + } + + $utimes(handle: number, resource: URI, mtime: number): TPromise { + return TPromise.as(this._provider.get(handle).utimes(resource, mtime)); + } + $stat(handle: number, resource: URI): TPromise { + return TPromise.as(this._provider.get(handle).stat(resource)); + } + $read(handle: number, resource: URI): TPromise { + return TPromise.as(this._provider.get(handle).read(resource, { + report: (chunk) => { + this._proxy.$reportFileChunk(handle, resource, [].slice.call(chunk)); + } + })); + } + $write(handle: number, resource: URI, content: number[]): TPromise { + return TPromise.as(this._provider.get(handle).write(resource, Buffer.from(content))); + } + $unlink(handle: number, resource: URI): TPromise { + return TPromise.as(this._provider.get(handle).unlink(resource)); + } + $rename(handle: number, resource: URI, target: URI): TPromise { + return TPromise.as(this._provider.get(handle).rename(resource, target)); + } + $mkdir(handle: number, resource: URI): TPromise { + return TPromise.as(this._provider.get(handle).mkdir(resource)); + } + $readdir(handle: number, resource: URI): TPromise { + return TPromise.as(this._provider.get(handle).readdir(resource)); + } + $rmdir(handle: number, resource: URI): TPromise { + return TPromise.as(this._provider.get(handle).rmdir(resource)); + } +} diff --git a/src/vs/workbench/api/node/extHostWorkspace.ts b/src/vs/workbench/api/node/extHostWorkspace.ts index 43323d5c00c..c66128780f6 100644 --- a/src/vs/workbench/api/node/extHostWorkspace.ts +++ b/src/vs/workbench/api/node/extHostWorkspace.ts @@ -16,11 +16,7 @@ import { fromRange, EndOfLine } from 'vs/workbench/api/node/extHostTypeConverter import { IWorkspaceData, ExtHostWorkspaceShape, MainContext, MainThreadWorkspaceShape, IMainContext } 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'; import { TrieMap } from 'vs/base/common/map'; -import { CancellationTokenSource } from 'vs/base/common/cancellation'; -import { Progress } from 'vs/platform/progress/common/progress'; class Workspace2 extends Workspace { @@ -203,51 +199,4 @@ export class ExtHostWorkspace implements ExtHostWorkspaceShape { return this._proxy.$applyWorkspaceEdit(resourceEdits); } - // --- EXPERIMENT: workspace resolver - - - private _handlePool = 0; - private readonly _fsProvider = new Map(); - private readonly _searchSession = new Map(); - - registerFileSystemProvider(authority: string, provider: vscode.FileSystemProvider): vscode.Disposable { - const handle = ++this._handlePool; - this._fsProvider.set(handle, provider); - const reg = provider.onDidChange(e => this._proxy.$onFileSystemChange(handle, e)); - this._proxy.$registerFileSystemProvider(handle, authority); - return new Disposable(() => { - this._fsProvider.delete(handle); - reg.dispose(); - }); - } - - $resolveFile(handle: number, resource: URI): TPromise { - const provider = this._fsProvider.get(handle); - return asWinJsPromise(token => provider.resolveContents(resource)); - } - - $storeFile(handle: number, resource: URI, content: string): TPromise { - const provider = this._fsProvider.get(handle); - return asWinJsPromise(token => provider.writeContents(resource, content)); - } - - $startSearch(handle: number, session: number, query: string): void { - const provider = this._fsProvider.get(handle); - const source = new CancellationTokenSource(); - const progress = new Progress(chunk => this._proxy.$updateSearchSession(session, chunk)); - - this._searchSession.set(session, source); - TPromise.wrap(provider.findFiles(query, progress, source.token)).then(() => { - this._proxy.$finishSearchSession(session); - }, err => { - this._proxy.$finishSearchSession(session, err); - }); - } - - $cancelSearch(handle: number, session: number): void { - if (this._searchSession.has(session)) { - this._searchSession.get(session).cancel(); - this._searchSession.delete(session); - } - } } diff --git a/src/vs/workbench/services/configuration/node/configuration.ts b/src/vs/workbench/services/configuration/node/configuration.ts index ded7484b703..13a8dfa35a5 100644 --- a/src/vs/workbench/services/configuration/node/configuration.ts +++ b/src/vs/workbench/services/configuration/node/configuration.ts @@ -472,12 +472,6 @@ export class WorkspaceServiceImpl extends WorkspaceService { const workspaceConfigurationModel = this.workspaceConfiguration.workspaceConfigurationModel; const workspaceFolders = toWorkspaceFolders(workspaceConfigurationModel.folders, URI.file(paths.dirname(this.workspaceConfigPath.fsPath))); - workspaceFolders.push({ - uri: URI.parse('ftp://waws-prod-db3-029.ftp.azurewebsites.windows.net/'), - name: 'FTP Sample', - index: workspaceFolders.length, - raw: null - }); if (!workspaceFolders.length) { return TPromise.wrapError(new Error('Invalid workspace configuraton file ' + this.workspaceConfigPath)); } diff --git a/src/vs/workbench/services/files/electron-browser/ftpFileSystemProvider.ts b/src/vs/workbench/services/files/electron-browser/ftpFileSystemProvider.ts deleted file mode 100644 index df081ff4b54..00000000000 --- a/src/vs/workbench/services/files/electron-browser/ftpFileSystemProvider.ts +++ /dev/null @@ -1,146 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * 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 Event from 'vs/base/common/event'; -import * as JSFtp from 'jsftp'; -import { TPromise } from 'vs/base/common/winjs.base'; -import { Readable } from 'stream'; -import { join, dirname, basename } from 'path'; -import { IStat, FileType, IFileSystemProvider } from 'vs/platform/files/common/files'; -import { IProgress } from 'vs/platform/progress/common/progress'; - -export class FtpFileSystemProvider implements IFileSystemProvider { - - private _connection: TPromise; - - readonly onDidChange = Event.None; - - constructor() { - this._connection = new TPromise((resolve, reject) => { - const connection = new JSFtp({ - host: 'waws-prod-db3-029.ftp.azurewebsites.windows.net' - }); - connection.keepAlive(1000 * 5); - connection.auth('USER', 'PASS', (err) => { - if (err) { - reject(err); - } else { - resolve(connection); - } - }); - }); - } - - private _withConnection(func: keyof JSFtp, ...args: any[]): TPromise { - return this._connection.then(connection => { - return new TPromise((resolve, reject) => { - (connection[func]).apply(connection, args.concat([function (err, result) { - if (err) { - reject(err); - } else { - resolve(result); - } - }])); - }); - }); - } - - dispose(): void { - this._withConnection('raw', 'QUIT'); - - } - - utimes(resource: URI, mtime: number): TPromise { - return this._withConnection('raw', 'NOOP') - .then(() => this.stat(resource)); - } - - stat(resource: URI): TPromise { - const { path } = resource; - if (path === '/') { - // root directory - return TPromise.as({ - type: FileType.Dir, - resource, - mtime: 0, - size: 0 - }); - } - - const name = basename(path); - const dir = dirname(path); - return this._withConnection('ls', dir).then(entries => { - for (const entry of entries) { - if (entry.name === name) { - return { - resource, - mtime: entry.time, - size: entry.size, - type: entry.type - }; - } - } - // console.log(entries, name, resource); - throw new Error(`ENO: ${resource.path}`); - }); - } - - readdir(resource: URI): TPromise { - return this._withConnection('ls', resource.path).then(ret => { - const result: IStat[] = []; - for (let entry of ret) { - result.push({ - resource: resource.with({ path: join(resource.path, entry.name) }), - mtime: entry.time, - size: entry.size, - type: entry.type - }); - } - return result; - }); - } - - read(resource: URI, progress: IProgress): TPromise { - return this._withConnection('get', resource.path).then(stream => { - return new TPromise((resolve, reject) => { - stream.on('data', d => progress.report(d)); - stream.on('close', hadErr => { - if (hadErr) { - reject(hadErr); - } else { - resolve(undefined); - } - }); - stream.resume(); - }); - }); - } - - write(resource: URI, content: Uint8Array): TPromise { - return this._withConnection('put', content, resource.path); - } - - rmdir(resource: URI): TPromise { - return this._withConnection('raw', 'RMD', [resource.path]); - } - - mkdir(resource: URI): TPromise { - return this._withConnection('raw', 'MKD', [resource.path]); - } - - unlink(resource: URI): TPromise { - return this._withConnection('raw', 'DELE', [resource.path]); - } - - rename(resource: URI, target: URI): TPromise { - return this._withConnection('raw', 'RNFR', [resource.path]).then(() => { - return this._withConnection('raw', 'RNTO', [target.path]); - }); - } -} diff --git a/src/vs/workbench/services/files/electron-browser/jsftp.d.ts b/src/vs/workbench/services/files/electron-browser/jsftp.d.ts deleted file mode 100644 index 4d728c4d05c..00000000000 --- a/src/vs/workbench/services/files/electron-browser/jsftp.d.ts +++ /dev/null @@ -1,47 +0,0 @@ - - -import { Readable } from 'stream'; -import { EventEmitter } from 'events'; - -declare namespace JSFtp { - - - interface JSFtpOptions { - host: string; - port?: number | 21; - user?: string | 'anonymous'; - pass?: string | '@anonymous'; - useList?: boolean - } - - interface Callback { - (err: any, result: T): void; - } - - - interface Entry { - name: string; - size: number; - time: number; - type: 0 | 1; - } -} - -interface JSFtp extends EventEmitter { - auth(user: string, password: string, callback: JSFtp.Callback): void - keepAlive(wait?: number): void; - ls(path: string, callback: JSFtp.Callback): void; - list(path: string, callback: JSFtp.Callback): void; - put(buffer: Buffer, path: string, callback: JSFtp.Callback): void; - get(path: string, callback: JSFtp.Callback): void; - setType(type: 'A' | 'AN' | 'AT' | 'AC' | 'E' | 'I' | 'L', callback: JSFtp.Callback): void; - raw(command: string, args: any[], callback: JSFtp.Callback): void -} - -interface JSFtpConstructor { - new(options: JSFtp.JSFtpOptions): JSFtp; -} - -declare const JSFtp: JSFtpConstructor; - -export = JSFtp; diff --git a/src/vs/workbench/services/files/electron-browser/remoteFileService.ts b/src/vs/workbench/services/files/electron-browser/remoteFileService.ts index b5c9f9096d1..f5dd9610f56 100644 --- a/src/vs/workbench/services/files/electron-browser/remoteFileService.ts +++ b/src/vs/workbench/services/files/electron-browser/remoteFileService.ts @@ -6,11 +6,10 @@ import URI from 'vs/base/common/uri'; import { FileService } from 'vs/workbench/services/files/electron-browser/fileService'; -import { IContent, IStreamContent, IFileStat, IResolveContentOptions, IUpdateContentOptions, IResolveFileOptions, IResolveFileResult, FileOperationEvent, FileOperation, IFileSystemProvider, IStat, FileType, IImportResult } from 'vs/platform/files/common/files'; +import { IContent, IStreamContent, IFileStat, IResolveContentOptions, IUpdateContentOptions, IResolveFileOptions, IResolveFileResult, FileOperationEvent, FileOperation, IFileSystemProvider, IStat, FileType, IImportResult, FileChangesEvent } from 'vs/platform/files/common/files'; import { TPromise } from 'vs/base/common/winjs.base'; import { basename, join } from 'path'; import { IDisposable } from 'vs/base/common/lifecycle'; -import * as Ftp from './ftpFileSystemProvider'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace'; import { IWorkbenchEditorService } from 'vs/workbench/services/editor/common/editorService'; @@ -60,7 +59,6 @@ function toIFileStat(provider: IFileSystemProvider, stat: IStat, recurse?: (stat } } - export function toDeepIFileStat(provider: IFileSystemProvider, stat: IStat, to: URI[]): TPromise { const trie = new TrieMap(); @@ -102,7 +100,6 @@ export class RemoteFileService extends FileService { storageService, textResourceConfigurationService, ); - this.registerProvider('ftp', new Ftp.FtpFileSystemProvider()); } registerProvider(authority: string, provider: IFileSystemProvider): IDisposable { @@ -111,9 +108,9 @@ export class RemoteFileService extends FileService { } this._provider.set(authority, provider); - const reg = provider.onDidChange(e => { + const reg = provider.onDidChange(changes => { // forward change events - this._onFileChanges.fire(e); + this._onFileChanges.fire(new FileChangesEvent(changes)); }); return { dispose: () => { @@ -201,7 +198,7 @@ export class RemoteFileService extends FileService { const encoding = this.getEncoding(resource); const stream = decodeStream(encoding); - await provider.read(resource, new Progress(chunk => stream.write(chunk))); + await provider.read(resource, new Progress(chunk => stream.write(chunk))); stream.end(); return { @@ -391,10 +388,15 @@ export class RemoteFileService extends FileService { return toIFileStat(provider, stat); } - // public watchFileChanges(resource: URI): void { - // throw new Error("Method not implemented."); - // } - // public unwatchFileChanges(resource: URI): void { - // throw new Error("Method not implemented."); - // } + // TODO@Joh - file watching on demand! + public watchFileChanges(resource: URI): void { + if (!this._provider.has(resource.scheme)) { + super.watchFileChanges(resource); + } + } + public unwatchFileChanges(resource: URI): void { + if (!this._provider.has(resource.scheme)) { + super.unwatchFileChanges(resource); + } + } }