diff --git a/src/vs/workbench/contrib/bulkEdit/browser/bulkFileEdits.ts b/src/vs/workbench/contrib/bulkEdit/browser/bulkFileEdits.ts index 04ea382ea8af..73a7234fd289 100644 --- a/src/vs/workbench/contrib/bulkEdit/browser/bulkFileEdits.ts +++ b/src/vs/workbench/contrib/bulkEdit/browser/bulkFileEdits.ts @@ -120,9 +120,9 @@ class CreateOperation implements IFileOperation { return new Noop(); // not overwriting, but ignoring, and the target file exists } if (this.options.folder) { - await this._workingCopyFileService.createFolder(this.newUri, { ...this.undoRedoInfo }, token); + await this._workingCopyFileService.createFolder([{ resource: this.newUri }], this.undoRedoInfo, token); } else { - await this._workingCopyFileService.create(this.newUri, this.contents, { overwrite: this.options.overwrite, ...this.undoRedoInfo }, token); + await this._workingCopyFileService.create([{ resource: this.newUri, contents: this.contents, overwrite: this.options.overwrite }], this.undoRedoInfo, token); } return this._instaService.createInstance(DeleteOperation, this.newUri, this.options, { isUndoing: true }, !this.options.folder && !this.contents); } diff --git a/src/vs/workbench/services/textfile/browser/textFileService.ts b/src/vs/workbench/services/textfile/browser/textFileService.ts index 13ddf1513c79..208a0cda90ba 100644 --- a/src/vs/workbench/services/textfile/browser/textFileService.ts +++ b/src/vs/workbench/services/textfile/browser/textFileService.ts @@ -151,7 +151,8 @@ export abstract class AbstractTextFileService extends Disposable implements ITex async create(resource: URI, value?: string | ITextSnapshot, options?: ICreateFileOptions): Promise { const readable = await this.getEncodedReadable(resource, value); - return this.workingCopyFileService.create(resource, readable, options); + const result = await this.workingCopyFileService.create([{ resource, contents: readable, overwrite: options?.overwrite }]); + return result[0]; } async write(resource: URI, value: string | ITextSnapshot, options?: IWriteTextFileOptions): Promise { diff --git a/src/vs/workbench/services/workingCopy/common/workingCopyFileService.ts b/src/vs/workbench/services/workingCopy/common/workingCopyFileService.ts index c5fb0ee11c88..1c3d2055b1ac 100644 --- a/src/vs/workbench/services/workingCopy/common/workingCopyFileService.ts +++ b/src/vs/workbench/services/workingCopy/common/workingCopyFileService.ts @@ -32,7 +32,15 @@ export interface SourceTargetPair { } export interface IFileOperationUndoRedoInfo { + + /** + * Id of the undo group that the file operation belongs to. + */ undoRedoGroupId?: number; + + /** + * Flag indicates if the operation is an undo. + */ isUndoing?: boolean } @@ -70,6 +78,15 @@ export interface IWorkingCopyFileOperationParticipant { ): Promise; } +export interface ICreateOperation { + resource: URI; + overwrite?: boolean; +} + +export interface ICreateFileOperation extends ICreateOperation { + contents?: VSBuffer | VSBufferReadable | VSBufferReadableStream, +} + export interface IDeleteOperation { resource: URI; useTrash?: boolean; @@ -147,7 +164,7 @@ export interface IWorkingCopyFileService { * Working copy owners can listen to the `onWillRunWorkingCopyFileOperation` and * `onDidRunWorkingCopyFileOperation` events to participate. */ - create(resource: URI, contents?: VSBuffer | VSBufferReadable | VSBufferReadableStream, options?: { overwrite?: boolean, undoRedoGroupId?: number, isUndoing?: boolean }, token?: CancellationToken): Promise; + create(operations: ICreateFileOperation[], undoInfo?: IFileOperationUndoRedoInfo, token?: CancellationToken): Promise; /** * Will create a folder and any parent folder that needs to be created. @@ -158,7 +175,7 @@ export interface IWorkingCopyFileService { * Note: events will only be emitted for the provided resource, but not any * parent folders that are being created as part of the operation. */ - createFolder(resource: URI, undoInfo?: IFileOperationUndoRedoInfo, token?: CancellationToken): Promise; + createFolder(operations: ICreateOperation[], undoInfo?: IFileOperationUndoRedoInfo, token?: CancellationToken): Promise; /** * Will move working copies matching the provided resources and corresponding children @@ -254,38 +271,38 @@ export class WorkingCopyFileService extends Disposable implements IWorkingCopyFi //#region File operations - create(resource: URI, contents?: VSBuffer | VSBufferReadable | VSBufferReadableStream, options?: { overwrite?: boolean, undoRedoGroupId?: number, isUndoing?: boolean }, token?: CancellationToken): Promise { - return this.doCreateFileOrFolder(resource, true, contents, options, token); + create(operations: ICreateFileOperation[], undoInfo?: IFileOperationUndoRedoInfo, token?: CancellationToken): Promise { + return Promise.all(operations.map(o => this.doCreateFileOrFolder(o, true, undoInfo, token))); } - createFolder(resource: URI, options?: { overwrite?: boolean, undoRedoGroupId?: number, isUndoing?: boolean }, token?: CancellationToken): Promise { - return this.doCreateFileOrFolder(resource, false, undefined, options, token); + createFolder(operations: ICreateOperation[], undoInfo?: IFileOperationUndoRedoInfo, token?: CancellationToken): Promise { + return Promise.all(operations.map(o => this.doCreateFileOrFolder(o, false, undoInfo, token))); } - async doCreateFileOrFolder(resource: URI, isFile: boolean, contents?: VSBuffer | VSBufferReadable | VSBufferReadableStream, options?: { overwrite?: boolean, undoRedoGroupId?: number, isUndoing?: boolean }, token?: CancellationToken): Promise { + async doCreateFileOrFolder(operation: ICreateFileOperation | ICreateOperation, isFile: boolean, undoInfo?: IFileOperationUndoRedoInfo, token?: CancellationToken): Promise { // validate create operation before starting if (isFile) { - const validateCreate = await this.fileService.canCreateFile(resource, options); + const validateCreate = await this.fileService.canCreateFile(operation.resource, { overwrite: operation.overwrite }); if (validateCreate instanceof Error) { throw validateCreate; } } // file operation participant - await this.runFileOperationParticipants([{ target: resource }], FileOperation.CREATE, options, token); + await this.runFileOperationParticipants([{ target: operation.resource }], FileOperation.CREATE, undoInfo, token); // before events - const event = { correlationId: this.correlationIds++, operation: FileOperation.CREATE, files: [{ target: resource }] }; + const event = { correlationId: this.correlationIds++, operation: FileOperation.CREATE, files: [{ target: operation.resource }] }; await this._onWillRunWorkingCopyFileOperation.fireAsync(event, CancellationToken.None); // now actually create on disk let stat: IFileStatWithMetadata; try { if (isFile) { - stat = await this.fileService.createFile(resource, contents, { overwrite: options?.overwrite }); + stat = await this.fileService.createFile(operation.resource, (operation as ICreateFileOperation).contents, { overwrite: operation.overwrite }); } else { - stat = await this.fileService.createFolder(resource); + stat = await this.fileService.createFolder(operation.resource); } } catch (error) { diff --git a/src/vs/workbench/services/workingCopy/test/browser/workingCopyFileService.test.ts b/src/vs/workbench/services/workingCopy/test/browser/workingCopyFileService.test.ts index 78eecbaa6f32..3ea04364e329 100644 --- a/src/vs/workbench/services/workingCopy/test/browser/workingCopyFileService.test.ts +++ b/src/vs/workbench/services/workingCopy/test/browser/workingCopyFileService.test.ts @@ -224,7 +224,7 @@ suite('WorkingCopyFileService', () => { eventCounter++; }); - await accessor.workingCopyFileService.createFolder(resource); + await accessor.workingCopyFileService.createFolder([{ resource }]); assert.equal(eventCounter, 3); @@ -459,7 +459,7 @@ suite('WorkingCopyFileService', () => { eventCounter++; }); - await accessor.workingCopyFileService.create(resource, contents); + await accessor.workingCopyFileService.create([{ resource, contents }]); assert.ok(!accessor.workingCopyService.isDirty(model.resource)); model.dispose(); diff --git a/src/vs/workbench/test/browser/api/mainThreadEditors.test.ts b/src/vs/workbench/test/browser/api/mainThreadEditors.test.ts index 8ce5268be391..cf370439d42a 100644 --- a/src/vs/workbench/test/browser/api/mainThreadEditors.test.ts +++ b/src/vs/workbench/test/browser/api/mainThreadEditors.test.ts @@ -40,7 +40,7 @@ import { SyncDescriptor } from 'vs/platform/instantiation/common/descriptors'; import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace'; import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; import { ILabelService } from 'vs/platform/label/common/label'; -import { IWorkingCopyFileService, IMoveOperation, IDeleteOperation, ICopyOperation } from 'vs/workbench/services/workingCopy/common/workingCopyFileService'; +import { IWorkingCopyFileService, IMoveOperation, IDeleteOperation, ICopyOperation, ICreateFileOperation } from 'vs/workbench/services/workingCopy/common/workingCopyFileService'; import { UndoRedoService } from 'vs/platform/undoRedo/common/undoRedoService'; import { TestDialogService } from 'vs/platform/dialogs/test/common/testDialogService'; import { IDialogService } from 'vs/platform/dialogs/common/dialogs'; @@ -103,8 +103,10 @@ suite('MainThreadEditors', () => { }); services.set(IWorkingCopyFileService, new class extends mock() { onDidRunWorkingCopyFileOperation = Event.None; - create(resource: URI) { - createdResources.add(resource); + create(operations: ICreateFileOperation[]) { + for (const operation of operations) { + createdResources.add(operation.resource); + } return Promise.resolve(Object.create(null)); } move(operations: IMoveOperation[]) { diff --git a/src/vs/workbench/test/common/workbenchTestServices.ts b/src/vs/workbench/test/common/workbenchTestServices.ts index 79c02bedf6b9..6a00cd3ef4d2 100644 --- a/src/vs/workbench/test/common/workbenchTestServices.ts +++ b/src/vs/workbench/test/common/workbenchTestServices.ts @@ -16,10 +16,9 @@ import { isLinux, isMacintosh } from 'vs/base/common/platform'; import { InMemoryStorageService, WillSaveStateReason } from 'vs/platform/storage/common/storage'; import { WorkingCopyService, IWorkingCopy, IWorkingCopyBackup, WorkingCopyCapabilities } from 'vs/workbench/services/workingCopy/common/workingCopyService'; import { NullExtensionService } from 'vs/workbench/services/extensions/common/extensions'; -import { IWorkingCopyFileService, IWorkingCopyFileOperationParticipant, WorkingCopyFileEvent, IDeleteOperation, ICopyOperation, IMoveOperation, IFileOperationUndoRedoInfo } from 'vs/workbench/services/workingCopy/common/workingCopyFileService'; +import { IWorkingCopyFileService, IWorkingCopyFileOperationParticipant, WorkingCopyFileEvent, IDeleteOperation, ICopyOperation, IMoveOperation, IFileOperationUndoRedoInfo, ICreateFileOperation, ICreateOperation } from 'vs/workbench/services/workingCopy/common/workingCopyFileService'; import { IDisposable, Disposable } from 'vs/base/common/lifecycle'; import { IFileStatWithMetadata } from 'vs/platform/files/common/files'; -import { VSBuffer, VSBufferReadable, VSBufferReadableStream } from 'vs/base/common/buffer'; import { ISaveOptions, IRevertOptions } from 'vs/workbench/common/editor'; import { CancellationToken } from 'vs/base/common/cancellation'; @@ -202,8 +201,8 @@ export class TestWorkingCopyFileService implements IWorkingCopyFileService { getDirty(resource: URI): IWorkingCopy[] { return []; } - create(resource: URI, contents?: VSBuffer | VSBufferReadable | VSBufferReadableStream, options?: { overwrite?: boolean | undefined; } | undefined): Promise { throw new Error('Method not implemented.'); } - createFolder(resource: URI): Promise { throw new Error('Method not implemented.'); } + create(operations: ICreateFileOperation[], undoInfo?: IFileOperationUndoRedoInfo, token?: CancellationToken): Promise { throw new Error('Method not implemented.'); } + createFolder(operations: ICreateOperation[], undoInfo?: IFileOperationUndoRedoInfo, token?: CancellationToken): Promise { throw new Error('Method not implemented.'); } move(operations: IMoveOperation[], undoInfo?: IFileOperationUndoRedoInfo): Promise { throw new Error('Method not implemented.'); }