diff --git a/src/vs/base/common/event.ts b/src/vs/base/common/event.ts index 1f397703d165382a591f397b6d31fa3d0b418aa7..e6c16f43df6a1194b91660c918ad009b4d34f6dd 100644 --- a/src/vs/base/common/event.ts +++ b/src/vs/base/common/event.ts @@ -654,27 +654,39 @@ export interface IWaitUntil { export class AsyncEmitter extends Emitter { - private _asyncDeliveryQueue?: [Listener, T, Promise[]][]; + private _asyncDeliveryQueue?: LinkedList<[Listener, Omit]>; - async fireAsync(eventFn: (thenables: Promise[], listener: Function) => T, token: CancellationToken): Promise { + async fireAsync(data: Omit, token: CancellationToken, promiseJoin?: (p: Promise) => Promise): Promise { if (!this._listeners) { return; } - // put all [listener,event]-pairs into delivery queue - // then emit all event. an inner/nested event might be - // the driver of this if (!this._asyncDeliveryQueue) { - this._asyncDeliveryQueue = []; + this._asyncDeliveryQueue = new LinkedList(); } for (let iter = this._listeners.iterator(), e = iter.next(); !e.done; e = iter.next()) { - const thenables: Promise[] = []; - this._asyncDeliveryQueue.push([e.value, eventFn(thenables, typeof e.value === 'function' ? e.value : e.value[0]), thenables]); + this._asyncDeliveryQueue.push([e.value, data]); } - while (this._asyncDeliveryQueue.length > 0 && !token.isCancellationRequested) { - const [listener, event, thenables] = this._asyncDeliveryQueue.shift()!; + while (this._asyncDeliveryQueue.size > 0 && !token.isCancellationRequested) { + + const [listener, data] = this._asyncDeliveryQueue.shift()!; + const thenables: Promise[] = []; + + const event = { + ...data, + waitUntil: (p: Promise): void => { + if (Object.isFrozen(thenables)) { + throw new Error('waitUntil can NOT be called asynchronous'); + } + if (promiseJoin) { + p = promiseJoin(p); + } + thenables.push(p); + } + }; + try { if (typeof listener === 'function') { listener.call(undefined, event); diff --git a/src/vs/base/test/common/event.test.ts b/src/vs/base/test/common/event.test.ts index 5937f8ecc7341918d11480462535d5e0189a46ec..a0e8ca9fcfa402edf2b4e4737c81394372da83a5 100644 --- a/src/vs/base/test/common/event.test.ts +++ b/src/vs/base/test/common/event.test.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ import * as assert from 'assert'; -import { Event, Emitter, EventBufferer, EventMultiplexer, AsyncEmitter, IWaitUntil, PauseableEmitter } from 'vs/base/common/event'; +import { Event, Emitter, EventBufferer, EventMultiplexer, IWaitUntil, PauseableEmitter, AsyncEmitter } from 'vs/base/common/event'; import { IDisposable, DisposableStore } from 'vs/base/common/lifecycle'; import * as Errors from 'vs/base/common/errors'; import { timeout } from 'vs/base/common/async'; @@ -273,11 +273,7 @@ suite('AsyncEmitter', function () { assert.equal(typeof e.waitUntil, 'function'); }); - emitter.fireAsync(thenables => ({ - foo: true, - bar: 1, - waitUntil(t: Promise) { thenables.push(t); } - }), CancellationToken.None); + emitter.fireAsync({ foo: true, bar: 1, }, CancellationToken.None); emitter.dispose(); }); @@ -304,12 +300,7 @@ suite('AsyncEmitter', function () { })); }); - await emitter.fireAsync(thenables => ({ - foo: true, - waitUntil(t) { - thenables.push(t); - } - }), CancellationToken.None); + await emitter.fireAsync({ foo: true }, CancellationToken.None); assert.equal(globalState, 2); }); @@ -325,12 +316,7 @@ suite('AsyncEmitter', function () { emitter.event(e => { e.waitUntil(timeout(10).then(async _ => { if (e.foo === 1) { - await emitter.fireAsync(thenables => ({ - foo: 2, - waitUntil(t) { - thenables.push(t); - } - }), CancellationToken.None); + await emitter.fireAsync({ foo: 2 }, CancellationToken.None); assert.deepEqual(events, [1, 2]); done = true; } @@ -343,12 +329,7 @@ suite('AsyncEmitter', function () { e.waitUntil(timeout(7)); }); - await emitter.fireAsync(thenables => ({ - foo: 1, - waitUntil(t) { - thenables.push(t); - } - }), CancellationToken.None); + await emitter.fireAsync({ foo: 1 }, CancellationToken.None); assert.ok(done); }); @@ -373,12 +354,7 @@ suite('AsyncEmitter', function () { e.waitUntil(timeout(10)); }); - await emitter.fireAsync(thenables => ({ - foo: true, - waitUntil(t) { - thenables.push(t); - } - }), CancellationToken.None).then(() => { + await emitter.fireAsync({ foo: true }, CancellationToken.None).then(() => { assert.equal(globalState, 2); }).catch(e => { console.log(e); diff --git a/src/vs/workbench/api/common/extHostFileSystemEventService.ts b/src/vs/workbench/api/common/extHostFileSystemEventService.ts index 2b38f88a5a380c2c9a70bad58c5be47bac780e97..d289fc249fed95d217b8825072e4dabebbc83b3e 100644 --- a/src/vs/workbench/api/common/extHostFileSystemEventService.ts +++ b/src/vs/workbench/api/common/extHostFileSystemEventService.ts @@ -196,26 +196,14 @@ export class ExtHostFileSystemEventService implements ExtHostFileSystemEventServ private async _fireWillEvent(emitter: AsyncEmitter, data: Omit, token: CancellationToken): Promise { const edits: WorkspaceEdit[] = []; - await Promise.resolve(emitter.fireAsync(bucket => { - return { - ...data, - ...{ - waitUntil: (thenable: Promise): void => { - if (Object.isFrozen(bucket)) { - throw new TypeError('waitUntil cannot be called async'); - } - const promise = Promise.resolve(thenable).then(result => { - // ignore all results except for WorkspaceEdits. Those - // are stored in a spare array - if (result instanceof WorkspaceEdit) { - edits.push(result); - } - }); - bucket.push(promise); - } - } - }; - }, token)); + + await emitter.fireAsync(data, token, async p => { + // ignore all results except for WorkspaceEdits. Those are stored in an array. + const result = await Promise.resolve(p); + if (result instanceof WorkspaceEdit) { + edits.push(result); + } + }); if (token.isCancellationRequested) { return; diff --git a/src/vs/workbench/services/textfile/browser/textFileService.ts b/src/vs/workbench/services/textfile/browser/textFileService.ts index 55e7fded9ba3ee56a0286f5159350e6c9c0355d1..6228f117813650b14a2d785b9c965a2a990ed1fb 100644 --- a/src/vs/workbench/services/textfile/browser/textFileService.ts +++ b/src/vs/workbench/services/textfile/browser/textFileService.ts @@ -345,7 +345,7 @@ export abstract class AbstractTextFileService extends Disposable implements ITex async create(resource: URI, value?: string | ITextSnapshot, options?: ICreateFileOptions): Promise { // before event - await this._onWillRunOperation.fireAsync(promises => new FileOperationWillRunEvent(promises, FileOperation.CREATE, resource), CancellationToken.None); + await this._onWillRunOperation.fireAsync({ operation: FileOperation.CREATE, target: resource }, CancellationToken.None); const stat = await this.doCreate(resource, value, options); @@ -375,7 +375,7 @@ export abstract class AbstractTextFileService extends Disposable implements ITex async delete(resource: URI, options?: { useTrash?: boolean, recursive?: boolean }): Promise { // before event - await this._onWillRunOperation.fireAsync(promises => new FileOperationWillRunEvent(promises, FileOperation.DELETE, resource), CancellationToken.None); + 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 }); @@ -389,7 +389,7 @@ export abstract class AbstractTextFileService extends Disposable implements ITex async move(source: URI, target: URI, overwrite?: boolean): Promise { // before event - await this._onWillRunOperation.fireAsync(promises => new FileOperationWillRunEvent(promises, FileOperation.MOVE, target, source), CancellationToken.None); + await this._onWillRunOperation.fireAsync({ operation: FileOperation.MOVE, target, source }, CancellationToken.None); // find all models that related to either source or target (can be many if resource is a folder) const sourceModels: ITextFileEditorModel[] = []; diff --git a/src/vs/workbench/services/textfile/common/textfiles.ts b/src/vs/workbench/services/textfile/common/textfiles.ts index 51338221b40e8f131ff269ae625acabe1377ed3c..a1383b939013e04eeb4a701d8181d04b501a20cf 100644 --- a/src/vs/workbench/services/textfile/common/textfiles.ts +++ b/src/vs/workbench/services/textfile/common/textfiles.ts @@ -131,21 +131,10 @@ export interface ITextFileService extends IDisposable { move(source: URI, target: URI, overwrite?: boolean): Promise; } -export class FileOperationWillRunEvent implements IWaitUntil { - - constructor( - private _thenables: Promise[], - readonly operation: FileOperation, - readonly target: URI, - readonly source?: URI | undefined - ) { } - - waitUntil(thenable: Promise): void { - if (Object.isFrozen(this._thenables)) { - throw new Error('waitUntil cannot be used aync'); - } - this._thenables.push(thenable); - } +export interface FileOperationWillRunEvent extends IWaitUntil { + operation: FileOperation; + target: URI; + source?: URI; } export class FileOperationDidRunEvent {