diff --git a/src/vs/platform/files/common/files.ts b/src/vs/platform/files/common/files.ts index 5bf160442d6..55780ce1277 100644 --- a/src/vs/platform/files/common/files.ts +++ b/src/vs/platform/files/common/files.ts @@ -1115,7 +1115,7 @@ export function etag(mtime: number | undefined, size: number | undefined): strin // TODO@ben remove traces of legacy file service export const ILegacyFileService = createDecorator('legacyFileService'); -export interface ILegacyFileService { +export interface ILegacyFileService extends IDisposable { _serviceBrand: any; encoding: IResourceEncodings; @@ -1123,11 +1123,13 @@ export interface ILegacyFileService { onFileChanges: Event; onAfterOperation: Event; + registerProvider(scheme: string, provider: IFileSystemProvider): IDisposable; + resolveContent(resource: URI, options?: IResolveContentOptions): Promise; resolveStreamContent(resource: URI, options?: IResolveContentOptions): Promise; - updateContent(resource: URI, value: string | ITextSnapshot, options?: IUpdateContentOptions): Promise; + updateContent(resource: URI, value: string | ITextSnapshot, options?: IUpdateContentOptions): Promise; - createFile(resource: URI, content?: string, options?: ICreateFileOptions): Promise; + createFile(resource: URI, content?: string, options?: ICreateFileOptions): Promise; } \ No newline at end of file diff --git a/src/vs/workbench/contrib/extensions/test/electron-browser/extensionsTipsService.test.ts b/src/vs/workbench/contrib/extensions/test/electron-browser/extensionsTipsService.test.ts index 35327a984db..7aa7fa4f630 100644 --- a/src/vs/workbench/contrib/extensions/test/electron-browser/extensionsTipsService.test.ts +++ b/src/vs/workbench/contrib/extensions/test/electron-browser/extensionsTipsService.test.ts @@ -26,7 +26,6 @@ import { TestNotificationService } from 'vs/platform/notification/test/common/te import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { URI } from 'vs/base/common/uri'; import { testWorkspace } from 'vs/platform/workspace/test/common/testWorkspace'; -import { IFileService } from 'vs/platform/files/common/files'; import { FileService } from 'vs/workbench/services/files/node/fileService'; import { TestConfigurationService } from 'vs/platform/configuration/test/common/testConfigurationService'; import { IPager } from 'vs/base/common/paging'; @@ -48,6 +47,11 @@ import { TestExperimentService } from 'vs/workbench/contrib/experiments/test/ele import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage'; import { ExtensionType } from 'vs/platform/extensions/common/extensions'; import { ISharedProcessService } from 'vs/platform/ipc/electron-browser/sharedProcessService'; +import { FileService2 } from 'vs/workbench/services/files2/common/fileService2'; +import { NullLogService } from 'vs/platform/log/common/log'; +import { Schemas } from 'vs/base/common/network'; +import { DiskFileSystemProvider } from 'vs/workbench/services/files2/node/diskFileSystemProvider'; +import { IFileService } from 'vs/platform/files/common/files'; const mockExtensionGallery: IGalleryExtension[] = [ aGalleryExtension('MockExtension1', { @@ -279,7 +283,20 @@ suite('ExtensionsTipsService Test', () => { const myWorkspace = testWorkspace(URI.from({ scheme: 'file', path: folderDir })); workspaceService = new TestContextService(myWorkspace); instantiationService.stub(IWorkspaceContextService, workspaceService); - instantiationService.stub(IFileService, new FileService(workspaceService, TestEnvironmentService, new TestTextResourceConfigurationService(), new TestConfigurationService(), new TestLifecycleService(), new TestStorageService(), new TestNotificationService(), { disableWatcher: true })); + const fileService = new FileService2(new NullLogService()); + fileService.registerProvider(Schemas.file, new DiskFileSystemProvider(new NullLogService())); + fileService.setLegacyService(new FileService( + fileService, + workspaceService, + TestEnvironmentService, + new TestTextResourceConfigurationService(), + new TestConfigurationService(), + new TestLifecycleService(), + new TestStorageService(), + new TestNotificationService(), + { disableWatcher: true }) + ); + instantiationService.stub(IFileService, fileService); } function testNoPromptForValidRecommendations(recommendations: string[]) { diff --git a/src/vs/workbench/services/backup/test/electron-browser/backupFileService.test.ts b/src/vs/workbench/services/backup/test/electron-browser/backupFileService.test.ts index a74b701eab8..fd227ee64f2 100644 --- a/src/vs/workbench/services/backup/test/electron-browser/backupFileService.test.ts +++ b/src/vs/workbench/services/backup/test/electron-browser/backupFileService.test.ts @@ -23,6 +23,9 @@ import { DefaultEndOfLine } from 'vs/editor/common/model'; import { snapshotToString } from 'vs/platform/files/common/files'; import { Schemas } from 'vs/base/common/network'; import { IWindowConfiguration } from 'vs/platform/windows/common/windows'; +import { FileService2 } from 'vs/workbench/services/files2/common/fileService2'; +import { NullLogService } from 'vs/platform/log/common/log'; +import { DiskFileSystemProvider } from 'vs/workbench/services/files2/node/diskFileSystemProvider'; const parentDir = getRandomTestPath(os.tmpdir(), 'vsctests', 'backupfileservice'); const backupHome = path.join(parentDir, 'Backups'); @@ -55,7 +58,19 @@ class TestBackupWindowService extends TestWindowService { class TestBackupFileService extends BackupFileService { constructor(workspace: Uri, backupHome: string, workspacesJsonPath: string) { - const fileService = new FileService(new TestContextService(new Workspace(workspace.fsPath, toWorkspaceFolders([{ path: workspace.fsPath }]))), TestEnvironmentService, new TestTextResourceConfigurationService(), new TestConfigurationService(), new TestLifecycleService(), new TestStorageService(), new TestNotificationService(), { disableWatcher: true }); + const fileService = new FileService2(new NullLogService()); + fileService.registerProvider(Schemas.file, new DiskFileSystemProvider(new NullLogService())); + fileService.setLegacyService(new FileService( + fileService, + new TestContextService(new Workspace(workspace.fsPath, toWorkspaceFolders([{ path: workspace.fsPath }]))), + TestEnvironmentService, + new TestTextResourceConfigurationService(), + new TestConfigurationService(), + new TestLifecycleService(), + new TestStorageService(), + new TestNotificationService(), + { disableWatcher: true }) + ); const windowService = new TestBackupWindowService(workspaceBackupPath); super(windowService, fileService); diff --git a/src/vs/workbench/services/configuration/test/electron-browser/configurationEditingService.test.ts b/src/vs/workbench/services/configuration/test/electron-browser/configurationEditingService.test.ts index 668844e8040..b024e716c8f 100644 --- a/src/vs/workbench/services/configuration/test/electron-browser/configurationEditingService.test.ts +++ b/src/vs/workbench/services/configuration/test/electron-browser/configurationEditingService.test.ts @@ -21,7 +21,6 @@ import { IConfigurationRegistry, Extensions as ConfigurationExtensions } from 'v import { WorkspaceService } from 'vs/workbench/services/configuration/node/configurationService'; import { FileService } from 'vs/workbench/services/files/node/fileService'; import { ConfigurationEditingService, ConfigurationEditingError, ConfigurationEditingErrorCode } from 'vs/workbench/services/configuration/common/configurationEditingService'; -import { IFileService } from 'vs/platform/files/common/files'; import { WORKSPACE_STANDALONE_CONFIGURATIONS } from 'vs/workbench/services/configuration/common/configuration'; import { IConfigurationService, ConfigurationTarget } from 'vs/platform/configuration/common/configuration'; import { TestInstantiationService } from 'vs/platform/instantiation/test/common/instantiationServiceMock'; @@ -38,6 +37,11 @@ import { createHash } from 'crypto'; import { IWindowConfiguration } from 'vs/platform/windows/common/windows'; import { RemoteAgentService } from 'vs/workbench/services/remote/electron-browser/remoteAgentServiceImpl'; import { IRemoteAgentService } from 'vs/workbench/services/remote/common/remoteAgentService'; +import { FileService2 } from 'vs/workbench/services/files2/common/fileService2'; +import { NullLogService } from 'vs/platform/log/common/log'; +import { Schemas } from 'vs/base/common/network'; +import { DiskFileSystemProvider } from 'vs/workbench/services/files2/node/diskFileSystemProvider'; +import { IFileService } from 'vs/platform/files/common/files'; class SettingsTestEnvironmentService extends EnvironmentService { @@ -107,7 +111,20 @@ suite('ConfigurationEditingService', () => { instantiationService.stub(IWorkspaceContextService, workspaceService); return workspaceService.initialize(noWorkspace ? { id: '' } : { folder: URI.file(workspaceDir), id: createHash('md5').update(URI.file(workspaceDir).toString()).digest('hex') }).then(() => { instantiationService.stub(IConfigurationService, workspaceService); - instantiationService.stub(IFileService, new FileService(workspaceService, TestEnvironmentService, new TestTextResourceConfigurationService(), new TestConfigurationService(), new TestLifecycleService(), new TestStorageService(), new TestNotificationService(), { disableWatcher: true })); + const fileService = new FileService2(new NullLogService()); + fileService.registerProvider(Schemas.file, new DiskFileSystemProvider(new NullLogService())); + fileService.setLegacyService(new FileService( + fileService, + workspaceService, + TestEnvironmentService, + new TestTextResourceConfigurationService(), + new TestConfigurationService(), + new TestLifecycleService(), + new TestStorageService(), + new TestNotificationService(), + { disableWatcher: true }) + ); + instantiationService.stub(IFileService, fileService); instantiationService.stub(ITextFileService, instantiationService.createInstance(TestTextFileService)); instantiationService.stub(ITextModelService, instantiationService.createInstance(TextModelResolverService)); instantiationService.stub(ICommandService, CommandService); diff --git a/src/vs/workbench/services/configuration/test/electron-browser/configurationService.test.ts b/src/vs/workbench/services/configuration/test/electron-browser/configurationService.test.ts index 8ef0255aea1..692b6da6475 100644 --- a/src/vs/workbench/services/configuration/test/electron-browser/configurationService.test.ts +++ b/src/vs/workbench/services/configuration/test/electron-browser/configurationService.test.ts @@ -32,7 +32,7 @@ import { TextModelResolverService } from 'vs/workbench/services/textmodelResolve import { IJSONEditingService } from 'vs/workbench/services/configuration/common/jsonEditing'; import { JSONEditingService } from 'vs/workbench/services/configuration/common/jsonEditingService'; import { createHash } from 'crypto'; -import { Emitter, Event } from 'vs/base/common/event'; +import { Emitter } from 'vs/base/common/event'; import { Schemas } from 'vs/base/common/network'; import { originalFSPath } from 'vs/base/common/resources'; import { isLinux } from 'vs/base/common/platform'; @@ -41,6 +41,9 @@ import { IWindowConfiguration } from 'vs/platform/windows/common/windows'; import { RemoteAgentService } from 'vs/workbench/services/remote/electron-browser/remoteAgentServiceImpl'; import { RemoteAuthorityResolverService } from 'vs/platform/remote/electron-browser/remoteAuthorityResolverService'; import { IRemoteAgentService } from 'vs/workbench/services/remote/common/remoteAgentService'; +import { FileService2 } from 'vs/workbench/services/files2/common/fileService2'; +import { NullLogService } from 'vs/platform/log/common/log'; +import { DiskFileSystemProvider } from 'vs/workbench/services/files2/node/diskFileSystemProvider'; class SettingsTestEnvironmentService extends EnvironmentService { @@ -224,10 +227,19 @@ suite('WorkspaceContextService - Workspace Editing', () => { instantiationService.stub(IEnvironmentService, environmentService); return workspaceService.initialize(getWorkspaceIdentifier(configPath)).then(() => { - - const fileService = new (class TestFileService extends FileService { - get onFileChanges(): Event { return fileChangeEvent.event; } - })(workspaceService, TestEnvironmentService, new TestTextResourceConfigurationService(), workspaceService, new TestLifecycleService(), new TestStorageService(), new TestNotificationService(), { disableWatcher: true }); + const fileService = new FileService2(new NullLogService()); + fileService.registerProvider(Schemas.file, new DiskFileSystemProvider(new NullLogService())); + fileService.setLegacyService(new FileService( + fileService, + workspaceService, + TestEnvironmentService, + new TestTextResourceConfigurationService(), + workspaceService, + new TestLifecycleService(), + new TestStorageService(), + new TestNotificationService(), + { disableWatcher: true }) + ); instantiationService.stub(IFileService, fileService); instantiationService.stub(ITextFileService, instantiationService.createInstance(TestTextFileService)); instantiationService.stub(ITextModelService, instantiationService.createInstance(TextModelResolverService)); @@ -489,7 +501,19 @@ suite('WorkspaceService - Initialization', () => { instantiationService.stub(IEnvironmentService, environmentService); return workspaceService.initialize({ id: '' }).then(() => { - const fileService = new FileService(workspaceService, TestEnvironmentService, new TestTextResourceConfigurationService(), workspaceService, new TestLifecycleService(), new TestStorageService(), new TestNotificationService(), { disableWatcher: true }); + const fileService = new FileService2(new NullLogService()); + fileService.registerProvider(Schemas.file, new DiskFileSystemProvider(new NullLogService())); + fileService.setLegacyService(new FileService( + fileService, + workspaceService, + TestEnvironmentService, + new TestTextResourceConfigurationService(), + workspaceService, + new TestLifecycleService(), + new TestStorageService(), + new TestNotificationService(), + { disableWatcher: true }) + ); instantiationService.stub(IFileService, fileService); instantiationService.stub(ITextFileService, instantiationService.createInstance(TestTextFileService)); instantiationService.stub(ITextModelService, instantiationService.createInstance(TextModelResolverService)); @@ -746,7 +770,19 @@ suite('WorkspaceConfigurationService - Folder', () => { instantiationService.stub(IEnvironmentService, environmentService); return workspaceService.initialize(convertToWorkspacePayload(URI.file(folderDir))).then(() => { - const fileService = new FileService(workspaceService, TestEnvironmentService, new TestTextResourceConfigurationService(), workspaceService, new TestLifecycleService(), new TestStorageService(), new TestNotificationService(), { disableWatcher: true }); + const fileService = new FileService2(new NullLogService()); + fileService.registerProvider(Schemas.file, new DiskFileSystemProvider(new NullLogService())); + fileService.setLegacyService(new FileService( + fileService, + workspaceService, + TestEnvironmentService, + new TestTextResourceConfigurationService(), + workspaceService, + new TestLifecycleService(), + new TestStorageService(), + new TestNotificationService(), + { disableWatcher: true }) + ); instantiationService.stub(IFileService, fileService); instantiationService.stub(ITextFileService, instantiationService.createInstance(TestTextFileService)); instantiationService.stub(ITextModelService, instantiationService.createInstance(TextModelResolverService)); @@ -1037,8 +1073,19 @@ suite('WorkspaceConfigurationService-Multiroot', () => { instantiationService.stub(IEnvironmentService, environmentService); return workspaceService.initialize(getWorkspaceIdentifier(configPath)).then(() => { - - const fileService = new FileService(workspaceService, TestEnvironmentService, new TestTextResourceConfigurationService(), workspaceService, new TestLifecycleService(), new TestStorageService(), new TestNotificationService(), { disableWatcher: true }); + const fileService = new FileService2(new NullLogService()); + fileService.registerProvider(Schemas.file, new DiskFileSystemProvider(new NullLogService())); + fileService.setLegacyService(new FileService( + fileService, + workspaceService, + TestEnvironmentService, + new TestTextResourceConfigurationService(), + workspaceService, + new TestLifecycleService(), + new TestStorageService(), + new TestNotificationService(), + { disableWatcher: true }) + ); instantiationService.stub(IFileService, fileService); instantiationService.stub(ITextFileService, instantiationService.createInstance(TestTextFileService)); instantiationService.stub(ITextModelService, instantiationService.createInstance(TextModelResolverService)); diff --git a/src/vs/workbench/services/files/node/fileService.ts b/src/vs/workbench/services/files/node/fileService.ts index b166ce856dc..6abe4c704e2 100644 --- a/src/vs/workbench/services/files/node/fileService.ts +++ b/src/vs/workbench/services/files/node/fileService.ts @@ -6,17 +6,14 @@ import * as paths from 'vs/base/common/path'; import * as fs from 'fs'; import * as os from 'os'; -import * as crypto from 'crypto'; import * as assert from 'assert'; -import { isParent, FileOperation, FileOperationEvent, IContent, IResolveFileOptions, IResolveFileResult, IResolveContentOptions, IFileStat, IStreamContent, FileOperationError, FileOperationResult, IUpdateContentOptions, FileChangesEvent, ICreateFileOptions, IContentData, ITextSnapshot, IFilesConfiguration, IFileSystemProviderRegistrationEvent, IFileSystemProvider, ILegacyFileService, IFileStatWithMetadata, IFileService, IResolveMetadataFileOptions, FileSystemProviderCapabilities, IWatchOptions } from 'vs/platform/files/common/files'; +import { FileOperation, FileOperationEvent, IContent, IResolveContentOptions, IFileStat, IStreamContent, FileOperationError, FileOperationResult, IUpdateContentOptions, FileChangesEvent, ICreateFileOptions, IContentData, ITextSnapshot, IFilesConfiguration, ILegacyFileService, IFileStatWithMetadata, IFileService, IFileSystemProvider, etag } from 'vs/platform/files/common/files'; import { MAX_FILE_SIZE, MAX_HEAP_SIZE } from 'vs/platform/files/node/fileConstants'; -import { isEqualOrParent } from 'vs/base/common/extpath'; -import * as arrays from 'vs/base/common/arrays'; import * as objects from 'vs/base/common/objects'; import { timeout } from 'vs/base/common/async'; import { URI as uri } from 'vs/base/common/uri'; import * as nls from 'vs/nls'; -import { isWindows, isLinux, isMacintosh } from 'vs/base/common/platform'; +import { isWindows, isMacintosh } from 'vs/base/common/platform'; import { IDisposable, toDisposable, Disposable } from 'vs/base/common/lifecycle'; import { IWorkspaceContextService, WorkbenchState } from 'vs/platform/workspace/common/workspace'; import * as pfs from 'vs/base/node/pfs'; @@ -30,7 +27,6 @@ import { IConfigurationService } from 'vs/platform/configuration/common/configur import { IEnvironmentService } from 'vs/platform/environment/common/environment'; import { CancellationToken, CancellationTokenSource } from 'vs/base/common/cancellation'; import { ILifecycleService, LifecyclePhase } from 'vs/platform/lifecycle/common/lifecycle'; -import { getBaseLabel } from 'vs/base/common/labels'; import { Schemas } from 'vs/base/common/network'; import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage'; import { INotificationService, Severity } from 'vs/platform/notification/common/notification'; @@ -39,51 +35,13 @@ import product from 'vs/platform/product/node/product'; import { IEncodingOverride, ResourceEncodings } from 'vs/workbench/services/files/node/encoding'; import { createReadableOfSnapshot } from 'vs/workbench/services/files/node/streams'; import { withUndefinedAsNull } from 'vs/base/common/types'; -import { normalizeNFC } from 'vs/base/common/normalization'; export interface IFileServiceTestOptions { disableWatcher?: boolean; encodingOverride?: IEncodingOverride[]; } -interface IStatAndLink { - stat: fs.Stats; - isSymbolicLink: boolean; -} - -function statLink(path: string, callback: (error: Error | null, statAndIsLink: IStatAndLink | null) => void): void { - fs.lstat(path, (error, lstat) => { - if (error || lstat.isSymbolicLink()) { - fs.stat(path, (error, stat) => { - if (error) { - return callback(error, null); - } - - callback(null, { stat, isSymbolicLink: lstat && lstat.isSymbolicLink() }); - }); - } else { - callback(null, { stat: lstat, isSymbolicLink: false }); - } - }); -} - -function readdir(path: string, callback: (error: Error | null, files: string[]) => void): void { - // Mac: uses NFD unicode form on disk, but we want NFC - // See also https://github.com/nodejs/node/issues/2165 - if (isMacintosh) { - return fs.readdir(path, (error, children) => { - if (error) { - return callback(error, []); - } - - return callback(null, children.map(c => normalizeNFC(c))); - }); - } - - return fs.readdir(path, callback); -} - -export class FileService extends Disposable implements ILegacyFileService, IFileService { +export class FileService extends Disposable implements ILegacyFileService { _serviceBrand: any; @@ -99,16 +57,12 @@ export class FileService extends Disposable implements ILegacyFileService, IFile protected readonly _onAfterOperation: Emitter = this._register(new Emitter()); get onAfterOperation(): Event { return this._onAfterOperation.event; } - protected readonly _onDidChangeFileSystemProviderRegistrations = this._register(new Emitter()); - get onDidChangeFileSystemProviderRegistrations(): Event { return this._onDidChangeFileSystemProviderRegistrations.event; } - - readonly onWillActivateFileSystemProvider = Event.None; - private activeWorkspaceFileChangeWatcher: IDisposable | null; private _encoding: ResourceEncodings; constructor( + protected fileService: IFileService, private contextService: IWorkspaceContextService, private environmentService: IEnvironmentService, private textResourceConfigurationService: ITextResourceConfigurationService, @@ -234,18 +188,6 @@ export class FileService extends Disposable implements ILegacyFileService, IFile return Disposable.None; } - activateProvider(scheme: string): Promise { - return Promise.reject(new Error('not implemented')); - } - - canHandleResource(resource: uri): boolean { - return resource.scheme === Schemas.file; - } - - hasCapability(resource: uri, capability: FileSystemProviderCapabilities): Promise { - return Promise.resolve(false); - } - resolveContent(resource: uri, options?: IResolveContentOptions): Promise { return this.resolveStreamContent(resource, options).then(streamContent => { return new Promise((resolve, reject) => { @@ -304,7 +246,7 @@ export class FileService extends Disposable implements ILegacyFileService, IFile return Promise.reject(error); }; - const statsPromise = this.resolve(resource).then(stat => { + const statsPromise = this.fileService.resolve(resource).then(stat => { result.resource = stat.resource; result.name = stat.name; result.mtime = stat.mtime; @@ -677,7 +619,7 @@ export class FileService extends Disposable implements ILegacyFileService, IFile return writeFilePromise.then(() => { // resolve - return this.resolve(resource); + return this.fileService.resolve(resource); }); } @@ -722,7 +664,7 @@ export class FileService extends Disposable implements ILegacyFileService, IFile return pfs.rimraf(tmpPath, pfs.RimRafMode.MOVE).then(() => { // 4.) resolve again - return this.resolve(resource); + return this.fileService.resolve(resource); }); }); }); @@ -836,131 +778,6 @@ export class FileService extends Disposable implements ILegacyFileService, IFile )); } - move(source: uri, target: uri, overwrite?: boolean): Promise { - return this.moveOrCopyFile(source, target, false, !!overwrite); - } - - copy(source: uri, target: uri, overwrite?: boolean): Promise { - return this.moveOrCopyFile(source, target, true, !!overwrite); - } - - private moveOrCopyFile(source: uri, target: uri, keepCopy: boolean, overwrite: boolean): Promise { - const sourcePath = this.toAbsolutePath(source); - const targetPath = this.toAbsolutePath(target); - - // 1.) move / copy - return this.doMoveOrCopyFile(sourcePath, targetPath, keepCopy, overwrite).then(() => { - - // 2.) resolve - return this.doResolve(target, { resolveMetadata: true }).then(result => { - - // Events (unless it was a no-op because paths are identical) - if (sourcePath !== targetPath) { - this._onAfterOperation.fire(new FileOperationEvent(source, keepCopy ? FileOperation.COPY : FileOperation.MOVE, result)); - } - - return result; - }); - }); - } - - private doMoveOrCopyFile(sourcePath: string, targetPath: string, keepCopy: boolean, overwrite: boolean): Promise { - - // 1.) validate operation - if (isParent(targetPath, sourcePath, !isLinux)) { - return Promise.reject(new Error('Unable to move/copy when source path is parent of target path')); - } else if (sourcePath === targetPath) { - return Promise.resolve(); // no-op but not an error - } - - // 2.) check if target exists - return pfs.exists(targetPath).then(exists => { - const isCaseRename = sourcePath.toLowerCase() === targetPath.toLowerCase(); - - // Return early with conflict if target exists and we are not told to overwrite - if (exists && !isCaseRename && !overwrite) { - return Promise.reject(new FileOperationError(nls.localize('fileMoveConflict', "Unable to move/copy. File already exists at destination."), FileOperationResult.FILE_MOVE_CONFLICT)); - } - - // 3.) make sure target is deleted before we move/copy unless this is a case rename of the same file - let deleteTargetPromise: Promise = Promise.resolve(); - if (exists && !isCaseRename) { - if (isEqualOrParent(sourcePath, targetPath, !isLinux /* ignorecase */)) { - return Promise.reject(new Error(nls.localize('unableToMoveCopyError', "Unable to move/copy. File would replace folder it is contained in."))); // catch this corner case! - } - - deleteTargetPromise = this.del(uri.file(targetPath), { recursive: true }); - } - - return deleteTargetPromise.then(() => { - - // 4.) make sure parents exists - return pfs.mkdirp(paths.dirname(targetPath)).then(() => { - - // 4.) copy/move - if (keepCopy) { - return pfs.copy(sourcePath, targetPath); - } else { - return pfs.move(sourcePath, targetPath); - } - }); - }); - }); - } - - del(resource: uri, options?: { useTrash?: boolean, recursive?: boolean }): Promise { - if (options && options.useTrash) { - return this.doMoveItemToTrash(resource); - } - - return this.doDelete(resource, !!(options && options.recursive)); - } - - private doMoveItemToTrash(resource: uri): Promise { - const absolutePath = resource.fsPath; - - const shell = (require('electron') as any as Electron.RendererInterface).shell; // workaround for being able to run tests out of VSCode debugger - const result = shell.moveItemToTrash(absolutePath); - if (!result) { - return Promise.reject(new Error(isWindows ? nls.localize('binFailed', "Failed to move '{0}' to the recycle bin", paths.basename(absolutePath)) : nls.localize('trashFailed', "Failed to move '{0}' to the trash", paths.basename(absolutePath)))); - } - - this._onAfterOperation.fire(new FileOperationEvent(resource, FileOperation.DELETE)); - - return Promise.resolve(); - } - - private doDelete(resource: uri, recursive: boolean): Promise { - const absolutePath = this.toAbsolutePath(resource); - - let assertNonRecursiveDelete: Promise; - if (!recursive) { - assertNonRecursiveDelete = pfs.stat(absolutePath).then(stat => { - if (!stat.isDirectory()) { - return undefined; - } - - return pfs.readdir(absolutePath).then(children => { - if (children.length === 0) { - return undefined; - } - - return Promise.reject(new Error(nls.localize('deleteFailed', "Failed to delete non-empty folder '{0}'.", paths.basename(absolutePath)))); - }); - }, error => Promise.resolve() /* ignore errors */); - } else { - assertNonRecursiveDelete = Promise.resolve(); - } - - return assertNonRecursiveDelete.then(() => { - return pfs.rimraf(absolutePath, pfs.RimRafMode.MOVE).then(() => { - - // Events - this._onAfterOperation.fire(new FileOperationEvent(resource, FileOperation.DELETE)); - }); - }); - } - // Helpers private toAbsolutePath(arg1: uri | IFileStat): string { @@ -976,20 +793,6 @@ export class FileService extends Disposable implements ILegacyFileService, IFile return paths.normalize(resource.fsPath); } - private doResolve(resource: uri, options: IResolveMetadataFileOptions): Promise; - private doResolve(resource: uri, options?: IResolveFileOptions): Promise; - private doResolve(resource: uri, options: IResolveFileOptions = Object.create(null)): Promise { - return this.toStatResolver(resource).then(model => model.resolve(options)); - } - - private toStatResolver(resource: uri): Promise { - const absolutePath = this.toAbsolutePath(resource); - - return pfs.statLink(absolutePath).then(({ isSymbolicLink, stat }) => { - return new StatResolver(resource, isSymbolicLink, stat.isDirectory(), stat.mtime.getTime(), stat.size, this.environmentService.verbose ? err => this.handleError(err) : undefined); - }); - } - dispose(): void { super.dispose(); @@ -998,395 +801,4 @@ export class FileService extends Disposable implements ILegacyFileService, IFile this.activeWorkspaceFileChangeWatcher = null; } } - - // Tests only - - watch(resource: uri, opts?: IWatchOptions | undefined): IDisposable { - return Disposable.None; - } - - resolve(resource: uri, options?: IResolveFileOptions): Promise; - resolve(resource: uri, options: IResolveMetadataFileOptions): Promise; - resolve(resource: uri, options?: IResolveFileOptions): Promise { - return this.doResolve(resource, options); - } - - resolveAll(toResolve: { resource: uri, options?: IResolveFileOptions }[]): Promise { - return Promise.all(toResolve.map(resourceAndOptions => this.doResolve(resourceAndOptions.resource, resourceAndOptions.options) - .then(stat => ({ stat, success: true }), error => ({ stat: undefined, success: false })))); - } - - createFolder(resource: uri): Promise { - - // 1.) Create folder - const absolutePath = this.toAbsolutePath(resource); - return pfs.mkdirp(absolutePath).then(() => { - - // 2.) Resolve - return this.doResolve(resource, { resolveMetadata: true }).then(result => { - - // Events - this._onAfterOperation.fire(new FileOperationEvent(resource, FileOperation.CREATE, result)); - - return result; - }); - }); - } - - exists(resource: uri): Promise { - return this.resolve(resource).then(() => true, () => false); - } -} - -function etag(stat: fs.Stats): string; -function etag(size: number, mtime: number): string; -function etag(arg1: any, arg2?: any): string { - let size: number; - let mtime: number; - if (typeof arg2 === 'number') { - size = arg1; - mtime = arg2; - } else { - size = (arg1).size; - mtime = (arg1).mtime.getTime(); - } - - return `"${crypto.createHash('sha1').update(String(size) + String(mtime)).digest('hex')}"`; -} - -/** - * Executes the given function (fn) over the given array of items (list) in parallel and returns the resulting errors and results as - * array to the callback (callback). The resulting errors and results are evaluated by calling the provided callback function. - */ -function parallel(list: T[], fn: (item: T, callback: (err: Error | null, result: E | null) => void) => void, callback: (err: Array | null, result: E[]) => void): void { - const results = new Array(list.length); - const errors = new Array(list.length); - let didErrorOccur = false; - let doneCount = 0; - - if (list.length === 0) { - return callback(null, []); - } - - list.forEach((item, index) => { - fn(item, (error, result) => { - if (error) { - didErrorOccur = true; - results[index] = null; - errors[index] = error; - } else { - results[index] = result; - errors[index] = null; - } - - if (++doneCount === list.length) { - return callback(didErrorOccur ? errors : null, results); - } - }); - }); -} - -/** - * Executes the given function (fn) over the given array of items (param) in sequential order and returns the first occurred error or the result as - * array to the callback (callback). The resulting errors and results are evaluated by calling the provided callback function. The first param can - * either be a function that returns an array of results to loop in async fashion or be an array of items already. - */ -function loop(param: (callback: (error: Error, result: T[]) => void) => void, fn: (item: T, callback: (error: Error | null, result: E | null) => void, index: number, total: number) => void, callback: (error: Error | null, result: E[] | null) => void): void; -function loop(param: T[], fn: (item: T, callback: (error: Error | null, result: E | null) => void, index: number, total: number) => void, callback: (error: Error | null, result: E[] | null) => void): void; -function loop(param: any, fn: (item: any, callback: (error: Error | null, result: E | null) => void, index: number, total: number) => void, callback: (error: Error | null, result: E[] | null) => void): void { - - // Assert - assert.ok(param, 'Missing first parameter'); - assert.ok(typeof (fn) === 'function', 'Second parameter must be a function that is called for each element'); - assert.ok(typeof (callback) === 'function', 'Third parameter must be a function that is called on error and success'); - - // Param is function, execute to retrieve array - if (typeof (param) === 'function') { - try { - param((error: Error, result: E[]) => { - if (error) { - callback(error, null); - } else { - loop(result, fn, callback); - } - }); - } catch (error) { - callback(error, null); - } - } - - // Expect the param to be an array and loop over it - else { - const results: E[] = []; - - const looper: (i: number) => void = function (i: number): void { - - // Still work to do - if (i < param.length) { - - // Execute function on array element - try { - fn(param[i], (error: any, result: E) => { - - // A method might only send a boolean value as return value (e.g. fs.exists), support this case gracefully - if (error === true || error === false) { - result = error; - error = null; - } - - // Quit looping on error - if (error) { - callback(error, null); - } - - // Otherwise push result on stack and continue looping - else { - if (result) { //Could be that provided function is not returning a result - results.push(result); - } - - process.nextTick(() => { - looper(i + 1); - }); - } - }, i, param.length); - } catch (error) { - callback(error, null); - } - } - - // Done looping, pass back results too callback function - else { - callback(null, results); - } - }; - - // Start looping with first element in array - looper(0); - } -} - -function Sequence(sequences: { (...param: any[]): void; }[]): void { - - // Assert - assert.ok(sequences.length > 1, 'Need at least one error handler and one function to process sequence'); - sequences.forEach((sequence) => { - assert.ok(typeof (sequence) === 'function'); - }); - - // Execute in Loop - const errorHandler = sequences.splice(0, 1)[0]; //Remove error handler - let sequenceResult: any = null; - - loop(sequences, (sequence, clb) => { - const sequenceFunction = function (error: any, result: any): void { - - // A method might only send a boolean value as return value (e.g. fs.exists), support this case gracefully - if (error === true || error === false) { - result = error; - error = null; - } - - // Handle Error and Result - if (error) { - clb(error, null); - } else { - sequenceResult = result; //Remember result of sequence - clb(null, null); //Don't pass on result to Looper as we are not aggregating it - } - }; - - // We call the sequence function setting "this" to be the callback we define here - // and we pass in the "sequenceResult" as first argument. Doing all this avoids having - // to pass in a callback to the sequence because the callback is already "this". - try { - sequence.call(sequenceFunction, sequenceResult); - } catch (error) { - clb(error, null); - } - }, (error, result) => { - if (error) { - errorHandler(error); - } - }); -} - -/** - * Takes a variable list of functions to execute in sequence. The first function must be the error handler and the - * following functions can do arbitrary work. "this" must be used as callback value for async functions to continue - * through the sequence: - * sequence( - * function errorHandler(error) { - * clb(error, null); - * }, - * - * function doSomethingAsync() { - * fs.doAsync(path, this); - * }, - * - * function done(result) { - * clb(null, result); - * } - * ); - */ -function sequence(errorHandler: (error: Error) => void, ...sequences: Function[]): void; -function sequence(sequences: Function[]): void; -function sequence(sequences: any): void { - Sequence((Array.isArray(sequences)) ? sequences : Array.prototype.slice.call(arguments)); -} - -export class StatResolver { - private name: string; - private etag: string; - - constructor( - private resource: uri, - private isSymbolicLink: boolean, - private isDirectory: boolean, - private mtime: number, - private size: number, - private errorLogger?: (error: Error | string) => void - ) { - assert.ok(resource && resource.scheme === Schemas.file, `Invalid resource: ${resource}`); - - this.name = getBaseLabel(resource); - this.etag = etag(size, mtime); - } - - resolve(options: IResolveFileOptions | undefined): Promise { - - // General Data - const fileStat: IFileStat = { - resource: this.resource, - isDirectory: this.isDirectory, - isSymbolicLink: this.isSymbolicLink, - isReadonly: false, - name: this.name, - etag: this.etag, - size: this.size, - mtime: this.mtime - }; - - // File Specific Data - if (!this.isDirectory) { - return Promise.resolve(fileStat); - } - - // Directory Specific Data - else { - - // Convert the paths from options.resolveTo to absolute paths - let absoluteTargetPaths: string[] | null = null; - if (options && options.resolveTo) { - absoluteTargetPaths = []; - for (const resource of options.resolveTo) { - absoluteTargetPaths.push(resource.fsPath); - } - } - - return new Promise(resolve => { - - // Load children - this.resolveChildren(this.resource.fsPath, absoluteTargetPaths, !!(options && options.resolveSingleChildDescendants), children => { - if (children) { - children = arrays.coalesce(children); // we don't want those null children (could be permission denied when reading a child) - } - fileStat.children = children || []; - - resolve(fileStat); - }); - }); - } - } - - private resolveChildren(absolutePath: string, absoluteTargetPaths: string[] | null, resolveSingleChildDescendants: boolean, callback: (children: IFileStat[] | null) => void): void { - readdir(absolutePath, (error: Error, files: string[]) => { - if (error) { - if (this.errorLogger) { - this.errorLogger(error); - } - - return callback(null); // return - we might not have permissions to read the folder - } - - // for each file in the folder - parallel(files, (file: string, clb: (error: Error | null, children: IFileStat | null) => void) => { - const fileResource = uri.file(paths.resolve(absolutePath, file)); - let fileStat: fs.Stats; - let isSymbolicLink = false; - const $this = this; - - sequence( - function onError(error: Error): void { - if ($this.errorLogger) { - $this.errorLogger(error); - } - - clb(null, null); // return - we might not have permissions to read the folder or stat the file - }, - - function stat(this: any): void { - statLink(fileResource.fsPath, this); - }, - - function countChildren(this: any, statAndLink: IStatAndLink): void { - fileStat = statAndLink.stat; - isSymbolicLink = statAndLink.isSymbolicLink; - - if (fileStat.isDirectory()) { - readdir(fileResource.fsPath, (error, result) => { - this(null, result ? result.length : 0); - }); - } else { - this(null, 0); - } - }, - - function resolve(childCount: number): void { - const childStat: IFileStat = { - resource: fileResource, - isDirectory: fileStat.isDirectory(), - isSymbolicLink, - isReadonly: false, - name: file, - mtime: fileStat.mtime.getTime(), - etag: etag(fileStat), - size: fileStat.size - }; - - // Return early for files - if (!fileStat.isDirectory()) { - return clb(null, childStat); - } - - // Handle Folder - let resolveFolderChildren = false; - if (files.length === 1 && resolveSingleChildDescendants) { - resolveFolderChildren = true; - } else if (childCount > 0 && absoluteTargetPaths && absoluteTargetPaths.some(targetPath => isEqualOrParent(targetPath, fileResource.fsPath, !isLinux /* ignorecase */))) { - resolveFolderChildren = true; - } - - // Continue resolving children based on condition - if (resolveFolderChildren) { - $this.resolveChildren(fileResource.fsPath, absoluteTargetPaths, resolveSingleChildDescendants, children => { - if (children) { - children = arrays.coalesce(children); // we don't want those null children - } - childStat.children = children || []; - - clb(null, childStat); - }); - } - - // Otherwise return result - else { - clb(null, childStat); - } - }); - }, (errors, result) => { - callback(result); - }); - }); - } } diff --git a/src/vs/workbench/services/files/node/remoteFileService.ts b/src/vs/workbench/services/files/node/remoteFileService.ts index 20963e400ac..e7c625cd75a 100644 --- a/src/vs/workbench/services/files/node/remoteFileService.ts +++ b/src/vs/workbench/services/files/node/remoteFileService.ts @@ -26,7 +26,7 @@ export class RemoteFileService extends FileService { private readonly _provider: Map; constructor( - @IFileService private readonly _fileService: IFileService, + @IFileService fileService: IFileService, @IStorageService storageService: IStorageService, @IEnvironmentService environmentService: IEnvironmentService, @IConfigurationService configurationService: IConfigurationService, @@ -36,6 +36,7 @@ export class RemoteFileService extends FileService { @ITextResourceConfigurationService textResourceConfigurationService: ITextResourceConfigurationService, ) { super( + fileService, contextService, environmentService, textResourceConfigurationService, @@ -73,7 +74,7 @@ export class RemoteFileService extends FileService { } return Promise.all([ - this._fileService.activateProvider(resource.scheme) + this.fileService.activateProvider(resource.scheme) ]).then(() => { const provider = this._provider.get(resource.scheme); if (!provider) { @@ -107,7 +108,7 @@ export class RemoteFileService extends FileService { private _readFile(resource: URI, options: IResolveContentOptions = Object.create(null)): Promise { return this._withProvider(resource).then(provider => { - return this._fileService.resolve(resource).then(fileStat => { + return this.fileService.resolve(resource).then(fileStat => { if (fileStat.isDirectory) { // todo@joh cannot copy a folder @@ -176,7 +177,7 @@ export class RemoteFileService extends FileService { return this._withProvider(resource).then(RemoteFileService._throwIfFileSystemIsReadonly).then(provider => { - return this._fileService.createFolder(resources.dirname(resource)).then(() => { + return this.fileService.createFolder(resources.dirname(resource)).then(() => { const { encoding } = this.encoding.getWriteEncoding(resource); return this._writeFile(provider, resource, new StringSnapshot(content || ''), encoding, { create: true, overwrite: Boolean(options && options.overwrite) }); }); @@ -197,7 +198,7 @@ export class RemoteFileService extends FileService { return super.updateContent(resource, value, options); } else { return this._withProvider(resource).then(RemoteFileService._throwIfFileSystemIsReadonly).then(provider => { - return this._fileService.createFolder(resources.dirname(resource)).then(() => { + return this.fileService.createFolder(resources.dirname(resource)).then(() => { const snapshot = typeof value === 'string' ? new StringSnapshot(value) : value; return this._writeFile(provider, resource, snapshot, options && options.encoding, { create: true, overwrite: true }); }); @@ -215,7 +216,7 @@ export class RemoteFileService extends FileService { target.once('error', err => reject(err)); target.once('finish', (_: unknown) => resolve(undefined)); }).then(_ => { - return this._fileService.resolve(resource, { resolveMetadata: true }) as Promise; + return this.fileService.resolve(resource, { resolveMetadata: true }) as Promise; }); } diff --git a/src/vs/workbench/services/files/test/electron-browser/fileService.test.ts b/src/vs/workbench/services/files/test/electron-browser/fileService.test.ts index 64cdf958d45..12cfd52f3f6 100644 --- a/src/vs/workbench/services/files/test/electron-browser/fileService.test.ts +++ b/src/vs/workbench/services/files/test/electron-browser/fileService.test.ts @@ -21,6 +21,10 @@ import { TestConfigurationService } from 'vs/platform/configuration/test/common/ import { TextModel } from 'vs/editor/common/model/textModel'; import { IEncodingOverride } from 'vs/workbench/services/files/node/encoding'; import { getPathFromAmdModule } from 'vs/base/common/amd'; +import { FileService2 } from 'vs/workbench/services/files2/common/fileService2'; +import { NullLogService } from 'vs/platform/log/common/log'; +import { Schemas } from 'vs/base/common/network'; +import { DiskFileSystemProvider } from 'vs/workbench/services/files2/node/diskFileSystemProvider'; suite('FileService', () => { let service: FileService; @@ -32,8 +36,11 @@ suite('FileService', () => { testDir = path.join(parentDir, id); const sourceDir = getPathFromAmdModule(require, './fixtures/service'); + const fileService = new FileService2(new NullLogService()); + fileService.registerProvider(Schemas.file, new DiskFileSystemProvider(new NullLogService())); + return pfs.copy(sourceDir, testDir).then(() => { - service = new FileService(new TestContextService(new Workspace(testDir, toWorkspaceFolders([{ path: testDir }]))), TestEnvironmentService, new TestTextResourceConfigurationService(), new TestConfigurationService(), new TestLifecycleService(), new TestStorageService(), new TestNotificationService(), { disableWatcher: true }); + service = new FileService(fileService, new TestContextService(new Workspace(testDir, toWorkspaceFolders([{ path: testDir }]))), TestEnvironmentService, new TestTextResourceConfigurationService(), new TestConfigurationService(), new TestLifecycleService(), new TestStorageService(), new TestNotificationService(), { disableWatcher: true }); }); }); @@ -348,28 +355,6 @@ suite('FileService', () => { }); }); - // test('watch - support atomic save', function (done) { - // const toWatch = uri.file(path.join(testDir, 'index.html')); - - // service.watch(toWatch); - - // service.onFileChanges((e: FileChangesEvent) => { - // assert.ok(e); - - // service.unwatch(toWatch); - // done(); - // }); - - // setTimeout(() => { - // // Simulate atomic save by deleting the file, creating it under different name - // // and then replacing the previously deleted file with those contents - // const renamed = `${toWatch.fsPath}.bak`; - // fs.unlinkSync(toWatch.fsPath); - // fs.writeFileSync(renamed, 'Changes'); - // fs.renameSync(renamed, toWatch.fsPath); - // }, 100); - // }); - test('options - encoding override (parent)', function () { // setup @@ -389,7 +374,12 @@ suite('FileService', () => { const textResourceConfigurationService = new TestTextResourceConfigurationService(configurationService); + const fileService = new FileService2(new NullLogService()); + fileService.registerProvider(Schemas.file, new DiskFileSystemProvider(new NullLogService())); + + const _service = new FileService( + fileService, new TestContextService(new Workspace(_testDir, toWorkspaceFolders([{ path: _testDir }]))), TestEnvironmentService, textResourceConfigurationService, @@ -434,7 +424,11 @@ suite('FileService', () => { const textResourceConfigurationService = new TestTextResourceConfigurationService(configurationService); + const fileService = new FileService2(new NullLogService()); + fileService.registerProvider(Schemas.file, new DiskFileSystemProvider(new NullLogService())); + const _service = new FileService( + fileService, new TestContextService(new Workspace(_testDir, toWorkspaceFolders([{ path: _testDir }]))), TestEnvironmentService, textResourceConfigurationService, @@ -468,7 +462,11 @@ suite('FileService', () => { const _sourceDir = getPathFromAmdModule(require, './fixtures/service'); const resource = uri.file(path.join(testDir, 'index.html')); + const fileService = new FileService2(new NullLogService()); + fileService.registerProvider(Schemas.file, new DiskFileSystemProvider(new NullLogService())); + const _service = new FileService( + fileService, new TestContextService(new Workspace(_testDir, toWorkspaceFolders([{ path: _testDir }]))), TestEnvironmentService, new TestTextResourceConfigurationService(), diff --git a/src/vs/workbench/services/files/test/electron-browser/resolver.test.ts b/src/vs/workbench/services/files/test/electron-browser/resolver.test.ts deleted file mode 100644 index 39e78467397..00000000000 --- a/src/vs/workbench/services/files/test/electron-browser/resolver.test.ts +++ /dev/null @@ -1,179 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import * as fs from 'fs'; -import * as path from 'vs/base/common/path'; -import * as assert from 'assert'; - -import { StatResolver } from 'vs/workbench/services/files/node/fileService'; -import { URI as uri } from 'vs/base/common/uri'; -import { isLinux } from 'vs/base/common/platform'; -import * as utils from 'vs/workbench/services/files/test/electron-browser/utils'; -import { getPathFromAmdModule } from 'vs/base/common/amd'; - -function create(relativePath: string): StatResolver { - let basePath = getPathFromAmdModule(require, './fixtures/resolver'); - let absolutePath = relativePath ? path.join(basePath, relativePath) : basePath; - let fsStat = fs.statSync(absolutePath); - - return new StatResolver(uri.file(absolutePath), fsStat.isSymbolicLink(), fsStat.isDirectory(), fsStat.mtime.getTime(), fsStat.size, undefined); -} - -function toResource(relativePath: string): uri { - let basePath = getPathFromAmdModule(require, './fixtures/resolver'); - let absolutePath = relativePath ? path.join(basePath, relativePath) : basePath; - - return uri.file(absolutePath); -} - -suite('Stat Resolver', () => { - - test('resolve file', function () { - let resolver = create('/index.html'); - return resolver.resolve(undefined).then(result => { - assert.ok(!result.isDirectory); - assert.equal(result.name, 'index.html'); - assert.ok(!!result.etag); - - resolver = create('examples'); - return resolver.resolve(undefined).then(result => { - assert.ok(result.isDirectory); - }); - }); - }); - - test('resolve directory', function () { - let testsElements = ['examples', 'other', 'index.html', 'site.css']; - - let resolver = create('/'); - - return resolver.resolve(undefined).then(result => { - assert.ok(result); - assert.ok(result.children); - assert.ok(result.children!.length > 0); - assert.ok(result!.isDirectory); - assert.equal(result.children!.length, testsElements.length); - - assert.ok(result.children!.every((entry) => { - return testsElements.some((name) => { - return path.basename(entry.resource.fsPath) === name; - }); - })); - - result.children!.forEach((value) => { - assert.ok(path.basename(value.resource.fsPath)); - if (['examples', 'other'].indexOf(path.basename(value.resource.fsPath)) >= 0) { - assert.ok(value.isDirectory); - } else if (path.basename(value.resource.fsPath) === 'index.html') { - assert.ok(!value.isDirectory); - assert.ok(!value.children); - } else if (path.basename(value.resource.fsPath) === 'site.css') { - assert.ok(!value.isDirectory); - assert.ok(!value.children); - } else { - assert.ok(!'Unexpected value ' + path.basename(value.resource.fsPath)); - } - }); - }); - }); - - test('resolve directory - resolveTo single directory', function () { - let resolver = create('/'); - - return resolver.resolve({ resolveTo: [toResource('other/deep')] }).then(result => { - assert.ok(result); - assert.ok(result.children); - assert.ok(result.children!.length > 0); - assert.ok(result.isDirectory); - - const children = result.children!; - assert.equal(children.length, 4); - - const other = utils.getByName(result, 'other'); - assert.ok(other); - assert.ok(other!.children!.length > 0); - - const deep = utils.getByName(other!, 'deep'); - assert.ok(deep); - assert.ok(deep!.children!.length > 0); - assert.equal(deep!.children!.length, 4); - }); - }); - - test('resolve directory - resolveTo single directory - mixed casing', function () { - let resolver = create('/'); - - return resolver.resolve({ resolveTo: [toResource('other/Deep')] }).then(result => { - assert.ok(result); - assert.ok(result.children); - assert.ok(result.children!.length > 0); - assert.ok(result.isDirectory); - - const children = result.children; - assert.equal(children!.length, 4); - - const other = utils.getByName(result, 'other'); - assert.ok(other); - assert.ok(other!.children!.length > 0); - - const deep = utils.getByName(other!, 'deep'); - if (isLinux) { // Linux has case sensitive file system - assert.ok(deep); - assert.ok(!deep!.children); // not resolved because we got instructed to resolve other/Deep with capital D - } else { - assert.ok(deep); - assert.ok(deep!.children!.length > 0); - assert.equal(deep!.children!.length, 4); - } - }); - }); - - test('resolve directory - resolveTo multiple directories', function () { - let resolver = create('/'); - - return resolver.resolve({ resolveTo: [toResource('other/deep'), toResource('examples')] }).then(result => { - assert.ok(result); - assert.ok(result.children); - assert.ok(result.children!.length > 0); - assert.ok(result.isDirectory); - - const children = result.children!; - assert.equal(children.length, 4); - - const other = utils.getByName(result, 'other'); - assert.ok(other); - assert.ok(other!.children!.length > 0); - - const deep = utils.getByName(other!, 'deep'); - assert.ok(deep); - assert.ok(deep!.children!.length > 0); - assert.equal(deep!.children!.length, 4); - - const examples = utils.getByName(result, 'examples'); - assert.ok(examples); - assert.ok(examples!.children!.length > 0); - assert.equal(examples!.children!.length, 4); - }); - }); - - test('resolve directory - resolveSingleChildFolders', function () { - let resolver = create('/other'); - - return resolver.resolve({ resolveSingleChildDescendants: true }).then(result => { - assert.ok(result); - assert.ok(result.children); - assert.ok(result.children!.length > 0); - assert.ok(result.isDirectory); - - const children = result.children!; - assert.equal(children.length, 1); - - let deep = utils.getByName(result, 'deep'); - assert.ok(deep); - assert.ok(deep!.children!.length > 0); - assert.equal(deep!.children!.length, 4); - }); - }); -}); diff --git a/src/vs/workbench/services/files2/common/fileService2.ts b/src/vs/workbench/services/files2/common/fileService2.ts index b97fa6f2912..5eb599c6f04 100644 --- a/src/vs/workbench/services/files2/common/fileService2.ts +++ b/src/vs/workbench/services/files2/common/fileService2.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { Disposable, IDisposable, toDisposable, combinedDisposable, dispose } from 'vs/base/common/lifecycle'; -import { IFileService, IResolveFileOptions, IResourceEncodings, FileChangesEvent, FileOperationEvent, IFileSystemProviderRegistrationEvent, IFileSystemProvider, IFileStat, IResolveFileResult, IResolveContentOptions, IContent, IStreamContent, ITextSnapshot, IUpdateContentOptions, ICreateFileOptions, IFileSystemProviderActivationEvent, FileOperationError, FileOperationResult, FileOperation, FileSystemProviderCapabilities, FileType, toFileSystemProviderErrorCode, FileSystemProviderErrorCode, IStat, IFileStatWithMetadata, IResolveMetadataFileOptions, etag, hasReadWriteCapability, hasFileFolderCopyCapability, hasOpenReadWriteCloseCapability, toFileOperationResult, IFileSystemProviderWithOpenReadWriteCloseCapability, IFileSystemProviderWithFileReadWriteCapability, IResolveFileResultWithMetadata, IWatchOptions } from 'vs/platform/files/common/files'; +import { IFileService, IResolveFileOptions, IResourceEncodings, FileChangesEvent, FileOperationEvent, IFileSystemProviderRegistrationEvent, IFileSystemProvider, IFileStat, IResolveFileResult, IResolveContentOptions, IContent, IStreamContent, ITextSnapshot, IUpdateContentOptions, ICreateFileOptions, IFileSystemProviderActivationEvent, FileOperationError, FileOperationResult, FileOperation, FileSystemProviderCapabilities, FileType, toFileSystemProviderErrorCode, FileSystemProviderErrorCode, IStat, IFileStatWithMetadata, IResolveMetadataFileOptions, etag, hasReadWriteCapability, hasFileFolderCopyCapability, hasOpenReadWriteCloseCapability, toFileOperationResult, IFileSystemProviderWithOpenReadWriteCloseCapability, IFileSystemProviderWithFileReadWriteCapability, IResolveFileResultWithMetadata, IWatchOptions, ILegacyFileService } from 'vs/platform/files/common/files'; import { URI } from 'vs/base/common/uri'; import { Event, Emitter } from 'vs/base/common/event'; import { ServiceIdentifier } from 'vs/platform/instantiation/common/instantiation'; @@ -19,11 +19,11 @@ export class FileService2 extends Disposable implements IFileService { //#region TODO@Ben HACKS - private _legacy: IFileService | null; - private joinOnLegacy: Promise; - private joinOnImplResolve: (service: IFileService) => void; + private _legacy: ILegacyFileService | null; + private joinOnLegacy: Promise; + private joinOnImplResolve: (service: ILegacyFileService) => void; - setLegacyService(legacy: IFileService): void { + setLegacyService(legacy: ILegacyFileService): void { this._legacy = this._register(legacy); this._register(legacy.onFileChanges(e => this._onFileChanges.fire(e))); diff --git a/src/vs/workbench/services/files2/node/diskFileSystemProvider.ts b/src/vs/workbench/services/files2/node/diskFileSystemProvider.ts index 0b78b17a14d..9de08abfca3 100644 --- a/src/vs/workbench/services/files2/node/diskFileSystemProvider.ts +++ b/src/vs/workbench/services/files2/node/diskFileSystemProvider.ts @@ -312,13 +312,13 @@ export class DiskFileSystemProvider extends Disposable implements IFileSystemPro watch(resource: URI, opts: IWatchOptions): IDisposable { if (opts.recursive) { - return this.watchRecursive(resource, opts); + return this.watchRecursive(resource, opts.excludes); } return this.watchNonRecursive(resource); // TODO@ben ideally the same watcher can be used in both cases } - private watchRecursive(resource: URI, opts: IWatchOptions): IDisposable { + private watchRecursive(resource: URI, excludes: string[]): IDisposable { throw new Error('Method not implemented.'); } diff --git a/src/vs/workbench/services/keybinding/test/electron-browser/keybindingEditing.test.ts b/src/vs/workbench/services/keybinding/test/electron-browser/keybindingEditing.test.ts index 2c282a27103..bc6a79fa3ac 100644 --- a/src/vs/workbench/services/keybinding/test/electron-browser/keybindingEditing.test.ts +++ b/src/vs/workbench/services/keybinding/test/electron-browser/keybindingEditing.test.ts @@ -30,7 +30,7 @@ import { ResolvedKeybindingItem } from 'vs/platform/keybinding/common/resolvedKe import { USLayoutResolvedKeybinding } from 'vs/platform/keybinding/common/usLayoutResolvedKeybinding'; import { MockContextKeyService } from 'vs/platform/keybinding/test/common/mockKeybindingService'; import { ILifecycleService } from 'vs/platform/lifecycle/common/lifecycle'; -import { ILogService } from 'vs/platform/log/common/log'; +import { ILogService, NullLogService } from 'vs/platform/log/common/log'; import { TestNotificationService } from 'vs/platform/notification/test/common/testNotificationService'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { NullTelemetryService } from 'vs/platform/telemetry/common/telemetryUtils'; @@ -44,6 +44,9 @@ import { ITextFileService } from 'vs/workbench/services/textfile/common/textfile import { TextModelResolverService } from 'vs/workbench/services/textmodelResolver/common/textModelResolverService'; import { IUntitledEditorService, UntitledEditorService } from 'vs/workbench/services/untitled/common/untitledEditorService'; import { TestBackupFileService, TestContextService, TestEditorGroupsService, TestEditorService, TestEnvironmentService, TestLifecycleService, TestLogService, TestStorageService, TestTextFileService, TestTextResourceConfigurationService, TestTextResourcePropertiesService } from 'vs/workbench/test/workbenchTestServices'; +import { FileService2 } from 'vs/workbench/services/files2/common/fileService2'; +import { Schemas } from 'vs/base/common/network'; +import { DiskFileSystemProvider } from 'vs/workbench/services/files2/node/diskFileSystemProvider'; interface Modifiers { metaKey?: boolean; @@ -81,7 +84,10 @@ suite('KeybindingsEditing', () => { instantiationService.stub(ILogService, new TestLogService()); instantiationService.stub(ITextResourcePropertiesService, new TestTextResourcePropertiesService(instantiationService.get(IConfigurationService))); instantiationService.stub(IModelService, instantiationService.createInstance(ModelServiceImpl)); - instantiationService.stub(IFileService, new FileService( + const fileService = new FileService2(new NullLogService()); + fileService.registerProvider(Schemas.file, new DiskFileSystemProvider(new NullLogService())); + fileService.setLegacyService(new FileService( + fileService, new TestContextService(new Workspace(testDir, toWorkspaceFolders([{ path: testDir }]))), TestEnvironmentService, new TestTextResourceConfigurationService(), @@ -91,6 +97,7 @@ suite('KeybindingsEditing', () => { new TestNotificationService(), { disableWatcher: true }) ); + instantiationService.stub(IFileService, fileService); instantiationService.stub(IUntitledEditorService, instantiationService.createInstance(UntitledEditorService)); instantiationService.stub(ITextFileService, instantiationService.createInstance(TestTextFileService)); instantiationService.stub(ITextModelService, instantiationService.createInstance(TextModelResolverService)); @@ -106,7 +113,7 @@ suite('KeybindingsEditing', () => { } teardown(() => { - return new Promise((c, e) => { + return new Promise((c) => { if (testDir) { rimraf(testDir, RimRafMode.MOVE).then(c, c); } else {