diff --git a/build/lib/i18n.resources.json b/build/lib/i18n.resources.json index fe693b695b4..ae71fd3bf63 100644 --- a/build/lib/i18n.resources.json +++ b/build/lib/i18n.resources.json @@ -302,6 +302,10 @@ "name": "vs/workbench/services/textMate", "project": "vscode-workbench" }, + { + "name": "vs/workbench/services/workingCopy", + "project": "vscode-workbench" + }, { "name": "vs/workbench/services/workspaces", "project": "vscode-workbench" diff --git a/src/vs/base/common/async.ts b/src/vs/base/common/async.ts index ceb38bd5b18..e5ff745c876 100644 --- a/src/vs/base/common/async.ts +++ b/src/vs/base/common/async.ts @@ -56,6 +56,20 @@ export function raceCancellation(promise: Promise, token: CancellationToke return Promise.race([promise, new Promise(resolve => token.onCancellationRequested(() => resolve(defaultValue)))]); } +export function raceTimeout(promise: Promise, timeout: number, onTimeout?: () => void): Promise { + let promiseResolve: (() => void) | undefined = undefined; + + const timer = setTimeout(() => { + promiseResolve?.(); + onTimeout?.(); + }, timeout); + + return Promise.race([ + promise.finally(() => clearTimeout(timer)), + new Promise(resolve => promiseResolve = resolve) + ]); +} + export function asPromise(callback: () => T | Thenable): Promise { return new Promise((resolve, reject) => { const item = callback(); diff --git a/src/vs/base/test/common/async.test.ts b/src/vs/base/test/common/async.test.ts index dda98ab6e1c..25e7d83cc7b 100644 --- a/src/vs/base/test/common/async.test.ts +++ b/src/vs/base/test/common/async.test.ts @@ -7,6 +7,7 @@ import * as assert from 'assert'; import * as async from 'vs/base/common/async'; import { isPromiseCanceledError } from 'vs/base/common/errors'; import { URI } from 'vs/base/common/uri'; +import { CancellationTokenSource } from 'vs/base/common/cancellation'; suite('Async', () => { @@ -646,4 +647,45 @@ suite('Async', () => { assert.ok(pendingCancelled); }); + + test('raceCancellation', async () => { + const cts = new CancellationTokenSource(); + + const now = Date.now(); + + const p = async.raceCancellation(async.timeout(100), cts.token); + cts.cancel(); + + await p; + + assert.ok(Date.now() - now < 100); + }); + + test('raceTimeout', async () => { + const cts = new CancellationTokenSource(); + + // timeout wins + let now = Date.now(); + let timedout = false; + + const p1 = async.raceTimeout(async.timeout(100), 1, () => timedout = true); + cts.cancel(); + + await p1; + + assert.ok(Date.now() - now < 100); + assert.equal(timedout, true); + + // promise wins + now = Date.now(); + timedout = false; + + const p2 = async.raceTimeout(async.timeout(1), 100, () => timedout = true); + cts.cancel(); + + await p2; + + assert.ok(Date.now() - now < 100); + assert.equal(timedout, false); + }); }); diff --git a/src/vs/workbench/api/browser/mainThreadFileSystemEventService.ts b/src/vs/workbench/api/browser/mainThreadFileSystemEventService.ts index d9cfed2f091..d88424433fb 100644 --- a/src/vs/workbench/api/browser/mainThreadFileSystemEventService.ts +++ b/src/vs/workbench/api/browser/mainThreadFileSystemEventService.ts @@ -8,16 +8,13 @@ import { FileChangeType, IFileService, FileOperation } from 'vs/platform/files/c import { extHostCustomer } from 'vs/workbench/api/common/extHostCustomers'; import { ExtHostContext, FileSystemEvents, IExtHostContext } from '../common/extHost.protocol'; import { ITextFileService } from 'vs/workbench/services/textfile/common/textfiles'; -import { IProgressService, ProgressLocation } from 'vs/platform/progress/common/progress'; +import { IProgressService } from 'vs/platform/progress/common/progress'; import { localize } from 'vs/nls'; import { Extensions, IConfigurationRegistry } from 'vs/platform/configuration/common/configurationRegistry'; import { Registry } from 'vs/platform/registry/common/platform'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { ILogService } from 'vs/platform/log/common/log'; -import { CancellationTokenSource } from 'vs/base/common/cancellation'; import { IWorkingCopyFileService } from 'vs/workbench/services/workingCopy/common/workingCopyFileService'; -import { URI } from 'vs/base/common/uri'; -import { IWaitUntil } from 'vs/base/common/event'; @extHostCustomer export class MainThreadFileSystemEventService { @@ -65,43 +62,11 @@ export class MainThreadFileSystemEventService { // BEFORE file operation - const messages = new Map(); - messages.set(FileOperation.CREATE, localize('msg-create', "Running 'File Create' participants...")); - messages.set(FileOperation.DELETE, localize('msg-delete', "Running 'File Delete' participants...")); - messages.set(FileOperation.MOVE, localize('msg-rename', "Running 'File Rename' participants...")); - - function participateInFileOperation(e: IWaitUntil, operation: FileOperation, target: URI, source?: URI): void { - const timeout = configService.getValue('files.participants.timeout'); - if (timeout <= 0) { - return; // disabled + workingCopyFileService.addFileOperationParticipant({ + participate: (target, source, operation, progress, timeout, token) => { + return proxy.$onWillRunFileOperation(operation, target, source, timeout, token); } - - const p = progressService.withProgress({ location: ProgressLocation.Window }, progress => { - - progress.report({ message: messages.get(operation) }); - - return new Promise((resolve, reject) => { - - const cts = new CancellationTokenSource(); - - const timeoutHandle = setTimeout(() => { - logService.trace('CANCELLED file participants because of timeout', timeout, target, operation); - cts.cancel(); - reject(new Error('timeout')); - }, timeout); - - proxy.$onWillRunFileOperation(operation, target, source, timeout, cts.token) - .then(resolve, reject) - .finally(() => clearTimeout(timeoutHandle)); - }); - - }); - - e.waitUntil(p); - } - - this._listener.add(textFileService.onWillCreateTextFile(e => participateInFileOperation(e, FileOperation.CREATE, e.resource))); - this._listener.add(workingCopyFileService.onBeforeWorkingCopyFileOperation(e => participateInFileOperation(e, e.operation, e.target, e.source))); + }); // AFTER file operation this._listener.add(textFileService.onDidCreateTextFile(e => proxy.$onDidRunFileOperation(FileOperation.CREATE, e.resource, undefined))); diff --git a/src/vs/workbench/services/textfile/browser/textFileService.ts b/src/vs/workbench/services/textfile/browser/textFileService.ts index 1d1f71f63f2..f97ce3ccfb2 100644 --- a/src/vs/workbench/services/textfile/browser/textFileService.ts +++ b/src/vs/workbench/services/textfile/browser/textFileService.ts @@ -9,7 +9,7 @@ import { AsyncEmitter } from 'vs/base/common/event'; import { ITextFileService, ITextFileStreamContent, ITextFileContent, IResourceEncodings, IReadTextFileOptions, IWriteTextFileOptions, toBufferOrReadable, TextFileOperationError, TextFileOperationResult, ITextFileSaveOptions, ITextFileEditorModelManager, TextFileCreateEvent } from 'vs/workbench/services/textfile/common/textfiles'; import { IRevertOptions, IEncodingSupport } from 'vs/workbench/common/editor'; import { ILifecycleService } from 'vs/platform/lifecycle/common/lifecycle'; -import { IFileService, FileOperationError, FileOperationResult, IFileStatWithMetadata, ICreateFileOptions } from 'vs/platform/files/common/files'; +import { IFileService, FileOperationError, FileOperationResult, IFileStatWithMetadata, ICreateFileOptions, FileOperation } from 'vs/platform/files/common/files'; import { Disposable } from 'vs/base/common/lifecycle'; import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; import { IUntitledTextEditorService, IUntitledTextEditorModelManager } from 'vs/workbench/services/untitled/common/untitledTextEditorService'; @@ -33,6 +33,7 @@ import { ICodeEditorService } from 'vs/editor/browser/services/codeEditorService import { suggestFilename } from 'vs/base/common/mime'; import { IRemotePathService } from 'vs/workbench/services/path/common/remotePathService'; import { isValidBasename } from 'vs/base/common/extpath'; +import { IWorkingCopyFileService } from 'vs/workbench/services/workingCopy/common/workingCopyFileService'; /** * The workbench file service implementation implements the raw file service spec and adds additional methods on top. @@ -43,9 +44,6 @@ export abstract class AbstractTextFileService extends Disposable implements ITex //#region events - private _onWillCreateTextFile = this._register(new AsyncEmitter()); - readonly onWillCreateTextFile = this._onWillCreateTextFile.event; - private _onDidCreateTextFile = this._register(new AsyncEmitter()); readonly onDidCreateTextFile = this._onDidCreateTextFile.event; @@ -70,7 +68,8 @@ export abstract class AbstractTextFileService extends Disposable implements ITex @IFilesConfigurationService protected readonly filesConfigurationService: IFilesConfigurationService, @ITextModelService private readonly textModelService: ITextModelService, @ICodeEditorService private readonly codeEditorService: ICodeEditorService, - @IRemotePathService private readonly remotePathService: IRemotePathService + @IRemotePathService private readonly remotePathService: IRemotePathService, + @IWorkingCopyFileService private readonly workingCopyFileService: IWorkingCopyFileService ) { super(); @@ -141,8 +140,8 @@ export abstract class AbstractTextFileService extends Disposable implements ITex async create(resource: URI, value?: string | ITextSnapshot, options?: ICreateFileOptions): Promise { - // before event - await this._onWillCreateTextFile.fireAsync({ resource }, CancellationToken.None); + // file operation participation + await this.workingCopyFileService.runFileOperationParticipants(resource, undefined, FileOperation.CREATE); // create file on disk const stat = await this.doCreate(resource, value, options); diff --git a/src/vs/workbench/services/textfile/common/textFileSaveParticipant.ts b/src/vs/workbench/services/textfile/common/textFileSaveParticipant.ts index 2ba91174b67..eb9ce782052 100644 --- a/src/vs/workbench/services/textfile/common/textFileSaveParticipant.ts +++ b/src/vs/workbench/services/textfile/common/textFileSaveParticipant.ts @@ -3,9 +3,9 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +import { localize } from 'vs/nls'; import { raceCancellation } from 'vs/base/common/async'; import { CancellationTokenSource, CancellationToken } from 'vs/base/common/cancellation'; -import { localize } from 'vs/nls'; import { ILogService } from 'vs/platform/log/common/log'; import { IProgressService, ProgressLocation } from 'vs/platform/progress/common/progress'; import { ITextFileSaveParticipant, IResolvedTextFileEditorModel } from 'vs/workbench/services/textfile/common/textfiles'; diff --git a/src/vs/workbench/services/textfile/common/textfiles.ts b/src/vs/workbench/services/textfile/common/textfiles.ts index 25ebaac45cd..d3531ab7b7b 100644 --- a/src/vs/workbench/services/textfile/common/textfiles.ts +++ b/src/vs/workbench/services/textfile/common/textfiles.ts @@ -95,11 +95,6 @@ export interface ITextFileService extends IDisposable { */ write(resource: URI, value: string | ITextSnapshot, options?: IWriteTextFileOptions): Promise; - /** - * An event that is fired before attempting to create a text file. - */ - readonly onWillCreateTextFile: Event; - /** * An event that is fired after a text file has been created. */ diff --git a/src/vs/workbench/services/textfile/electron-browser/nativeTextFileService.ts b/src/vs/workbench/services/textfile/electron-browser/nativeTextFileService.ts index def5df7ccbe..aeff0a5f220 100644 --- a/src/vs/workbench/services/textfile/electron-browser/nativeTextFileService.ts +++ b/src/vs/workbench/services/textfile/electron-browser/nativeTextFileService.ts @@ -38,6 +38,7 @@ import { IFilesConfigurationService } from 'vs/workbench/services/filesConfigura import { ITextModelService } from 'vs/editor/common/services/resolverService'; import { ICodeEditorService } from 'vs/editor/browser/services/codeEditorService'; import { IRemotePathService } from 'vs/workbench/services/path/common/remotePathService'; +import { IWorkingCopyFileService } from 'vs/workbench/services/workingCopy/common/workingCopyFileService'; export class NativeTextFileService extends AbstractTextFileService { @@ -55,9 +56,10 @@ export class NativeTextFileService extends AbstractTextFileService { @IFilesConfigurationService filesConfigurationService: IFilesConfigurationService, @ITextModelService textModelService: ITextModelService, @ICodeEditorService codeEditorService: ICodeEditorService, - @IRemotePathService remotePathService: IRemotePathService + @IRemotePathService remotePathService: IRemotePathService, + @IWorkingCopyFileService workingCopyFileService: IWorkingCopyFileService ) { - super(fileService, untitledTextEditorService, lifecycleService, instantiationService, modelService, environmentService, dialogService, fileDialogService, textResourceConfigurationService, filesConfigurationService, textModelService, codeEditorService, remotePathService); + super(fileService, untitledTextEditorService, lifecycleService, instantiationService, modelService, environmentService, dialogService, fileDialogService, textResourceConfigurationService, filesConfigurationService, textModelService, codeEditorService, remotePathService, workingCopyFileService); } private _encoding: EncodingOracle | undefined; diff --git a/src/vs/workbench/services/textfile/test/browser/textFileService.test.ts b/src/vs/workbench/services/textfile/test/browser/textFileService.test.ts index 9c613755fa5..2279232737c 100644 --- a/src/vs/workbench/services/textfile/test/browser/textFileService.test.ts +++ b/src/vs/workbench/services/textfile/test/browser/textFileService.test.ts @@ -135,12 +135,14 @@ suite('Files - TextFileService', () => { let eventCounter = 0; - accessor.textFileService.onWillCreateTextFile(e => { - assert.equal(e.resource.toString(), model.resource.toString()); - eventCounter++; + const disposable1 = accessor.workingCopyFileService.addFileOperationParticipant({ + participate: async target => { + assert.equal(target.toString(), model.resource.toString()); + eventCounter++; + } }); - accessor.textFileService.onDidCreateTextFile(e => { + const disposable2 = accessor.textFileService.onDidCreateTextFile(e => { assert.equal(e.resource.toString(), model.resource.toString()); eventCounter++; }); @@ -149,5 +151,8 @@ suite('Files - TextFileService', () => { assert.ok(!accessor.textFileService.isDirty(model.resource)); assert.equal(eventCounter, 2); + + disposable1.dispose(); + disposable2.dispose(); }); }); diff --git a/src/vs/workbench/services/workingCopy/common/workingCopyFileOperationParticipant.ts b/src/vs/workbench/services/workingCopy/common/workingCopyFileOperationParticipant.ts new file mode 100644 index 00000000000..0b194d5d1bf --- /dev/null +++ b/src/vs/workbench/services/workingCopy/common/workingCopyFileOperationParticipant.ts @@ -0,0 +1,80 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { localize } from 'vs/nls'; +import { raceTimeout } from 'vs/base/common/async'; +import { CancellationTokenSource } from 'vs/base/common/cancellation'; +import { ILogService } from 'vs/platform/log/common/log'; +import { IProgressService, ProgressLocation } from 'vs/platform/progress/common/progress'; +import { IDisposable, Disposable, toDisposable } from 'vs/base/common/lifecycle'; +import { IWorkingCopyFileOperationParticipant } from 'vs/workbench/services/workingCopy/common/workingCopyFileService'; +import { URI } from 'vs/base/common/uri'; +import { FileOperation } from 'vs/platform/files/common/files'; +import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; + +export class WorkingCopyFileOperationParticipant extends Disposable { + + private readonly participants: IWorkingCopyFileOperationParticipant[] = []; + + constructor( + @IProgressService private readonly progressService: IProgressService, + @ILogService private readonly logService: ILogService, + @IConfigurationService private readonly configurationService: IConfigurationService + ) { + super(); + } + + addFileOperationParticipant(participant: IWorkingCopyFileOperationParticipant): IDisposable { + this.participants.push(participant); + + return toDisposable(() => this.participants.splice(this.participants.indexOf(participant), 1)); + } + + async participate(target: URI, source: URI | undefined, operation: FileOperation): Promise { + const timeout = this.configurationService.getValue('files.participants.timeout'); + if (timeout <= 0) { + return; // disabled + } + + const cts = new CancellationTokenSource(); + + return this.progressService.withProgress({ + location: ProgressLocation.Window, + title: this.progressLabel(operation) + }, async progress => { + + // For each participant + for (const participant of this.participants) { + if (cts.token.isCancellationRequested) { + break; + } + + try { + const promise = participant.participate(target, source, operation, progress, timeout, cts.token); + await raceTimeout(promise, timeout, () => cts.dispose(true /* cancel */)); + } catch (err) { + this.logService.warn(err); + } + } + }); + } + + private progressLabel(operation: FileOperation): string { + switch (operation) { + case FileOperation.CREATE: + return localize('msg-create', "Running 'File Create' participants..."); + case FileOperation.MOVE: + return localize('msg-rename', "Running 'File Rename' participants..."); + case FileOperation.COPY: + return localize('msg-copy', "Running 'File Copy' participants..."); + case FileOperation.DELETE: + return localize('msg-delete', "Running 'File Delete' participants..."); + } + } + + dispose(): void { + this.participants.splice(0, this.participants.length); + } +} diff --git a/src/vs/workbench/services/workingCopy/common/workingCopyFileService.ts b/src/vs/workbench/services/workingCopy/common/workingCopyFileService.ts index fdb12e44d28..7b7e1bff3bc 100644 --- a/src/vs/workbench/services/workingCopy/common/workingCopyFileService.ts +++ b/src/vs/workbench/services/workingCopy/common/workingCopyFileService.ts @@ -3,15 +3,17 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; +import { createDecorator, IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; import { Event, AsyncEmitter, IWaitUntil } from 'vs/base/common/event'; import { URI } from 'vs/base/common/uri'; -import { Disposable } from 'vs/base/common/lifecycle'; +import { Disposable, IDisposable } from 'vs/base/common/lifecycle'; import { IFileService, FileOperation, IFileStatWithMetadata } from 'vs/platform/files/common/files'; import { CancellationToken } from 'vs/base/common/cancellation'; import { IWorkingCopyService, IWorkingCopy } from 'vs/workbench/services/workingCopy/common/workingCopyService'; import { isEqualOrParent, isEqual } from 'vs/base/common/resources'; +import { IProgress, IProgressStep } from 'vs/platform/progress/common/progress'; +import { WorkingCopyFileOperationParticipant } from 'vs/workbench/services/workingCopy/common/workingCopyFileOperationParticipant'; export const IWorkingCopyFileService = createDecorator('workingCopyFileService'); @@ -39,6 +41,22 @@ export interface WorkingCopyFileEvent extends IWaitUntil { readonly source?: URI; } +export interface IWorkingCopyFileOperationParticipant { + + /** + * Participate in a file operation of a working copy. Allows to + * change the working copy before it is being saved to disk. + */ + participate( + target: URI, + source: URI | undefined, + operation: FileOperation, + progress: IProgress, + timeout: number, + token: CancellationToken + ): Promise; +} + /** * A service that allows to perform file operations with working copy support. * Any operation that would leave a stale dirty working copy behind will make @@ -53,20 +71,12 @@ export interface IWorkingCopyFileService { //#region Events - /** - * An event that is fired before attempting a certain working copy IO operation. - * - * Participants can join this event with a long running operation to make changes - * to the working copy before the operation starts. - */ - readonly onBeforeWorkingCopyFileOperation: Event; - /** * An event that is fired when a certain working copy IO operation is about to run. * * Participants can join this event with a long running operation to keep some state * before the operation is started, but working copies should not be changed at this - * point in time. + * point in time. For that purpose, use the `IWorkingCopyFileOperationParticipant` API. */ readonly onWillRunWorkingCopyFileOperation: Event; @@ -88,6 +98,19 @@ export interface IWorkingCopyFileService { //#endregion + //#region File operation participants + + /** + * Adds a participant for file operations on working copies. + */ + addFileOperationParticipant(participant: IWorkingCopyFileOperationParticipant): IDisposable; + + /** + * Execute all known file operation participants. + */ + runFileOperationParticipants(target: URI, source: URI | undefined, operation: FileOperation): Promise + + //#region File operations /** @@ -138,9 +161,6 @@ export class WorkingCopyFileService extends Disposable implements IWorkingCopyFi //#region Events - private readonly _onBeforeWorkingCopyFileOperation = this._register(new AsyncEmitter()); - readonly onBeforeWorkingCopyFileOperation = this._onBeforeWorkingCopyFileOperation.event; - private readonly _onWillRunWorkingCopyFileOperation = this._register(new AsyncEmitter()); readonly onWillRunWorkingCopyFileOperation = this._onWillRunWorkingCopyFileOperation.event; @@ -155,8 +175,9 @@ export class WorkingCopyFileService extends Disposable implements IWorkingCopyFi private correlationIds = 0; constructor( - @IFileService private fileService: IFileService, - @IWorkingCopyService private workingCopyService: IWorkingCopyService + @IFileService private readonly fileService: IFileService, + @IWorkingCopyService private readonly workingCopyService: IWorkingCopyService, + @IInstantiationService private readonly instantiationService: IInstantiationService ) { super(); } @@ -170,10 +191,12 @@ export class WorkingCopyFileService extends Disposable implements IWorkingCopyFi } private async moveOrCopy(source: URI, target: URI, move: boolean, overwrite?: boolean): Promise { - const event = { correlationId: this.correlationIds++, operation: move ? FileOperation.MOVE : FileOperation.COPY, target, source }; - // before events - await this._onBeforeWorkingCopyFileOperation.fireAsync(event, CancellationToken.None); + // file operation participant + await this.runFileOperationParticipants(target, source, move ? FileOperation.MOVE : FileOperation.COPY); + + // before event + const event = { correlationId: this.correlationIds++, operation: move ? FileOperation.MOVE : FileOperation.COPY, target, source }; await this._onWillRunWorkingCopyFileOperation.fireAsync(event, CancellationToken.None); // handle dirty working copies depending on the operation: @@ -205,10 +228,12 @@ export class WorkingCopyFileService extends Disposable implements IWorkingCopyFi } async delete(resource: URI, options?: { useTrash?: boolean, recursive?: boolean }): Promise { - const event = { correlationId: this.correlationIds++, operation: FileOperation.DELETE, target: resource }; + + // file operation participant + await this.runFileOperationParticipants(resource, undefined, FileOperation.DELETE); // before events - await this._onBeforeWorkingCopyFileOperation.fireAsync(event, CancellationToken.None); + const event = { correlationId: this.correlationIds++, operation: FileOperation.DELETE, target: resource }; await this._onWillRunWorkingCopyFileOperation.fireAsync(event, CancellationToken.None); // Check for any existing dirty working copies for the resource @@ -233,6 +258,21 @@ export class WorkingCopyFileService extends Disposable implements IWorkingCopyFi } + //#region File operation participants + + private readonly fileOperationParticipants = this._register(this.instantiationService.createInstance(WorkingCopyFileOperationParticipant)); + + addFileOperationParticipant(participant: IWorkingCopyFileOperationParticipant): IDisposable { + return this.fileOperationParticipants.addFileOperationParticipant(participant); + } + + runFileOperationParticipants(target: URI, source: URI | undefined, operation: FileOperation): Promise { + return this.fileOperationParticipants.participate(target, source, operation); + } + + //#endregion + + //#region Path related getDirty(resource: URI): IWorkingCopy[] { 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 642486db70c..ffcf97d9edc 100644 --- a/src/vs/workbench/services/workingCopy/test/browser/workingCopyFileService.test.ts +++ b/src/vs/workbench/services/workingCopy/test/browser/workingCopyFileService.test.ts @@ -51,17 +51,18 @@ suite('WorkingCopyFileService', () => { let eventCounter = 0; let correlationId: number | undefined = undefined; - const listener0 = accessor.workingCopyFileService.onBeforeWorkingCopyFileOperation(e => { - assert.equal(e.target.toString(), model.resource.toString()); - assert.equal(e.operation, FileOperation.DELETE); - eventCounter++; - correlationId = e.correlationId; + const participant = accessor.workingCopyFileService.addFileOperationParticipant({ + participate: async (target, source, operation) => { + assert.equal(target.toString(), model.resource.toString()); + assert.equal(operation, FileOperation.DELETE); + eventCounter++; + } }); const listener1 = accessor.workingCopyFileService.onWillRunWorkingCopyFileOperation(e => { assert.equal(e.target.toString(), model.resource.toString()); assert.equal(e.operation, FileOperation.DELETE); - assert.equal(e.correlationId, correlationId); + correlationId = e.correlationId; eventCounter++; }); @@ -77,7 +78,7 @@ suite('WorkingCopyFileService', () => { assert.equal(eventCounter, 3); - listener0.dispose(); + participant.dispose(); listener1.dispose(); listener2.dispose(); }); @@ -117,12 +118,13 @@ suite('WorkingCopyFileService', () => { let eventCounter = 0; let correlationId: number | undefined = undefined; - const listener0 = accessor.workingCopyFileService.onBeforeWorkingCopyFileOperation(e => { - assert.equal(e.target.toString(), targetModel.resource.toString()); - assert.equal(e.source?.toString(), sourceModel.resource.toString()); - assert.equal(e.operation, move ? FileOperation.MOVE : FileOperation.COPY); - eventCounter++; - correlationId = e.correlationId; + const participant = accessor.workingCopyFileService.addFileOperationParticipant({ + participate: async (target, source, operation) => { + assert.equal(target.toString(), targetModel.resource.toString()); + assert.equal(source?.toString(), sourceModel.resource.toString()); + assert.equal(operation, move ? FileOperation.MOVE : FileOperation.COPY); + eventCounter++; + } }); const listener1 = accessor.workingCopyFileService.onWillRunWorkingCopyFileOperation(e => { @@ -130,7 +132,7 @@ suite('WorkingCopyFileService', () => { assert.equal(e.source?.toString(), sourceModel.resource.toString()); assert.equal(e.operation, move ? FileOperation.MOVE : FileOperation.COPY); eventCounter++; - assert.equal(e.correlationId, correlationId); + correlationId = e.correlationId; }); const listener2 = accessor.workingCopyFileService.onDidRunWorkingCopyFileOperation(e => { @@ -161,7 +163,7 @@ suite('WorkingCopyFileService', () => { sourceModel.dispose(); targetModel.dispose(); - listener0.dispose(); + participant.dispose(); listener1.dispose(); listener2.dispose(); } diff --git a/src/vs/workbench/test/browser/workbenchTestServices.ts b/src/vs/workbench/test/browser/workbenchTestServices.ts index 901efc53693..fb5e9e681da 100644 --- a/src/vs/workbench/test/browser/workbenchTestServices.ts +++ b/src/vs/workbench/test/browser/workbenchTestServices.ts @@ -122,7 +122,8 @@ export class TestTextFileService extends BrowserTextFileService { @IFilesConfigurationService filesConfigurationService: IFilesConfigurationService, @ITextModelService textModelService: ITextModelService, @ICodeEditorService codeEditorService: ICodeEditorService, - @IRemotePathService remotePathService: IRemotePathService + @IRemotePathService remotePathService: IRemotePathService, + @IWorkingCopyFileService workingCopyFileService: IWorkingCopyFileService ) { super( fileService, @@ -137,7 +138,8 @@ export class TestTextFileService extends BrowserTextFileService { filesConfigurationService, textModelService, codeEditorService, - remotePathService + remotePathService, + workingCopyFileService ); } diff --git a/src/vs/workbench/test/electron-browser/workbenchTestServices.ts b/src/vs/workbench/test/electron-browser/workbenchTestServices.ts index db2309ddf7b..769dfdcb2ce 100644 --- a/src/vs/workbench/test/electron-browser/workbenchTestServices.ts +++ b/src/vs/workbench/test/electron-browser/workbenchTestServices.ts @@ -29,6 +29,7 @@ import { IOpenedWindow, IOpenEmptyWindowOptions, IWindowOpenable, IOpenWindowOpt import { parseArgs, OPTIONS } from 'vs/platform/environment/node/argv'; import { LogLevel } from 'vs/platform/log/common/log'; import { IRemotePathService } from 'vs/workbench/services/path/common/remotePathService'; +import { IWorkingCopyFileService } from 'vs/workbench/services/workingCopy/common/workingCopyFileService'; export const TestWindowConfiguration: IWindowConfiguration = { windowId: 0, @@ -61,7 +62,8 @@ export class TestTextFileService extends NativeTextFileService { @IFilesConfigurationService filesConfigurationService: IFilesConfigurationService, @ITextModelService textModelService: ITextModelService, @ICodeEditorService codeEditorService: ICodeEditorService, - @IRemotePathService remotePathService: IRemotePathService + @IRemotePathService remotePathService: IRemotePathService, + @IWorkingCopyFileService workingCopyFileService: IWorkingCopyFileService ) { super( fileService, @@ -77,7 +79,8 @@ export class TestTextFileService extends NativeTextFileService { filesConfigurationService, textModelService, codeEditorService, - remotePathService + remotePathService, + workingCopyFileService ); }