From 9b6ae0ca7bf783ebf47255040836e87b66591ba0 Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Wed, 15 Jan 2020 10:39:45 +0100 Subject: [PATCH] textfiles - reduce service methods that are not needed anymore --- .../electron-browser/backupOnShutdown.test.ts | 12 +- .../files/browser/views/explorerViewer.ts | 8 +- .../contrib/search/browser/replaceService.ts | 109 +++++++------ .../common/configurationEditingService.ts | 2 +- .../textfile/browser/textFileService.ts | 147 +++++------------- .../services/textfile/common/textfiles.ts | 27 +--- .../textfile/test/textFileEditorModel.test.ts | 4 +- .../textfile/test/textFileService.test.ts | 86 ++++------ .../workingCopy/common/workingCopyService.ts | 8 + .../test/common/workingCopyService.test.ts | 6 + .../workbench/test/workbenchTestServices.ts | 4 + 11 files changed, 162 insertions(+), 251 deletions(-) diff --git a/src/vs/workbench/contrib/backup/test/electron-browser/backupOnShutdown.test.ts b/src/vs/workbench/contrib/backup/test/electron-browser/backupOnShutdown.test.ts index db140201650..e8d36fc4af6 100644 --- a/src/vs/workbench/contrib/backup/test/electron-browser/backupOnShutdown.test.ts +++ b/src/vs/workbench/contrib/backup/test/electron-browser/backupOnShutdown.test.ts @@ -22,6 +22,7 @@ import { IFilesConfigurationService } from 'vs/workbench/services/filesConfigura import { IFileDialogService, ConfirmResult } from 'vs/platform/dialogs/common/dialogs'; import { BackupOnShutdown } from 'vs/workbench/contrib/backup/electron-browser/backupOnShutdown'; import { IBackupFileService } from 'vs/workbench/services/backup/common/backup'; +import { IWorkingCopyService } from 'vs/workbench/services/workingCopy/common/workingCopyService'; class ServiceAccessor { constructor( @@ -34,7 +35,8 @@ class ServiceAccessor { @IFileService public fileService: TestFileService, @IElectronService public electronService: TestElectronService, @IFileDialogService public fileDialogService: TestFileDialogService, - @IBackupFileService public backupFileService: TestBackupFileService + @IBackupFileService public backupFileService: TestBackupFileService, + @IWorkingCopyService public workingCopyService: IWorkingCopyService ) { } } @@ -94,7 +96,7 @@ suite('BackupOnShutdown', () => { await model.load(); model.textEditorModel!.setValue('foo'); - assert.equal(accessor.textFileService.getDirty().length, 1); + assert.equal(accessor.workingCopyService.dirtyCount, 1); const event = new BeforeShutdownEventImpl(); accessor.lifecycleService.fireWillShutdown(event); @@ -110,7 +112,7 @@ suite('BackupOnShutdown', () => { await model.load(); model.textEditorModel!.setValue('foo'); - assert.equal(accessor.textFileService.getDirty().length, 1); + assert.equal(accessor.workingCopyService.dirtyCount, 1); const event = new BeforeShutdownEventImpl(); accessor.lifecycleService.fireWillShutdown(event); @@ -135,7 +137,7 @@ suite('BackupOnShutdown', () => { await model.load(); model.textEditorModel!.setValue('foo'); - assert.equal(accessor.textFileService.getDirty().length, 1); + assert.equal(accessor.workingCopyService.dirtyCount, 1); const event = new BeforeShutdownEventImpl(); accessor.lifecycleService.fireWillShutdown(event); @@ -269,7 +271,7 @@ suite('BackupOnShutdown', () => { await model.load(); model.textEditorModel!.setValue('foo'); - assert.equal(accessor.textFileService.getDirty().length, 1); + assert.equal(accessor.workingCopyService.dirtyCount, 1); const event = new BeforeShutdownEventImpl(); event.reason = shutdownReason; diff --git a/src/vs/workbench/contrib/files/browser/views/explorerViewer.ts b/src/vs/workbench/contrib/files/browser/views/explorerViewer.ts index c98ad1df6a6..3553eb9385c 100644 --- a/src/vs/workbench/contrib/files/browser/views/explorerViewer.ts +++ b/src/vs/workbench/contrib/files/browser/views/explorerViewer.ts @@ -55,6 +55,7 @@ import { ILabelService } from 'vs/platform/label/common/label'; import { isNumber } from 'vs/base/common/types'; import { domEvent } from 'vs/base/browser/event'; import { IEditableData } from 'vs/workbench/common/views'; +import { IWorkingCopyService } from 'vs/workbench/services/workingCopy/common/workingCopyService'; export class ExplorerDelegate implements IListVirtualDelegate { @@ -642,7 +643,8 @@ export class FileDragAndDrop implements ITreeDragAndDrop { @IInstantiationService private instantiationService: IInstantiationService, @ITextFileService private textFileService: ITextFileService, @IHostService private hostService: IHostService, - @IWorkspaceEditingService private workspaceEditingService: IWorkspaceEditingService + @IWorkspaceEditingService private workspaceEditingService: IWorkspaceEditingService, + @IWorkingCopyService private workingCopyService: IWorkingCopyService ) { this.toDispose = []; @@ -946,8 +948,8 @@ export class FileDragAndDrop implements ITreeDragAndDrop { // if the target exists and is dirty, make sure to revert it. otherwise the dirty contents // of the target file would replace the contents of the added file. since we already // confirmed the overwrite before, this is OK. - if (this.textFileService.isDirty(targetFile)) { - await this.textFileService.revertAll([targetFile], { soft: true }); + if (this.workingCopyService.isDirty(targetFile)) { + await Promise.all(this.workingCopyService.getWorkingCopies(targetFile).map(workingCopy => workingCopy.revert({ soft: true }))); } const copyTarget = joinPath(target.resource, basename(sourceFile)); diff --git a/src/vs/workbench/contrib/search/browser/replaceService.ts b/src/vs/workbench/contrib/search/browser/replaceService.ts index 4f962476861..8cab5f81de8 100644 --- a/src/vs/workbench/contrib/search/browser/replaceService.ts +++ b/src/vs/workbench/contrib/search/browser/replaceService.ts @@ -4,7 +4,6 @@ *--------------------------------------------------------------------------------------------*/ import * as nls from 'vs/nls'; -import * as errors from 'vs/base/common/errors'; import { URI } from 'vs/base/common/uri'; import * as network from 'vs/base/common/network'; import { Disposable } from 'vs/base/common/lifecycle'; @@ -65,21 +64,19 @@ class ReplacePreviewModel extends Disposable { super(); } - resolve(replacePreviewUri: URI): Promise { + async resolve(replacePreviewUri: URI): Promise { const fileResource = toFileResource(replacePreviewUri); const fileMatch = this.searchWorkbenchService.searchModel.searchResult.matches().filter(match => match.resource.toString() === fileResource.toString())[0]; - return this.textModelResolverService.createModelReference(fileResource).then(ref => { - ref = this._register(ref); - const sourceModel = ref.object.textEditorModel; - const sourceModelModeId = sourceModel.getLanguageIdentifier().language; - const replacePreviewModel = this.modelService.createModel(createTextBufferFactoryFromSnapshot(sourceModel.createSnapshot()), this.modeService.create(sourceModelModeId), replacePreviewUri); - this._register(fileMatch.onChange(modelChange => this.update(sourceModel, replacePreviewModel, fileMatch, modelChange))); - this._register(this.searchWorkbenchService.searchModel.onReplaceTermChanged(() => this.update(sourceModel, replacePreviewModel, fileMatch))); - this._register(fileMatch.onDispose(() => replacePreviewModel.dispose())); // TODO@Sandeep we should not dispose a model directly but rather the reference (depends on https://github.com/Microsoft/vscode/issues/17073) - this._register(replacePreviewModel.onWillDispose(() => this.dispose())); - this._register(sourceModel.onWillDispose(() => this.dispose())); - return replacePreviewModel; - }); + const ref = this._register(await this.textModelResolverService.createModelReference(fileResource)); + const sourceModel = ref.object.textEditorModel; + const sourceModelModeId = sourceModel.getLanguageIdentifier().language; + const replacePreviewModel = this.modelService.createModel(createTextBufferFactoryFromSnapshot(sourceModel.createSnapshot()), this.modeService.create(sourceModelModeId), replacePreviewUri); + this._register(fileMatch.onChange(modelChange => this.update(sourceModel, replacePreviewModel, fileMatch, modelChange))); + this._register(this.searchWorkbenchService.searchModel.onReplaceTermChanged(() => this.update(sourceModel, replacePreviewModel, fileMatch))); + this._register(fileMatch.onDispose(() => replacePreviewModel.dispose())); // TODO@Sandeep we should not dispose a model directly but rather the reference (depends on https://github.com/Microsoft/vscode/issues/17073) + this._register(replacePreviewModel.onWillDispose(() => this.dispose())); + this._register(sourceModel.onWillDispose(() => this.dispose())); + return replacePreviewModel; } private update(sourceModel: ITextModel, replacePreviewModel: ITextModel, fileMatch: FileMatch, override: boolean = false): void { @@ -103,15 +100,24 @@ export class ReplaceService implements IReplaceService { replace(match: Match): Promise; replace(files: FileMatch[], progress?: IProgress): Promise; replace(match: FileMatchOrMatch, progress?: IProgress, resource?: URI): Promise; - replace(arg: any, progress: IProgress | undefined = undefined, resource: URI | null = null): Promise { + async replace(arg: any, progress: IProgress | undefined = undefined, resource: URI | null = null): Promise { const edits: ResourceTextEdit[] = this.createEdits(arg, resource); - return this.bulkEditorService.apply({ edits }, { progress }).then(() => this.textFileService.saveAll(edits.map(e => e.resource))); + await this.bulkEditorService.apply({ edits }, { progress }); + + return Promise.all(edits.map(e => { + const model = this.textFileService.models.get(e.resource); + if (model) { + return model.save(); + } + + return Promise.resolve(undefined); + })); } - openReplacePreview(element: FileMatchOrMatch, preserveFocus?: boolean, sideBySide?: boolean, pinned?: boolean): Promise { + async openReplacePreview(element: FileMatchOrMatch, preserveFocus?: boolean, sideBySide?: boolean, pinned?: boolean): Promise { const fileMatch = element instanceof Match ? element.parent() : element; - return this.editorService.openEditor({ + const editor = await this.editorService.openEditor({ leftResource: fileMatch.resource, rightResource: toReplaceResource(fileMatch.resource), label: nls.localize('fileReplaceChanges', "{0} ↔ {1} (Replace Preview)", fileMatch.name(), fileMatch.name()), @@ -120,46 +126,39 @@ export class ReplaceService implements IReplaceService { pinned, revealIfVisible: true } - }).then(editor => { - const input = editor?.input; - const disposable = fileMatch.onDispose(() => { - if (input) { - input.dispose(); - } - disposable.dispose(); - }); - this.updateReplacePreview(fileMatch).then(() => { - if (editor) { - const editorControl = editor.getControl(); - if (element instanceof Match && editorControl) { - editorControl.revealLineInCenter(element.range().startLineNumber, ScrollType.Immediate); - } - } - }); - }, errors.onUnexpectedError); + }); + const input = editor?.input; + const disposable = fileMatch.onDispose(() => { + if (input) { + input.dispose(); + } + disposable.dispose(); + }); + await this.updateReplacePreview(fileMatch); + if (editor) { + const editorControl = editor.getControl(); + if (element instanceof Match && editorControl) { + editorControl.revealLineInCenter(element.range().startLineNumber, ScrollType.Immediate); + } + } } - updateReplacePreview(fileMatch: FileMatch, override: boolean = false): Promise { + async updateReplacePreview(fileMatch: FileMatch, override: boolean = false): Promise { const replacePreviewUri = toReplaceResource(fileMatch.resource); - return Promise.all([this.textModelResolverService.createModelReference(fileMatch.resource), this.textModelResolverService.createModelReference(replacePreviewUri)]) - .then(([sourceModelRef, replaceModelRef]) => { - const sourceModel = sourceModelRef.object.textEditorModel; - const replaceModel = replaceModelRef.object.textEditorModel; - const returnValue = Promise.resolve(null); - // If model is disposed do not update - if (sourceModel && replaceModel) { - if (override) { - replaceModel.setValue(sourceModel.getValue()); - } else { - replaceModel.undo(); - } - this.applyEditsToPreview(fileMatch, replaceModel); - } - return returnValue.then(() => { - sourceModelRef.dispose(); - replaceModelRef.dispose(); - }); - }); + const [sourceModelRef, replaceModelRef] = await Promise.all([this.textModelResolverService.createModelReference(fileMatch.resource), this.textModelResolverService.createModelReference(replacePreviewUri)]); + const sourceModel = sourceModelRef.object.textEditorModel; + const replaceModel = replaceModelRef.object.textEditorModel; + // If model is disposed do not update + if (sourceModel && replaceModel) { + if (override) { + replaceModel.setValue(sourceModel.getValue()); + } else { + replaceModel.undo(); + } + this.applyEditsToPreview(fileMatch, replaceModel); + } + sourceModelRef.dispose(); + replaceModelRef.dispose(); } private applyEditsToPreview(fileMatch: FileMatch, replaceModel: ITextModel): void { diff --git a/src/vs/workbench/services/configuration/common/configurationEditingService.ts b/src/vs/workbench/services/configuration/common/configurationEditingService.ts index e313353286b..b9b233360a7 100644 --- a/src/vs/workbench/services/configuration/common/configurationEditingService.ts +++ b/src/vs/workbench/services/configuration/common/configurationEditingService.ts @@ -487,7 +487,7 @@ export class ConfigurationEditingService { } // Target cannot be dirty if not writing into buffer - if (checkDirty && this.textFileService.isDirty(operation.resource)) { + if (checkDirty && operation.resource && this.textFileService.isDirty(operation.resource)) { return this.reject(ConfigurationEditingErrorCode.ERROR_CONFIGURATION_FILE_DIRTY, target, operation); } return reference; diff --git a/src/vs/workbench/services/textfile/browser/textFileService.ts b/src/vs/workbench/services/textfile/browser/textFileService.ts index 6c32866863e..d5bbe8bc182 100644 --- a/src/vs/workbench/services/textfile/browser/textFileService.ts +++ b/src/vs/workbench/services/textfile/browser/textFileService.ts @@ -177,7 +177,7 @@ export abstract class AbstractTextFileService extends Disposable implements ITex await this._onWillRunOperation.fireAsync({ operation: FileOperation.DELETE, target: resource }, CancellationToken.None); const dirtyFiles = this.getDirty().filter(dirty => isEqualOrParent(dirty, resource)); - await this.revertAll(dirtyFiles, { soft: true }); + await this.doRevertAll(dirtyFiles, { soft: true }); await this.fileService.del(resource, options); @@ -243,7 +243,7 @@ export abstract class AbstractTextFileService extends Disposable implements ITex // in order to move and copy, we need to soft revert all dirty models, // both from the source as well as the target if any const dirtyModels = [...sourceModels, ...conflictingModels].filter(model => model.isDirty()); - await this.revertAll(dirtyModels.map(dirtyModel => dirtyModel.resource), { soft: true }); + await this.doRevertAll(dirtyModels.map(dirtyModel => dirtyModel.resource), { soft: true }); // now we can rename the source to target via file operation let stat: IFileStatWithMetadata; @@ -290,85 +290,44 @@ export abstract class AbstractTextFileService extends Disposable implements ITex //#region save async save(resource: URI, options?: ITextFileSaveOptions): Promise { - return !(await this.saveAll([resource], options)).results.some(result => result.error); - } - - saveAll(includeUntitled?: boolean, options?: ITextFileSaveOptions): Promise; - saveAll(resources: URI[], options?: ITextFileSaveOptions): Promise; - saveAll(arg1?: boolean | URI[], options?: ITextFileSaveOptions): Promise { - - // Extract the resources to save - let resourcesToSave: URI[] = []; - if (Array.isArray(arg1)) { - // if specific resources are given, we consider even - // non-dirty ones if options.force is provided - if (options?.force) { - resourcesToSave = arg1; - } else { - resourcesToSave = this.getDirty(arg1); - } - } else { - // if no resources are given, we only consider dirty - // resources even if options.force is provided - resourcesToSave = this.getDirty(); - } - - // split up between files and untitled - const filesToSave: URI[] = []; - const untitledToSave: URI[] = []; - resourcesToSave.forEach(resourceToSave => { - if ((Array.isArray(arg1) || arg1 === true /* includeUntitled */) && resourceToSave.scheme === Schemas.untitled) { - untitledToSave.push(resourceToSave); - } else if (this.fileService.canHandleResource(resourceToSave)) { - filesToSave.push(resourceToSave); - } - }); - - return this.doSaveAll(filesToSave, untitledToSave, options); - } - - private async doSaveAll(fileResources: URI[], untitledResources: URI[], options?: ITextFileSaveOptions): Promise { - - // Handle files first that can just be saved - const result = await this.doSaveAllFiles(fileResources, options); - // Preflight for untitled to handle cancellation from the dialog - const targetsForUntitled: URI[] = []; - for (const untitled of untitledResources) { - if (this.untitledTextEditorService.exists(untitled)) { - let targetUri: URI; + // Untitled + if (resource.scheme === Schemas.untitled) { + if (this.untitledTextEditorService.exists(resource)) { + let targetUri: URI | undefined; // Untitled with associated file path don't need to prompt - if (this.untitledTextEditorService.hasAssociatedFilePath(untitled)) { - targetUri = toLocalResource(untitled, this.environmentService.configuration.remoteAuthority); + if (this.untitledTextEditorService.hasAssociatedFilePath(resource)) { + targetUri = toLocalResource(resource, this.environmentService.configuration.remoteAuthority); } // Otherwise ask user else { - const targetPath = await this.promptForPath(untitled, this.suggestFileName(untitled)); - if (!targetPath) { - return { results: [...fileResources, ...untitledResources].map(r => ({ source: r })) }; - } - - targetUri = targetPath; + targetUri = await this.promptForPath(resource, this.suggestFileName(resource)); } - targetsForUntitled.push(targetUri); + // Save as if target provided + if (targetUri) { + await this.saveAs(resource, targetUri, options); + + return true; + } } } - // Handle untitled - await Promise.all(targetsForUntitled.map(async (target, index) => { - const uri = await this.saveAs(untitledResources[index], target); + // File + else { + const model = this.models.get(resource); + if (model) { - result.results.push({ - source: untitledResources[index], - target: uri, - error: !uri // the operation was canceled or failed, so mark as error - }); - })); + // Save with options + await model.save(options); - return result; + return !model.isDirty(); + } + } + + return false; } protected async promptForPath(resource: URI, defaultUri: URI, availableFileSystems?: readonly string[]): Promise { @@ -432,32 +391,6 @@ export abstract class AbstractTextFileService extends Disposable implements ITex return options; } - private async doSaveAllFiles(resources?: URI[], options: ITextFileSaveOptions = Object.create(null)): Promise { - const fileModelsToSave = this.getFileModels(resources); - - const mapResourceToResult = new ResourceMap(); - for (const fileModelToSave of fileModelsToSave) { - mapResourceToResult.set(fileModelToSave.resource, { source: fileModelToSave.resource }); - } - - // Save all in parallel - await Promise.all(fileModelsToSave.map(async model => { - - // Save with options - await model.save(options); - - // If model is still dirty, mark the resulting operation as error - if (model.isDirty()) { - const result = mapResourceToResult.get(model.resource); - if (result) { - result.error = true; - } - } - })); - - return { results: mapResourceToResult.values() }; - } - private getFileModels(arg1?: URI | URI[]): ITextFileEditorModel[] { if (Array.isArray(arg1)) { const models: ITextFileEditorModel[] = []; @@ -682,10 +615,10 @@ export abstract class AbstractTextFileService extends Disposable implements ITex //#region revert async revert(resource: URI, options?: IRevertOptions): Promise { - return !(await this.revertAll([resource], options)).results.some(result => result.error); + return !(await this.doRevertAll([resource], options)).results.some(result => result.error); } - async revertAll(resources?: URI[], options?: IRevertOptions): Promise { + private async doRevertAll(resources?: URI[], options?: IRevertOptions): Promise { // Revert files first const revertOperationResult = await this.doRevertAllFiles(resources, options); @@ -739,18 +672,7 @@ export abstract class AbstractTextFileService extends Disposable implements ITex //#region dirty - getDirty(resources?: URI[]): URI[] { - - // Collect files - const dirty = this.getDirtyFileModels(resources).map(dirtyFileModel => dirtyFileModel.resource); - - // Add untitled ones - dirty.push(...this.untitledTextEditorService.getDirty(resources)); - - return dirty; - } - - isDirty(resource?: URI): boolean { + isDirty(resource: URI): boolean { // Check for dirty file if (this.models.getAll(resource).some(model => model.isDirty())) { @@ -761,5 +683,16 @@ export abstract class AbstractTextFileService extends Disposable implements ITex return this.untitledTextEditorService.getDirty().some(dirty => !resource || dirty.toString() === resource.toString()); } + protected getDirty(resources?: URI[]): URI[] { + + // Collect files + const dirty = this.getDirtyFileModels(resources).map(dirtyFileModel => dirtyFileModel.resource); + + // Add untitled ones + dirty.push(...this.untitledTextEditorService.getDirty(resources)); + + return dirty; + } + //#endregion } diff --git a/src/vs/workbench/services/textfile/common/textfiles.ts b/src/vs/workbench/services/textfile/common/textfiles.ts index 89ce41bdbaa..f505df3e772 100644 --- a/src/vs/workbench/services/textfile/common/textfiles.ts +++ b/src/vs/workbench/services/textfile/common/textfiles.ts @@ -45,18 +45,9 @@ export interface ITextFileService extends IDisposable { /** * A resource is dirty if it has unsaved changes or is an untitled file not yet saved. * - * @param resource the resource to check for being dirty. If it is not specified, will check for - * all dirty resources. + * @param resource the resource to check for being dirty */ - isDirty(resource?: URI): boolean; - - /** - * Returns all resources that are currently dirty matching the provided resources or all dirty resources. - * - * @param resources the resources to check for being dirty. If it is not specified, will check for - * all dirty resources. - */ - getDirty(resources?: URI[]): URI[]; + isDirty(resource: URI): boolean; /** * Saves the resource. @@ -77,15 +68,6 @@ export interface ITextFileService extends IDisposable { */ saveAs(resource: URI, targetResource?: URI, options?: ISaveOptions): Promise; - /** - * Saves the set of resources and returns a promise with the operation result. - * - * @param resources can be null to save all. - * @param includeUntitled to save all resources and optionally exclude untitled ones. - */ - saveAll(includeUntitled?: boolean, options?: ISaveOptions): Promise; - saveAll(resources: URI[], options?: ISaveOptions): Promise; - /** * Reverts the provided resource. * @@ -94,11 +76,6 @@ export interface ITextFileService extends IDisposable { */ revert(resource: URI, options?: IRevertOptions): Promise; - /** - * Reverts all the provided resources and returns a promise with the operation result. - */ - revertAll(resources?: URI[], options?: IRevertOptions): Promise; - /** * Create a file. If the file exists it will be overwritten with the contents if * the options enable to overwrite. diff --git a/src/vs/workbench/services/textfile/test/textFileEditorModel.test.ts b/src/vs/workbench/services/textfile/test/textFileEditorModel.test.ts index ccf2844c238..45c9082978b 100644 --- a/src/vs/workbench/services/textfile/test/textFileEditorModel.test.ts +++ b/src/vs/workbench/services/textfile/test/textFileEditorModel.test.ts @@ -386,7 +386,6 @@ suite('Files - TextFileEditorModel', () => { assert.ok(m1Mtime > 0); assert.ok(m2Mtime > 0); - assert.ok(accessor.textFileService.isDirty()); assert.ok(accessor.textFileService.isDirty(toResource.call(this, '/path/index_async2.txt'))); assert.ok(!accessor.textFileService.isDirty(toResource.call(this, '/path/index_async.txt'))); @@ -394,7 +393,8 @@ suite('Files - TextFileEditorModel', () => { assert.ok(accessor.textFileService.isDirty(toResource.call(this, '/path/index_async.txt'))); await timeout(10); - await accessor.textFileService.saveAll(); + await accessor.textFileService.save(toResource.call(this, '/path/index_async.txt')); + await accessor.textFileService.save(toResource.call(this, '/path/index_async2.txt')); assert.ok(!accessor.textFileService.isDirty(toResource.call(this, '/path/index_async.txt'))); assert.ok(!accessor.textFileService.isDirty(toResource.call(this, '/path/index_async2.txt'))); assert.ok(assertIsDefined(model1.getStat()).mtime > m1Mtime); diff --git a/src/vs/workbench/services/textfile/test/textFileService.test.ts b/src/vs/workbench/services/textfile/test/textFileService.test.ts index 4358e4dbc3f..e3d5a9b8e0a 100644 --- a/src/vs/workbench/services/textfile/test/textFileService.test.ts +++ b/src/vs/workbench/services/textfile/test/textFileService.test.ts @@ -60,42 +60,38 @@ suite('Files - TextFileService', () => { model = instantiationService.createInstance(TextFileEditorModel, toResource.call(this, '/path/file.txt'), 'utf8', undefined); (accessor.textFileService.models).add(model.resource, model); - const service = accessor.textFileService; - await model.load(); - assert.ok(!service.isDirty(model.resource)); + assert.ok(!accessor.textFileService.isDirty(model.resource)); model.textEditorModel!.setValue('foo'); - assert.ok(service.isDirty(model.resource)); - assert.equal(service.getDirty().length, 1); - assert.equal(service.getDirty([model.resource])[0].toString(), model.resource.toString()); + assert.ok(accessor.textFileService.isDirty(model.resource)); + assert.equal(accessor.textFileService.getDirty().length, 1); + assert.equal(accessor.textFileService.getDirty([model.resource])[0].toString(), model.resource.toString()); const untitled = accessor.untitledTextEditorService.createOrGet(); const untitledModel = await untitled.resolve(); - assert.ok(!service.isDirty(untitled.getResource())); - assert.equal(service.getDirty().length, 1); + assert.ok(!accessor.textFileService.isDirty(untitled.getResource())); + assert.equal(accessor.textFileService.getDirty().length, 1); untitledModel.textEditorModel!.setValue('changed'); - assert.ok(service.isDirty(untitled.getResource())); - assert.equal(service.getDirty().length, 2); - assert.equal(service.getDirty([untitled.getResource()])[0].toString(), untitled.getResource().toString()); + assert.ok(accessor.textFileService.isDirty(untitled.getResource())); + assert.equal(accessor.textFileService.getDirty().length, 2); + assert.equal(accessor.textFileService.getDirty([untitled.getResource()])[0].toString(), untitled.getResource().toString()); }); test('save - file', async function () { model = instantiationService.createInstance(TextFileEditorModel, toResource.call(this, '/path/file.txt'), 'utf8', undefined); (accessor.textFileService.models).add(model.resource, model); - const service = accessor.textFileService; - await model.load(); model.textEditorModel!.setValue('foo'); - assert.ok(service.isDirty(model.resource)); + assert.ok(accessor.textFileService.isDirty(model.resource)); - const res = await service.save(model.resource); + const res = await accessor.textFileService.save(model.resource); assert.ok(res); - assert.ok(!service.isDirty(model.resource)); + assert.ok(!accessor.textFileService.isDirty(model.resource)); }); test('save - UNC path', async function () { @@ -114,76 +110,62 @@ suite('Files - TextFileService', () => { await model.load(); model.textEditorModel!.setValue('foo'); - const res = await accessor.textFileService.saveAll(true); + const res = await accessor.textFileService.save(untitledUncUri); assert.ok(loadOrCreateStub.calledOnce); - assert.equal(res.results.length, 1); - assert.ok(!res.results[0].error); - assert.equal(res.results[0].target!.scheme, Schemas.file); - assert.equal(res.results[0].target!.authority, untitledUncUri.authority); - assert.equal(res.results[0].target!.path, untitledUncUri.path); + assert.ok(res); }); test('saveAll - file', async function () { model = instantiationService.createInstance(TextFileEditorModel, toResource.call(this, '/path/file.txt'), 'utf8', undefined); (accessor.textFileService.models).add(model.resource, model); - const service = accessor.textFileService; - await model.load(); model.textEditorModel!.setValue('foo'); - assert.ok(service.isDirty(model.resource)); + assert.ok(accessor.textFileService.isDirty(model.resource)); - const res = await service.saveAll([model.resource]); + const res = await accessor.textFileService.save(model.resource); assert.ok(res); - assert.ok(!service.isDirty(model.resource)); - assert.equal(res.results.length, 1); - assert.equal(res.results[0].source.toString(), model.resource.toString()); + assert.ok(!accessor.textFileService.isDirty(model.resource)); }); test('saveAs - file', async function () { model = instantiationService.createInstance(TextFileEditorModel, toResource.call(this, '/path/file.txt'), 'utf8', undefined); (accessor.textFileService.models).add(model.resource, model); - - const service = accessor.textFileService; - service.setPromptPath(model.resource); + accessor.textFileService.setPromptPath(model.resource); await model.load(); model.textEditorModel!.setValue('foo'); - assert.ok(service.isDirty(model.resource)); + assert.ok(accessor.textFileService.isDirty(model.resource)); - const res = await service.saveAs(model.resource); + const res = await accessor.textFileService.saveAs(model.resource); assert.equal(res!.toString(), model.resource.toString()); - assert.ok(!service.isDirty(model.resource)); + assert.ok(!accessor.textFileService.isDirty(model.resource)); }); test('revert - file', async function () { model = instantiationService.createInstance(TextFileEditorModel, toResource.call(this, '/path/file.txt'), 'utf8', undefined); (accessor.textFileService.models).add(model.resource, model); - - const service = accessor.textFileService; - service.setPromptPath(model.resource); + accessor.textFileService.setPromptPath(model.resource); await model.load(); model!.textEditorModel!.setValue('foo'); - assert.ok(service.isDirty(model.resource)); + assert.ok(accessor.textFileService.isDirty(model.resource)); - const res = await service.revert(model.resource); + const res = await accessor.textFileService.revert(model.resource); assert.ok(res); - assert.ok(!service.isDirty(model.resource)); + assert.ok(!accessor.textFileService.isDirty(model.resource)); }); test('delete - dirty file', async function () { model = instantiationService.createInstance(TextFileEditorModel, toResource.call(this, '/path/file.txt'), 'utf8', undefined); (accessor.textFileService.models).add(model.resource, model); - const service = accessor.textFileService; - await model.load(); model!.textEditorModel!.setValue('foo'); - assert.ok(service.isDirty(model.resource)); + assert.ok(accessor.textFileService.isDirty(model.resource)); - await service.delete(model.resource); - assert.ok(!service.isDirty(model.resource)); + await accessor.textFileService.delete(model.resource); + assert.ok(!accessor.textFileService.isDirty(model.resource)); }); test('move - dirty file', async function () { @@ -200,24 +182,22 @@ suite('Files - TextFileService', () => { (accessor.textFileService.models).add(sourceModel.resource, sourceModel); (accessor.textFileService.models).add(targetModel.resource, targetModel); - const service = accessor.textFileService; - await sourceModel.load(); sourceModel.textEditorModel!.setValue('foo'); - assert.ok(service.isDirty(sourceModel.resource)); + assert.ok(accessor.textFileService.isDirty(sourceModel.resource)); if (targetDirty) { await targetModel.load(); targetModel.textEditorModel!.setValue('bar'); - assert.ok(service.isDirty(targetModel.resource)); + assert.ok(accessor.textFileService.isDirty(targetModel.resource)); } - await service.move(sourceModel.resource, targetModel.resource, true); + await accessor.textFileService.move(sourceModel.resource, targetModel.resource, true); assert.equal(targetModel.textEditorModel!.getValue(), 'foo'); - assert.ok(!service.isDirty(sourceModel.resource)); - assert.ok(service.isDirty(targetModel.resource)); + assert.ok(!accessor.textFileService.isDirty(sourceModel.resource)); + assert.ok(accessor.textFileService.isDirty(targetModel.resource)); sourceModel.dispose(); targetModel.dispose(); diff --git a/src/vs/workbench/services/workingCopy/common/workingCopyService.ts b/src/vs/workbench/services/workingCopy/common/workingCopyService.ts index 3193a3dd81c..5d7a8a95456 100644 --- a/src/vs/workbench/services/workingCopy/common/workingCopyService.ts +++ b/src/vs/workbench/services/workingCopy/common/workingCopyService.ts @@ -94,6 +94,8 @@ export interface IWorkingCopyService { readonly workingCopies: IWorkingCopy[]; + getWorkingCopies(resource: URI): IWorkingCopy[]; + registerWorkingCopy(workingCopy: IWorkingCopy): IDisposable; //#endregion @@ -127,6 +129,12 @@ export class WorkingCopyService extends Disposable implements IWorkingCopyServic get workingCopies(): IWorkingCopy[] { return values(this._workingCopies); } private _workingCopies = new Set(); + getWorkingCopies(resource: URI): IWorkingCopy[] { + const workingCopies = this.mapResourceToWorkingCopy.get(resource.toString()); + + return workingCopies ? values(workingCopies) : []; + } + registerWorkingCopy(workingCopy: IWorkingCopy): IDisposable { const disposables = new DisposableStore(); diff --git a/src/vs/workbench/services/workingCopy/test/common/workingCopyService.test.ts b/src/vs/workbench/services/workingCopy/test/common/workingCopyService.test.ts index a6a5269f462..435a48241f5 100644 --- a/src/vs/workbench/services/workingCopy/test/common/workingCopyService.test.ts +++ b/src/vs/workbench/services/workingCopy/test/common/workingCopyService.test.ts @@ -109,6 +109,8 @@ suite('WorkingCopyService', () => { assert.equal(service.dirtyCount, 1); assert.equal(service.dirtyWorkingCopies.length, 1); assert.equal(service.dirtyWorkingCopies[0], copy1); + assert.equal(service.getWorkingCopies(copy1.resource).length, 1); + assert.equal(service.getWorkingCopies(copy1.resource)[0], copy1); assert.equal(service.isDirty(resource1), true); assert.equal(service.hasDirty, true); assert.equal(onDidChangeDirty.length, 1); @@ -176,6 +178,10 @@ suite('WorkingCopyService', () => { const copy2 = new TestWorkingCopy(resource); const unregister2 = service.registerWorkingCopy(copy2); + assert.equal(service.getWorkingCopies(copy1.resource).length, 2); + assert.equal(service.getWorkingCopies(copy1.resource)[0], copy1); + assert.equal(service.getWorkingCopies(copy1.resource)[1], copy2); + copy1.setDirty(true); assert.equal(service.dirtyCount, 1); diff --git a/src/vs/workbench/test/workbenchTestServices.ts b/src/vs/workbench/test/workbenchTestServices.ts index c048560e2d5..e437a2cc049 100644 --- a/src/vs/workbench/test/workbenchTestServices.ts +++ b/src/vs/workbench/test/workbenchTestServices.ts @@ -261,6 +261,10 @@ export class TestTextFileService extends NativeTextFileService { promptForPath(_resource: URI, _defaultPath: URI): Promise { return Promise.resolve(this.promptPath); } + + getDirty(resources?: URI[]): URI[] { + return super.getDirty(resources); + } } export interface ITestInstantiationService extends IInstantiationService { -- GitLab