Change:
continues Timeout and user-cancellation applies when calling -events. However, we don't expose that in the API and silently stop async-event delivery and ignore results. We could let extension know via
For some big repo, it is possible for LS to parse more then hundreds files (which takes long time), right now, this can't be cancelled since we (LS) don't get any cancellation token (hence, no cancellation request). vscode side acts like it is cancelled, but only front end (vscode) is cancelled, all the backends (LSP and LS) that are actually doing the work are not cancelled. so even if vscode code send new requests (since vscode side is cancelled), LSP/LS won't be able to respond (not all LS is designed to process concurrent requests especially requests that have potential to change LS states). so only front end cancellation won't help much overall experiences.
fyi - this is now a thing and ready for LSP adoption
Neat, thanks
I verified that the token is available on the events, but it wasn't clear to me how to cause a user cancellation of the save/rename/delete. Can this only be done if there's slow file system provider?
You can call of the event with a promise that never or slowly resolves. Eventually the UI shows a progress notification which allows to cancel. That cancel will propagate to the extension.
I have now also verified that the token properly reflects the cancel status.
Thanks!

Discussion:
export interface IWaitUntil { <ins> token: CancellationToken; </ins> waitUntil(thenable: Promise<unknown>): void; } <ins> export type IWaitUntilData<T> = Omit<Omit<T, 'waitUntil'>, 'token'>; </ins> export class AsyncEmitter<T extends IWaitUntil> extends Emitter<T> { <del> private _asyncDeliveryQueue?: LinkedList<[Listener<T>, Omit<T, 'waitUntil'>]>; </del> <ins> private _asyncDeliveryQueue?: LinkedList<[Listener<T>, IWaitUntilData<T>]>; </ins> <del> async fireAsync(data: Omit<T, 'waitUntil'>, token: CancellationToken, promiseJoin?: (p: Promise<unknown>, listener: Function) => Promise<unknown>): Promise<void> { </del> <ins> async fireAsync(data: IWaitUntilData<T>, token: CancellationToken, promiseJoin?: (p: Promise<unknown>, listener: Function) => Promise<unknown>): Promise<void> { </ins> if (!this._listeners) { return; }

const event = <T>{ ...data, <ins> token, </ins> waitUntil: (p: Promise<unknown>): void => { if (Object.isFrozen(thenables)) { throw new Error('waitUntil can NOT be called asynchronous');

*  Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ <del> import { Emitter, Event, AsyncEmitter, IWaitUntil } from 'vs/base/common/event'; </del> <ins> import { Emitter, Event, AsyncEmitter, IWaitUntil, IWaitUntilData } from 'vs/base/common/event'; </ins> import { IRelativePattern, parse } from 'vs/base/common/glob'; import { URI } from 'vs/base/common/uri'; import { ExtHostDocumentsAndEditors } from 'vs/workbench/api/common/extHostDocumentsAndEditors';

return undefined; } <del> private async _fireWillEvent<E extends IWaitUntil>(emitter: AsyncEmitter<E>, data: Omit<E, 'waitUntil'>, timeout: number, token: CancellationToken): Promise<IWillRunFileOperationParticipation | undefined> { </del> <ins> private async _fireWillEvent<E extends IWaitUntil>(emitter: AsyncEmitter<E>, data: IWaitUntilData<E>, timeout: number, token: CancellationToken): Promise<IWillRunFileOperationParticipation | undefined> { </ins> const extensionNames = new Set<string>(); const edits: WorkspaceEdit[] = [];

export interface FileWillCreateEvent { /** <ins> * A cancellation token. */ readonly token: CancellationToken; /** </ins> * The files that are going to be created. */ readonly files: readonly Uri[];

export interface FileWillDeleteEvent { /** <ins> * A cancellation token. */ readonly token: CancellationToken; /** </ins> * The files that are going to be deleted. */ readonly files: readonly Uri[];

export interface FileWillRenameEvent { /** <ins> * A cancellation token. */ readonly token: CancellationToken; /** </ins> * The files that are going to be renamed. */ readonly files: ReadonlyArray<{ readonly oldUri: Uri, readonly newUri: Uri }>;