Path: blob/main/extensions/copilot/src/util/vs/base/common/event.ts
13405 views
//!!! DO NOT modify, this file was COPIED from 'microsoft/vscode'12/*---------------------------------------------------------------------------------------------3* Copyright (c) Microsoft Corporation. All rights reserved.4* Licensed under the MIT License. See License.txt in the project root for license information.5*--------------------------------------------------------------------------------------------*/67import { CancelablePromise } from './async';8import { CancellationToken } from './cancellation';9import { diffSets } from './collections';10import { onUnexpectedError } from './errors';11import { createSingleCallFunction } from './functional';12import { combinedDisposable, Disposable, DisposableMap, DisposableStore, IDisposable, toDisposable } from './lifecycle';13import { LinkedList } from './linkedList';14import { IObservable, IObservableWithChange, IObserver } from './observable';15import { StopWatch } from './stopwatch';16import { MicrotaskDelay } from './symbols';171819// -----------------------------------------------------------------------------------------------------------------------20// Uncomment the next line to print warnings whenever an emitter with listeners is disposed. That is a sign of code smell.21// -----------------------------------------------------------------------------------------------------------------------22const _enableDisposeWithListenerWarning = false23// || Boolean("TRUE") // causes a linter warning so that it cannot be pushed24;252627// -----------------------------------------------------------------------------------------------------------------------28// Uncomment the next line to print warnings whenever a snapshotted event is used repeatedly without cleanup.29// See https://github.com/microsoft/vscode/issues/14285130// -----------------------------------------------------------------------------------------------------------------------31const _enableSnapshotPotentialLeakWarning = false32// || Boolean("TRUE") // causes a linter warning so that it cannot be pushed33;3435/**36* An event with zero or one parameters that can be subscribed to. The event is a function itself.37*/38export interface Event<T> {39(listener: (e: T) => unknown, thisArgs?: any, disposables?: IDisposable[] | DisposableStore): IDisposable;40}4142export namespace Event {43export const None: Event<any> = () => Disposable.None;4445function _addLeakageTraceLogic(options: EmitterOptions) {46if (_enableSnapshotPotentialLeakWarning) {47const { onDidAddListener: origListenerDidAdd } = options;48const stack = Stacktrace.create();49let count = 0;50options.onDidAddListener = () => {51if (++count === 2) {52console.warn('snapshotted emitter LIKELY used public and SHOULD HAVE BEEN created with DisposableStore. snapshotted here');53stack.print();54}55origListenerDidAdd?.();56};57}58}5960/**61* Given an event, returns another event which debounces calls and defers the listeners to a later task via a shared62* `setTimeout`. The event is converted into a signal (`Event<void>`) to avoid additional object creation as a63* result of merging events and to try prevent race conditions that could arise when using related deferred and64* non-deferred events.65*66* This is useful for deferring non-critical work (eg. general UI updates) to ensure it does not block critical work67* (eg. latency of keypress to text rendered).68*69* *NOTE* that this function returns an `Event` and it MUST be called with a `DisposableStore` whenever the returned70* event is accessible to "third parties", e.g the event is a public property. Otherwise a leaked listener on the71* returned event causes this utility to leak a listener on the original event.72*73* @param event The event source for the new event.74* @param flushOnListenerRemove Whether to fire all debounced events when a listener is removed. If this is not75* specified, some events could go missing. Use this if it's important that all events are processed, even if the76* listener gets disposed before the debounced event fires.77* @param disposable A disposable store to add the new EventEmitter to.78*/79export function defer(event: Event<unknown>, flushOnListenerRemove?: boolean, disposable?: DisposableStore): Event<void> {80return debounce<unknown, void>(event, () => void 0, 0, undefined, flushOnListenerRemove ?? true, undefined, disposable);81}8283/**84* Given an event, returns another event which only fires once.85*86* @param event The event source for the new event.87*/88export function once<T>(event: Event<T>): Event<T> {89return (listener, thisArgs = null, disposables?) => {90// we need this, in case the event fires during the listener call91let didFire = false;92let result: IDisposable | undefined = undefined;93result = event(e => {94if (didFire) {95return;96} else if (result) {97result.dispose();98} else {99didFire = true;100}101102return listener.call(thisArgs, e);103}, null, disposables);104105if (didFire) {106result.dispose();107}108109return result;110};111}112113/**114* Given an event, returns another event which only fires once, and only when the condition is met.115*116* @param event The event source for the new event.117*/118export function onceIf<T>(event: Event<T>, condition: (e: T) => boolean): Event<T> {119return Event.once(Event.filter(event, condition));120}121122/**123* Maps an event of one type into an event of another type using a mapping function, similar to how124* `Array.prototype.map` works.125*126* *NOTE* that this function returns an `Event` and it MUST be called with a `DisposableStore` whenever the returned127* event is accessible to "third parties", e.g the event is a public property. Otherwise a leaked listener on the128* returned event causes this utility to leak a listener on the original event.129*130* @param event The event source for the new event.131* @param map The mapping function.132* @param disposable A disposable store to add the new EventEmitter to.133*/134export function map<I, O>(event: Event<I>, map: (i: I) => O, disposable?: DisposableStore): Event<O> {135return snapshot((listener, thisArgs = null, disposables?) => event(i => listener.call(thisArgs, map(i)), null, disposables), disposable);136}137138/**139* Wraps an event in another event that performs some function on the event object before firing.140*141* *NOTE* that this function returns an `Event` and it MUST be called with a `DisposableStore` whenever the returned142* event is accessible to "third parties", e.g the event is a public property. Otherwise a leaked listener on the143* returned event causes this utility to leak a listener on the original event.144*145* @param event The event source for the new event.146* @param each The function to perform on the event object.147* @param disposable A disposable store to add the new EventEmitter to.148*/149export function forEach<I>(event: Event<I>, each: (i: I) => void, disposable?: DisposableStore): Event<I> {150return snapshot((listener, thisArgs = null, disposables?) => event(i => { each(i); listener.call(thisArgs, i); }, null, disposables), disposable);151}152153/**154* Wraps an event in another event that fires only when some condition is met.155*156* *NOTE* that this function returns an `Event` and it MUST be called with a `DisposableStore` whenever the returned157* event is accessible to "third parties", e.g the event is a public property. Otherwise a leaked listener on the158* returned event causes this utility to leak a listener on the original event.159*160* @param event The event source for the new event.161* @param filter The filter function that defines the condition. The event will fire for the object if this function162* returns true.163* @param disposable A disposable store to add the new EventEmitter to.164*/165export function filter<T, U>(event: Event<T | U>, filter: (e: T | U) => e is T, disposable?: DisposableStore): Event<T>;166export function filter<T>(event: Event<T>, filter: (e: T) => boolean, disposable?: DisposableStore): Event<T>;167export function filter<T, R>(event: Event<T | R>, filter: (e: T | R) => e is R, disposable?: DisposableStore): Event<R>;168export function filter<T>(event: Event<T>, filter: (e: T) => boolean, disposable?: DisposableStore): Event<T> {169return snapshot((listener, thisArgs = null, disposables?) => event(e => filter(e) && listener.call(thisArgs, e), null, disposables), disposable);170}171172/**173* Given an event, returns the same event but typed as `Event<void>`.174*/175export function signal<T>(event: Event<T>): Event<void> {176return event as Event<any> as Event<void>;177}178179/**180* Given a collection of events, returns a single event which emits whenever any of the provided events emit.181*/182export function any<T>(...events: Event<T>[]): Event<T>;183export function any(...events: Event<any>[]): Event<void>;184export function any<T>(...events: Event<T>[]): Event<T> {185return (listener, thisArgs = null, disposables?) => {186const disposable = combinedDisposable(...events.map(event => event(e => listener.call(thisArgs, e))));187return addAndReturnDisposable(disposable, disposables);188};189}190191/**192* *NOTE* that this function returns an `Event` and it MUST be called with a `DisposableStore` whenever the returned193* event is accessible to "third parties", e.g the event is a public property. Otherwise a leaked listener on the194* returned event causes this utility to leak a listener on the original event.195*/196export function reduce<I, O>(event: Event<I>, merge: (last: O | undefined, event: I) => O, initial?: O, disposable?: DisposableStore): Event<O> {197let output: O | undefined = initial;198199return map<I, O>(event, e => {200output = merge(output, e);201return output;202}, disposable);203}204205function snapshot<T>(event: Event<T>, disposable: DisposableStore | undefined): Event<T> {206let listener: IDisposable | undefined;207208const options: EmitterOptions | undefined = {209onWillAddFirstListener() {210listener = event(emitter.fire, emitter);211},212onDidRemoveLastListener() {213listener?.dispose();214}215};216217if (!disposable) {218_addLeakageTraceLogic(options);219}220221const emitter = new Emitter<T>(options);222223disposable?.add(emitter);224225return emitter.event;226}227228/**229* Adds the IDisposable to the store if it's set, and returns it. Useful to230* Event function implementation.231*/232function addAndReturnDisposable<T extends IDisposable>(d: T, store: DisposableStore | IDisposable[] | undefined): T {233if (store instanceof Array) {234store.push(d);235} else if (store) {236store.add(d);237}238return d;239}240241/**242* Given an event, creates a new emitter that event that will debounce events based on {@link delay} and give an243* array event object of all events that fired.244*245* *NOTE* that this function returns an `Event` and it MUST be called with a `DisposableStore` whenever the returned246* event is accessible to "third parties", e.g the event is a public property. Otherwise a leaked listener on the247* returned event causes this utility to leak a listener on the original event.248*249* @param event The original event to debounce.250* @param merge A function that reduces all events into a single event.251* @param delay The number of milliseconds to debounce.252* @param leading Whether to fire a leading event without debouncing.253* @param flushOnListenerRemove Whether to fire all debounced events when a listener is removed. If this is not254* specified, some events could go missing. Use this if it's important that all events are processed, even if the255* listener gets disposed before the debounced event fires.256* @param leakWarningThreshold See {@link EmitterOptions.leakWarningThreshold}.257* @param disposable A disposable store to register the debounce emitter to.258*/259export function debounce<T>(event: Event<T>, merge: (last: T | undefined, event: T) => T, delay?: number | typeof MicrotaskDelay, leading?: boolean, flushOnListenerRemove?: boolean, leakWarningThreshold?: number, disposable?: DisposableStore): Event<T>;260export function debounce<I, O>(event: Event<I>, merge: (last: O | undefined, event: I) => O, delay?: number | typeof MicrotaskDelay, leading?: boolean, flushOnListenerRemove?: boolean, leakWarningThreshold?: number, disposable?: DisposableStore): Event<O>;261export function debounce<I, O>(event: Event<I>, merge: (last: O | undefined, event: I) => O, delay: number | typeof MicrotaskDelay = 100, leading = false, flushOnListenerRemove = false, leakWarningThreshold?: number, disposable?: DisposableStore): Event<O> {262let subscription: IDisposable;263let output: O | undefined = undefined;264let handle: Timeout | undefined | null = undefined;265let numDebouncedCalls = 0;266let doFire: (() => void) | undefined;267268const options: EmitterOptions | undefined = {269leakWarningThreshold,270onWillAddFirstListener() {271subscription = event(cur => {272numDebouncedCalls++;273output = merge(output, cur);274275if (leading && !handle) {276emitter.fire(output);277output = undefined;278}279280doFire = () => {281const _output = output;282output = undefined;283handle = undefined;284if (!leading || numDebouncedCalls > 1) {285emitter.fire(_output!);286}287numDebouncedCalls = 0;288};289290if (typeof delay === 'number') {291if (handle) {292clearTimeout(handle);293}294handle = setTimeout(doFire, delay);295} else {296if (handle === undefined) {297handle = null;298queueMicrotask(doFire);299}300}301});302},303onWillRemoveListener() {304if (flushOnListenerRemove && numDebouncedCalls > 0) {305doFire?.();306}307},308onDidRemoveLastListener() {309doFire = undefined;310subscription.dispose();311}312};313314if (!disposable) {315_addLeakageTraceLogic(options);316}317318const emitter = new Emitter<O>(options);319320disposable?.add(emitter);321322return emitter.event;323}324325/**326* Debounces an event, firing after some delay (default=0) with an array of all event original objects.327*328* *NOTE* that this function returns an `Event` and it MUST be called with a `DisposableStore` whenever the returned329* event is accessible to "third parties", e.g the event is a public property. Otherwise a leaked listener on the330* returned event causes this utility to leak a listener on the original event.331*332* @param event The event source for the new event.333* @param delay The number of milliseconds to debounce.334* @param flushOnListenerRemove Whether to fire all debounced events when a listener is removed. If this is not335* specified, some events could go missing. Use this if it's important that all events are processed, even if the336* listener gets disposed before the debounced event fires.337* @param disposable A disposable store to add the new EventEmitter to.338*/339export function accumulate<T>(event: Event<T>, delay: number | typeof MicrotaskDelay = 0, flushOnListenerRemove?: boolean, disposable?: DisposableStore): Event<T[]> {340return Event.debounce<T, T[]>(event, (last, e) => {341if (!last) {342return [e];343}344last.push(e);345return last;346}, delay, undefined, flushOnListenerRemove ?? true, undefined, disposable);347}348349/**350* Throttles an event, ensuring the event is fired at most once during the specified delay period.351* Unlike debounce, throttle will fire immediately on the leading edge and/or after the delay on the trailing edge.352*353* *NOTE* that this function returns an `Event` and it MUST be called with a `DisposableStore` whenever the returned354* event is accessible to "third parties", e.g the event is a public property. Otherwise a leaked listener on the355* returned event causes this utility to leak a listener on the original event.356*357* @param event The event source for the new event.358* @param merge An accumulator function that merges events if multiple occur during the throttle period.359* @param delay The number of milliseconds to throttle.360* @param leading Whether to fire on the leading edge (immediately on first event).361* @param trailing Whether to fire on the trailing edge (after delay with the last value).362* @param leakWarningThreshold See {@link EmitterOptions.leakWarningThreshold}.363* @param disposable A disposable store to register the throttle emitter to.364*/365export function throttle<T>(event: Event<T>, merge: (last: T | undefined, event: T) => T, delay?: number | typeof MicrotaskDelay, leading?: boolean, trailing?: boolean, leakWarningThreshold?: number, disposable?: DisposableStore): Event<T>;366export function throttle<I, O>(event: Event<I>, merge: (last: O | undefined, event: I) => O, delay?: number | typeof MicrotaskDelay, leading?: boolean, trailing?: boolean, leakWarningThreshold?: number, disposable?: DisposableStore): Event<O>;367export function throttle<I, O>(event: Event<I>, merge: (last: O | undefined, event: I) => O, delay: number | typeof MicrotaskDelay = 100, leading = true, trailing = true, leakWarningThreshold?: number, disposable?: DisposableStore): Event<O> {368let subscription: IDisposable;369let output: O | undefined = undefined;370let handle: Timeout | undefined = undefined;371let numThrottledCalls = 0;372373const options: EmitterOptions | undefined = {374leakWarningThreshold,375onWillAddFirstListener() {376subscription = event(cur => {377numThrottledCalls++;378output = merge(output, cur);379380// If not currently throttling, fire immediately if leading is enabled381if (handle === undefined) {382if (leading) {383emitter.fire(output);384output = undefined;385numThrottledCalls = 0;386}387388// Set up the throttle period389if (typeof delay === 'number') {390handle = setTimeout(() => {391// Fire on trailing edge if there were calls during throttle period392if (trailing && numThrottledCalls > 0) {393emitter.fire(output!);394}395output = undefined;396handle = undefined;397numThrottledCalls = 0;398}, delay);399} else {400// Use a special marker to indicate microtask is pending401handle = 0 as unknown as Timeout;402queueMicrotask(() => {403// Fire on trailing edge if there were calls during throttle period404if (trailing && numThrottledCalls > 0) {405emitter.fire(output!);406}407output = undefined;408handle = undefined;409numThrottledCalls = 0;410});411}412}413// If already throttling, just accumulate the value for trailing edge414});415},416onDidRemoveLastListener() {417subscription.dispose();418}419};420421if (!disposable) {422_addLeakageTraceLogic(options);423}424425const emitter = new Emitter<O>(options);426427disposable?.add(emitter);428429return emitter.event;430}431432/**433* Filters an event such that some condition is _not_ met more than once in a row, effectively ensuring duplicate434* event objects from different sources do not fire the same event object.435*436* *NOTE* that this function returns an `Event` and it MUST be called with a `DisposableStore` whenever the returned437* event is accessible to "third parties", e.g the event is a public property. Otherwise a leaked listener on the438* returned event causes this utility to leak a listener on the original event.439*440* @param event The event source for the new event.441* @param equals The equality condition.442* @param disposable A disposable store to add the new EventEmitter to.443*444* @example445* ```446* // Fire only one time when a single window is opened or focused447* Event.latch(Event.any(onDidOpenWindow, onDidFocusWindow))448* ```449*/450export function latch<T>(event: Event<T>, equals: (a: T, b: T) => boolean = (a, b) => a === b, disposable?: DisposableStore): Event<T> {451let firstCall = true;452let cache: T;453454return filter(event, value => {455const shouldEmit = firstCall || !equals(value, cache);456firstCall = false;457cache = value;458return shouldEmit;459}, disposable);460}461462/**463* Splits an event whose parameter is a union type into 2 separate events for each type in the union.464*465* *NOTE* that this function returns an `Event` and it MUST be called with a `DisposableStore` whenever the returned466* event is accessible to "third parties", e.g the event is a public property. Otherwise a leaked listener on the467* returned event causes this utility to leak a listener on the original event.468*469* @example470* ```471* const event = new EventEmitter<number | undefined>().event;472* const [numberEvent, undefinedEvent] = Event.split(event, isUndefined);473* ```474*475* @param event The event source for the new event.476* @param isT A function that determines what event is of the first type.477* @param disposable A disposable store to add the new EventEmitter to.478*/479export function split<T, U>(event: Event<T | U>, isT: (e: T | U) => e is T, disposable?: DisposableStore): [Event<T>, Event<U>] {480return [481Event.filter(event, isT, disposable),482Event.filter(event, e => !isT(e), disposable) as Event<U>,483];484}485486/**487* Buffers an event until it has a listener attached.488*489* *NOTE* that this function returns an `Event` and it MUST be called with a `DisposableStore` whenever the returned490* event is accessible to "third parties", e.g the event is a public property. Otherwise a leaked listener on the491* returned event causes this utility to leak a listener on the original event.492*493* @param event The event source for the new event.494* @param flushAfterTimeout Determines whether to flush the buffer after a timeout immediately or after a495* `setTimeout` when the first event listener is added.496* @param _buffer Internal: A source event array used for tests.497*498* @example499* ```500* // Start accumulating events, when the first listener is attached, flush501* // the event after a timeout such that multiple listeners attached before502* // the timeout would receive the event503* this.onInstallExtension = Event.buffer(service.onInstallExtension, true);504* ```505*/506export function buffer<T>(event: Event<T>, flushAfterTimeout = false, _buffer: T[] = [], disposable?: DisposableStore): Event<T> {507let buffer: T[] | null = _buffer.slice();508509let listener: IDisposable | null = event(e => {510if (buffer) {511buffer.push(e);512} else {513emitter.fire(e);514}515});516517if (disposable) {518disposable.add(listener);519}520521const flush = () => {522buffer?.forEach(e => emitter.fire(e));523buffer = null;524};525526const emitter = new Emitter<T>({527onWillAddFirstListener() {528if (!listener) {529listener = event(e => emitter.fire(e));530if (disposable) {531disposable.add(listener);532}533}534},535536onDidAddFirstListener() {537if (buffer) {538if (flushAfterTimeout) {539setTimeout(flush);540} else {541flush();542}543}544},545546onDidRemoveLastListener() {547if (listener) {548listener.dispose();549}550listener = null;551}552});553554if (disposable) {555disposable.add(emitter);556}557558return emitter.event;559}560/**561* Wraps the event in an {@link IChainableEvent}, allowing a more functional programming style.562*563* @example564* ```565* // Normal566* const onEnterPressNormal = Event.filter(567* Event.map(onKeyPress.event, e => new StandardKeyboardEvent(e)),568* e.keyCode === KeyCode.Enter569* ).event;570*571* // Using chain572* const onEnterPressChain = Event.chain(onKeyPress.event, $ => $573* .map(e => new StandardKeyboardEvent(e))574* .filter(e => e.keyCode === KeyCode.Enter)575* );576* ```577*/578export function chain<T, R>(event: Event<T>, sythensize: ($: IChainableSythensis<T>) => IChainableSythensis<R>): Event<R> {579const fn: Event<R> = (listener, thisArgs, disposables) => {580const cs = sythensize(new ChainableSynthesis()) as ChainableSynthesis;581return event(function (value) {582const result = cs.evaluate(value);583if (result !== HaltChainable) {584listener.call(thisArgs, result);585}586}, undefined, disposables);587};588589return fn;590}591592const HaltChainable = Symbol('HaltChainable');593594class ChainableSynthesis implements IChainableSythensis<any> {595private readonly steps: ((input: any) => unknown)[] = [];596597map<O>(fn: (i: any) => O): this {598this.steps.push(fn);599return this;600}601602forEach(fn: (i: any) => void): this {603this.steps.push(v => {604fn(v);605return v;606});607return this;608}609610filter(fn: (e: any) => boolean): this {611this.steps.push(v => fn(v) ? v : HaltChainable);612return this;613}614615reduce<R>(merge: (last: R | undefined, event: any) => R, initial?: R | undefined): this {616let last = initial;617this.steps.push(v => {618last = merge(last, v);619return last;620});621return this;622}623624latch(equals: (a: any, b: any) => boolean = (a, b) => a === b): ChainableSynthesis {625let firstCall = true;626let cache: any;627this.steps.push(value => {628const shouldEmit = firstCall || !equals(value, cache);629firstCall = false;630cache = value;631return shouldEmit ? value : HaltChainable;632});633634return this;635}636637public evaluate(value: any) {638for (const step of this.steps) {639value = step(value);640if (value === HaltChainable) {641break;642}643}644645return value;646}647}648649export interface IChainableSythensis<T> {650map<O>(fn: (i: T) => O): IChainableSythensis<O>;651forEach(fn: (i: T) => void): IChainableSythensis<T>;652filter<R extends T>(fn: (e: T) => e is R): IChainableSythensis<R>;653filter(fn: (e: T) => boolean): IChainableSythensis<T>;654reduce<R>(merge: (last: R, event: T) => R, initial: R): IChainableSythensis<R>;655reduce<R>(merge: (last: R | undefined, event: T) => R): IChainableSythensis<R>;656latch(equals?: (a: T, b: T) => boolean): IChainableSythensis<T>;657}658659export interface NodeEventEmitter {660on(event: string | symbol, listener: Function): unknown;661removeListener(event: string | symbol, listener: Function): unknown;662}663664/**665* Creates an {@link Event} from a node event emitter.666*/667export function fromNodeEventEmitter<T>(emitter: NodeEventEmitter, eventName: string, map: (...args: any[]) => T = id => id): Event<T> {668const fn = (...args: any[]) => result.fire(map(...args));669const onFirstListenerAdd = () => emitter.on(eventName, fn);670const onLastListenerRemove = () => emitter.removeListener(eventName, fn);671const result = new Emitter<T>({ onWillAddFirstListener: onFirstListenerAdd, onDidRemoveLastListener: onLastListenerRemove });672673return result.event;674}675676export interface DOMEventEmitter {677addEventListener(event: string | symbol, listener: Function): void;678removeEventListener(event: string | symbol, listener: Function): void;679}680681/**682* Creates an {@link Event} from a DOM event emitter.683*/684export function fromDOMEventEmitter<T>(emitter: DOMEventEmitter, eventName: string, map: (...args: any[]) => T = id => id): Event<T> {685const fn = (...args: any[]) => result.fire(map(...args));686const onFirstListenerAdd = () => emitter.addEventListener(eventName, fn);687const onLastListenerRemove = () => emitter.removeEventListener(eventName, fn);688const result = new Emitter<T>({ onWillAddFirstListener: onFirstListenerAdd, onDidRemoveLastListener: onLastListenerRemove });689690return result.event;691}692693/**694* Creates a promise out of an event, using the {@link Event.once} helper.695*/696export function toPromise<T>(event: Event<T>, disposables?: IDisposable[] | DisposableStore): CancelablePromise<T> {697let cancelRef: () => void;698let listener: IDisposable;699const promise = new Promise((resolve) => {700listener = once(event)(resolve);701addToDisposables(listener, disposables);702703// not resolved, matching the behavior of a normal disposal704cancelRef = () => {705disposeAndRemove(listener, disposables);706};707}) as CancelablePromise<T>;708promise.cancel = cancelRef!;709710if (disposables) {711promise.finally(() => disposeAndRemove(listener, disposables));712}713714return promise;715}716717/**718* A convenience function for forwarding an event to another emitter which719* improves readability.720*721* This is similar to {@link Relay} but allows instantiating and forwarding722* on a single line and also allows for multiple source events.723* @param from The event to forward.724* @param to The emitter to forward the event to.725* @example726* Event.forward(event, emitter);727* // equivalent to728* event(e => emitter.fire(e));729* // equivalent to730* event(emitter.fire, emitter);731*/732export function forward<T>(from: Event<T>, to: Emitter<T>): IDisposable {733return from(e => to.fire(e));734}735736/**737* Adds a listener to an event and calls the listener immediately with undefined as the event object.738*739* @example740* ```741* // Initialize the UI and update it when dataChangeEvent fires742* runAndSubscribe(dataChangeEvent, () => this._updateUI());743* ```744*/745export function runAndSubscribe<T>(event: Event<T>, handler: (e: T) => unknown, initial: T): IDisposable;746export function runAndSubscribe<T>(event: Event<T>, handler: (e: T | undefined) => unknown): IDisposable;747export function runAndSubscribe<T>(event: Event<T>, handler: (e: T | undefined) => unknown, initial?: T): IDisposable {748handler(initial);749return event(e => handler(e));750}751752class EmitterObserver<T> implements IObserver {753754readonly emitter: Emitter<T>;755756private _counter = 0;757private _hasChanged = false;758759constructor(readonly _observable: IObservable<T>, store: DisposableStore | undefined) {760const options: EmitterOptions = {761onWillAddFirstListener: () => {762_observable.addObserver(this);763764// Communicate to the observable that we received its current value and would like to be notified about future changes.765this._observable.reportChanges();766},767onDidRemoveLastListener: () => {768_observable.removeObserver(this);769}770};771if (!store) {772_addLeakageTraceLogic(options);773}774this.emitter = new Emitter<T>(options);775if (store) {776store.add(this.emitter);777}778}779780beginUpdate<T>(_observable: IObservable<T>): void {781// assert(_observable === this.obs);782this._counter++;783}784785handlePossibleChange<T>(_observable: IObservable<T>): void {786// assert(_observable === this.obs);787}788789handleChange<T, TChange>(_observable: IObservableWithChange<T, TChange>, _change: TChange): void {790// assert(_observable === this.obs);791this._hasChanged = true;792}793794endUpdate<T>(_observable: IObservable<T>): void {795// assert(_observable === this.obs);796this._counter--;797if (this._counter === 0) {798this._observable.reportChanges();799if (this._hasChanged) {800this._hasChanged = false;801this.emitter.fire(this._observable.get());802}803}804}805}806807/**808* Creates an event emitter that is fired when the observable changes.809* Each listeners subscribes to the emitter.810*/811export function fromObservable<T>(obs: IObservable<T>, store?: DisposableStore): Event<T> {812const observer = new EmitterObserver(obs, store);813return observer.emitter.event;814}815816/**817* Each listener is attached to the observable directly.818*/819export function fromObservableLight(observable: IObservable<unknown>): Event<void> {820return (listener, thisArgs, disposables) => {821let count = 0;822let didChange = false;823const observer: IObserver = {824beginUpdate() {825count++;826},827endUpdate() {828count--;829if (count === 0) {830observable.reportChanges();831if (didChange) {832didChange = false;833listener.call(thisArgs);834}835}836},837handlePossibleChange() {838// noop839},840handleChange() {841didChange = true;842}843};844observable.addObserver(observer);845observable.reportChanges();846const disposable = {847dispose() {848observable.removeObserver(observer);849}850};851852addToDisposables(disposable, disposables);853854return disposable;855};856}857}858859export interface EmitterOptions {860/**861* Optional function that's called *before* the very first listener is added862*/863onWillAddFirstListener?: Function;864/**865* Optional function that's called *after* the very first listener is added866*/867onDidAddFirstListener?: Function;868/**869* Optional function that's called after a listener is added870*/871onDidAddListener?: Function;872/**873* Optional function that's called *after* remove the very last listener874*/875onDidRemoveLastListener?: Function;876/**877* Optional function that's called *before* a listener is removed878*/879onWillRemoveListener?: Function;880/**881* Optional function that's called when a listener throws an error. Defaults to882* {@link onUnexpectedError}883*/884onListenerError?: (e: any) => void;885/**886* Number of listeners that are allowed before assuming a leak. Default to887* a globally configured value888*889* @see setGlobalLeakWarningThreshold890*/891leakWarningThreshold?: number;892/**893* Pass in a delivery queue, which is useful for ensuring894* in order event delivery across multiple emitters.895*/896deliveryQueue?: EventDeliveryQueue;897898/** ONLY enable this during development */899_profName?: string;900}901902903export class EventProfiling {904905static readonly all = new Set<EventProfiling>();906907private static _idPool = 0;908909readonly name: string;910public listenerCount: number = 0;911public invocationCount = 0;912public elapsedOverall = 0;913public durations: number[] = [];914915private _stopWatch?: StopWatch;916917constructor(name: string) {918this.name = `${name}_${EventProfiling._idPool++}`;919EventProfiling.all.add(this);920}921922start(listenerCount: number): void {923this._stopWatch = new StopWatch();924this.listenerCount = listenerCount;925}926927stop(): void {928if (this._stopWatch) {929const elapsed = this._stopWatch.elapsed();930this.durations.push(elapsed);931this.elapsedOverall += elapsed;932this.invocationCount += 1;933this._stopWatch = undefined;934}935}936}937938let _globalLeakWarningThreshold = -1;939export function setGlobalLeakWarningThreshold(n: number): IDisposable {940const oldValue = _globalLeakWarningThreshold;941_globalLeakWarningThreshold = n;942return {943dispose() {944_globalLeakWarningThreshold = oldValue;945}946};947}948949class LeakageMonitor {950951private static _idPool = 1;952953private _stacks: Map<string, number> | undefined;954private _warnCountdown: number = 0;955956constructor(957private readonly _errorHandler: (err: Error) => void,958readonly threshold: number,959readonly name: string = (LeakageMonitor._idPool++).toString(16).padStart(3, '0')960) { }961962dispose(): void {963this._stacks?.clear();964}965966check(stack: Stacktrace, listenerCount: number): undefined | (() => void) {967968const threshold = this.threshold;969if (threshold <= 0 || listenerCount < threshold) {970return undefined;971}972973if (!this._stacks) {974this._stacks = new Map();975}976const count = (this._stacks.get(stack.value) || 0);977this._stacks.set(stack.value, count + 1);978this._warnCountdown -= 1;979980if (this._warnCountdown <= 0) {981// only warn on first exceed and then every time the limit982// is exceeded by 50% again983this._warnCountdown = threshold * 0.5;984985const [topStack, topCount] = this.getMostFrequentStack()!;986const message = `[${this.name}] potential listener LEAK detected, having ${listenerCount} listeners already. MOST frequent listener (${topCount}):`;987console.warn(message);988console.warn(topStack);989990const error = new ListenerLeakError(message, topStack);991this._errorHandler(error);992}993994return () => {995const count = (this._stacks!.get(stack.value) || 0);996this._stacks!.set(stack.value, count - 1);997};998}9991000getMostFrequentStack(): [string, number] | undefined {1001if (!this._stacks) {1002return undefined;1003}1004let topStack: [string, number] | undefined;1005let topCount: number = 0;1006for (const [stack, count] of this._stacks) {1007if (!topStack || topCount < count) {1008topStack = [stack, count];1009topCount = count;1010}1011}1012return topStack;1013}1014}10151016class Stacktrace {10171018static create() {1019const err = new Error();1020return new Stacktrace(err.stack ?? '');1021}10221023private constructor(readonly value: string) { }10241025print() {1026console.warn(this.value.split('\n').slice(2).join('\n'));1027}1028}10291030// error that is logged when going over the configured listener threshold1031export class ListenerLeakError extends Error {1032constructor(message: string, stack: string) {1033super(message);1034this.name = 'ListenerLeakError';1035this.stack = stack;1036}1037}10381039// SEVERE error that is logged when having gone way over the configured listener1040// threshold so that the emitter refuses to accept more listeners1041export class ListenerRefusalError extends Error {1042constructor(message: string, stack: string) {1043super(message);1044this.name = 'ListenerRefusalError';1045this.stack = stack;1046}1047}10481049let id = 0;1050class UniqueContainer<T> {1051stack?: Stacktrace;1052public id = id++;1053constructor(public readonly value: T) { }1054}1055const compactionThreshold = 2;10561057type ListenerContainer<T> = UniqueContainer<(data: T) => void>;1058type ListenerOrListeners<T> = (ListenerContainer<T> | undefined)[] | ListenerContainer<T>;10591060const forEachListener = <T>(listeners: ListenerOrListeners<T>, fn: (c: ListenerContainer<T>) => void) => {1061if (listeners instanceof UniqueContainer) {1062fn(listeners);1063} else {1064for (let i = 0; i < listeners.length; i++) {1065const l = listeners[i];1066if (l) {1067fn(l);1068}1069}1070}1071};10721073/**1074* The Emitter can be used to expose an Event to the public1075* to fire it from the insides.1076* Sample:1077class Document {10781079private readonly _onDidChange = new Emitter<(value:string)=>any>();10801081public onDidChange = this._onDidChange.event;10821083// getter-style1084// get onDidChange(): Event<(value:string)=>any> {1085// return this._onDidChange.event;1086// }10871088private _doIt() {1089//...1090this._onDidChange.fire(value);1091}1092}1093*/1094export class Emitter<T> {10951096private readonly _options?: EmitterOptions;1097private readonly _leakageMon?: LeakageMonitor;1098private readonly _perfMon?: EventProfiling;1099private _disposed?: true;1100private _event?: Event<T>;11011102/**1103* A listener, or list of listeners. A single listener is the most common1104* for event emitters (#185789), so we optimize that special case to avoid1105* wrapping it in an array (just like Node.js itself.)1106*1107* A list of listeners never 'downgrades' back to a plain function if1108* listeners are removed, for two reasons:1109*1110* 1. That's complicated (especially with the deliveryQueue)1111* 2. A listener with >1 listener is likely to have >1 listener again at1112* some point, and swapping between arrays and functions may[citation needed]1113* introduce unnecessary work and garbage.1114*1115* The array listeners can be 'sparse', to avoid reallocating the array1116* whenever any listener is added or removed. If more than `1 / compactionThreshold`1117* of the array is empty, only then is it resized.1118*/1119protected _listeners?: ListenerOrListeners<T>;11201121/**1122* Always to be defined if _listeners is an array. It's no longer a true1123* queue, but holds the dispatching 'state'. If `fire()` is called on an1124* emitter, any work left in the _deliveryQueue is finished first.1125*/1126private _deliveryQueue?: EventDeliveryQueuePrivate;1127protected _size = 0;11281129constructor(options?: EmitterOptions) {1130this._options = options;1131this._leakageMon = (_globalLeakWarningThreshold > 0 || this._options?.leakWarningThreshold)1132? new LeakageMonitor(options?.onListenerError ?? onUnexpectedError, this._options?.leakWarningThreshold ?? _globalLeakWarningThreshold) :1133undefined;1134this._perfMon = this._options?._profName ? new EventProfiling(this._options._profName) : undefined;1135this._deliveryQueue = this._options?.deliveryQueue as EventDeliveryQueuePrivate | undefined;1136}11371138dispose() {1139if (!this._disposed) {1140this._disposed = true;11411142// It is bad to have listeners at the time of disposing an emitter, it is worst to have listeners keep the emitter1143// alive via the reference that's embedded in their disposables. Therefore we loop over all remaining listeners and1144// unset their subscriptions/disposables. Looping and blaming remaining listeners is done on next tick because the1145// the following programming pattern is very popular:1146//1147// const someModel = this._disposables.add(new ModelObject()); // (1) create and register model1148// this._disposables.add(someModel.onDidChange(() => { ... }); // (2) subscribe and register model-event listener1149// ...later...1150// this._disposables.dispose(); disposes (1) then (2): don't warn after (1) but after the "overall dispose" is done11511152if (this._deliveryQueue?.current === this) {1153this._deliveryQueue.reset();1154}1155if (this._listeners) {1156if (_enableDisposeWithListenerWarning) {1157const listeners = this._listeners;1158queueMicrotask(() => {1159forEachListener(listeners, l => l.stack?.print());1160});1161}11621163this._listeners = undefined;1164this._size = 0;1165}1166this._options?.onDidRemoveLastListener?.();1167this._leakageMon?.dispose();1168}1169}11701171/**1172* For the public to allow to subscribe1173* to events from this Emitter1174*/1175get event(): Event<T> {1176this._event ??= (callback: (e: T) => unknown, thisArgs?: any, disposables?: IDisposable[] | DisposableStore) => {1177if (this._leakageMon && this._size > this._leakageMon.threshold ** 2) {1178const message = `[${this._leakageMon.name}] REFUSES to accept new listeners because it exceeded its threshold by far (${this._size} vs ${this._leakageMon.threshold})`;1179console.warn(message);11801181const tuple = this._leakageMon.getMostFrequentStack() ?? ['UNKNOWN stack', -1];1182const error = new ListenerRefusalError(`${message}. HINT: Stack shows most frequent listener (${tuple[1]}-times)`, tuple[0]);1183const errorHandler = this._options?.onListenerError || onUnexpectedError;1184errorHandler(error);11851186return Disposable.None;1187}11881189if (this._disposed) {1190// todo: should we warn if a listener is added to a disposed emitter? This happens often1191return Disposable.None;1192}11931194if (thisArgs) {1195callback = callback.bind(thisArgs);1196}11971198const contained = new UniqueContainer(callback);11991200let removeMonitor: Function | undefined;1201let stack: Stacktrace | undefined;1202if (this._leakageMon && this._size >= Math.ceil(this._leakageMon.threshold * 0.2)) {1203// check and record this emitter for potential leakage1204contained.stack = Stacktrace.create();1205removeMonitor = this._leakageMon.check(contained.stack, this._size + 1);1206}12071208if (_enableDisposeWithListenerWarning) {1209contained.stack = stack ?? Stacktrace.create();1210}12111212if (!this._listeners) {1213this._options?.onWillAddFirstListener?.(this);1214this._listeners = contained;1215this._options?.onDidAddFirstListener?.(this);1216} else if (this._listeners instanceof UniqueContainer) {1217this._deliveryQueue ??= new EventDeliveryQueuePrivate();1218this._listeners = [this._listeners, contained];1219} else {1220this._listeners.push(contained);1221}1222this._options?.onDidAddListener?.(this);12231224this._size++;122512261227const result = toDisposable(() => {1228removeMonitor?.();1229this._removeListener(contained);1230});1231addToDisposables(result, disposables);12321233return result;1234};12351236return this._event;1237}12381239private _removeListener(listener: ListenerContainer<T>) {1240this._options?.onWillRemoveListener?.(this);12411242if (!this._listeners) {1243return; // expected if a listener gets disposed1244}12451246if (this._size === 1) {1247this._listeners = undefined;1248this._options?.onDidRemoveLastListener?.(this);1249this._size = 0;1250return;1251}12521253// size > 1 which requires that listeners be a list:1254const listeners = this._listeners as (ListenerContainer<T> | undefined)[];12551256const index = listeners.indexOf(listener);1257if (index === -1) {1258console.log('disposed?', this._disposed);1259console.log('size?', this._size);1260console.log('arr?', JSON.stringify(this._listeners));1261throw new Error('Attempted to dispose unknown listener');1262}12631264this._size--;1265listeners[index] = undefined;12661267const adjustDeliveryQueue = this._deliveryQueue!.current === this;1268if (this._size * compactionThreshold <= listeners.length) {1269let n = 0;1270for (let i = 0; i < listeners.length; i++) {1271if (listeners[i]) {1272listeners[n++] = listeners[i];1273} else if (adjustDeliveryQueue && n < this._deliveryQueue!.end) {1274this._deliveryQueue!.end--;1275if (n < this._deliveryQueue!.i) {1276this._deliveryQueue!.i--;1277}1278}1279}1280listeners.length = n;1281}1282}12831284private _deliver(listener: undefined | UniqueContainer<(value: T) => void>, value: T) {1285if (!listener) {1286return;1287}12881289const errorHandler = this._options?.onListenerError || onUnexpectedError;1290if (!errorHandler) {1291listener.value(value);1292return;1293}12941295try {1296listener.value(value);1297} catch (e) {1298errorHandler(e);1299}1300}13011302/** Delivers items in the queue. Assumes the queue is ready to go. */1303private _deliverQueue(dq: EventDeliveryQueuePrivate) {1304const listeners = dq.current!._listeners! as (ListenerContainer<T> | undefined)[];1305while (dq.i < dq.end) {1306// important: dq.i is incremented before calling deliver() because it might reenter deliverQueue()1307this._deliver(listeners[dq.i++], dq.value as T);1308}1309dq.reset();1310}13111312/**1313* To be kept private to fire an event to1314* subscribers1315*/1316fire(event: T): void {1317if (this._deliveryQueue?.current) {1318this._deliverQueue(this._deliveryQueue);1319this._perfMon?.stop(); // last fire() will have starting perfmon, stop it before starting the next dispatch1320}13211322this._perfMon?.start(this._size);13231324if (!this._listeners) {1325// no-op1326} else if (this._listeners instanceof UniqueContainer) {1327this._deliver(this._listeners, event);1328} else {1329const dq = this._deliveryQueue!;1330dq.enqueue(this, event, this._listeners.length);1331this._deliverQueue(dq);1332}13331334this._perfMon?.stop();1335}13361337hasListeners(): boolean {1338return this._size > 0;1339}1340}13411342export interface EventDeliveryQueue {1343_isEventDeliveryQueue: true;1344}13451346export const createEventDeliveryQueue = (): EventDeliveryQueue => new EventDeliveryQueuePrivate();13471348class EventDeliveryQueuePrivate implements EventDeliveryQueue {1349declare _isEventDeliveryQueue: true;13501351/**1352* Index in current's listener list.1353*/1354public i = -1;13551356/**1357* The last index in the listener's list to deliver.1358*/1359public end = 0;13601361/**1362* Emitter currently being dispatched on. Emitter._listeners is always an array.1363*/1364public current?: Emitter<any>;1365/**1366* Currently emitting value. Defined whenever `current` is.1367*/1368public value?: unknown;13691370public enqueue<T>(emitter: Emitter<T>, value: T, end: number) {1371this.i = 0;1372this.end = end;1373this.current = emitter;1374this.value = value;1375}13761377public reset() {1378this.i = this.end; // force any current emission loop to stop, mainly for during dispose1379this.current = undefined;1380this.value = undefined;1381}1382}13831384export interface IWaitUntil {1385token: CancellationToken;1386waitUntil(thenable: Promise<unknown>): void;1387}13881389export type IWaitUntilData<T> = Omit<Omit<T, 'waitUntil'>, 'token'>;13901391export class AsyncEmitter<T extends IWaitUntil> extends Emitter<T> {13921393private _asyncDeliveryQueue?: LinkedList<[(ev: T) => void, IWaitUntilData<T>]>;13941395async fireAsync(data: IWaitUntilData<T>, token: CancellationToken, promiseJoin?: (p: Promise<unknown>, listener: Function) => Promise<unknown>): Promise<void> {1396if (!this._listeners) {1397return;1398}13991400if (!this._asyncDeliveryQueue) {1401this._asyncDeliveryQueue = new LinkedList();1402}14031404forEachListener(this._listeners, listener => this._asyncDeliveryQueue!.push([listener.value, data]));14051406while (this._asyncDeliveryQueue.size > 0 && !token.isCancellationRequested) {14071408const [listener, data] = this._asyncDeliveryQueue.shift()!;1409const thenables: Promise<unknown>[] = [];14101411// eslint-disable-next-line local/code-no-dangerous-type-assertions1412const event = <T>{1413...data,1414token,1415waitUntil: (p: Promise<unknown>): void => {1416if (Object.isFrozen(thenables)) {1417throw new Error('waitUntil can NOT be called asynchronous');1418}1419if (promiseJoin) {1420p = promiseJoin(p, listener);1421}1422thenables.push(p);1423}1424};14251426try {1427listener(event);1428} catch (e) {1429onUnexpectedError(e);1430continue;1431}14321433// freeze thenables-collection to enforce sync-calls to1434// wait until and then wait for all thenables to resolve1435Object.freeze(thenables);14361437await Promise.allSettled(thenables).then(values => {1438for (const value of values) {1439if (value.status === 'rejected') {1440onUnexpectedError(value.reason);1441}1442}1443});1444}1445}1446}144714481449export class PauseableEmitter<T> extends Emitter<T> {14501451private _isPaused = 0;1452protected _eventQueue = new LinkedList<T>();1453private _mergeFn?: (input: T[]) => T;14541455public get isPaused(): boolean {1456return this._isPaused !== 0;1457}14581459constructor(options?: EmitterOptions & { merge?: (input: T[]) => T }) {1460super(options);1461this._mergeFn = options?.merge;1462}14631464pause(): void {1465this._isPaused++;1466}14671468resume(): void {1469if (this._isPaused !== 0 && --this._isPaused === 0) {1470if (this._mergeFn) {1471// use the merge function to create a single composite1472// event. make a copy in case firing pauses this emitter1473if (this._eventQueue.size > 0) {1474const events = Array.from(this._eventQueue);1475this._eventQueue.clear();1476super.fire(this._mergeFn(events));1477}14781479} else {1480// no merging, fire each event individually and test1481// that this emitter isn't paused halfway through1482while (!this._isPaused && this._eventQueue.size !== 0) {1483super.fire(this._eventQueue.shift()!);1484}1485}1486}1487}14881489override fire(event: T): void {1490if (this._size) {1491if (this._isPaused !== 0) {1492this._eventQueue.push(event);1493} else {1494super.fire(event);1495}1496}1497}1498}14991500export class DebounceEmitter<T> extends PauseableEmitter<T> {15011502private readonly _delay: number;1503private _handle: Timeout | undefined;15041505constructor(options: EmitterOptions & { merge: (input: T[]) => T; delay?: number }) {1506super(options);1507this._delay = options.delay ?? 100;1508}15091510override fire(event: T): void {1511if (!this._handle) {1512this.pause();1513this._handle = setTimeout(() => {1514this._handle = undefined;1515this.resume();1516}, this._delay);1517}1518super.fire(event);1519}1520}15211522/**1523* An emitter which queue all events and then process them at the1524* end of the event loop.1525*/1526export class MicrotaskEmitter<T> extends Emitter<T> {1527private _queuedEvents: T[] = [];1528private _mergeFn?: (input: T[]) => T;15291530constructor(options?: EmitterOptions & { merge?: (input: T[]) => T }) {1531super(options);1532this._mergeFn = options?.merge;1533}1534override fire(event: T): void {15351536if (!this.hasListeners()) {1537return;1538}15391540this._queuedEvents.push(event);1541if (this._queuedEvents.length === 1) {1542queueMicrotask(() => {1543if (this._mergeFn) {1544super.fire(this._mergeFn(this._queuedEvents));1545} else {1546this._queuedEvents.forEach(e => super.fire(e));1547}1548this._queuedEvents = [];1549});1550}1551}1552}15531554/**1555* An event emitter that multiplexes many events into a single event.1556*1557* @example Listen to the `onData` event of all `Thing`s, dynamically adding and removing `Thing`s1558* to the multiplexer as needed.1559*1560* ```typescript1561* const anythingDataMultiplexer = new EventMultiplexer<{ data: string }>();1562*1563* const thingListeners = DisposableMap<Thing, IDisposable>();1564*1565* thingService.onDidAddThing(thing => {1566* thingListeners.set(thing, anythingDataMultiplexer.add(thing.onData);1567* });1568* thingService.onDidRemoveThing(thing => {1569* thingListeners.deleteAndDispose(thing);1570* });1571*1572* anythingDataMultiplexer.event(e => {1573* console.log('Something fired data ' + e.data)1574* });1575* ```1576*/1577export class EventMultiplexer<T> implements IDisposable {15781579private readonly emitter: Emitter<T>;1580private hasListeners = false;1581private events: { event: Event<T>; listener: IDisposable | null }[] = [];15821583constructor() {1584this.emitter = new Emitter<T>({1585onWillAddFirstListener: () => this.onFirstListenerAdd(),1586onDidRemoveLastListener: () => this.onLastListenerRemove()1587});1588}15891590get event(): Event<T> {1591return this.emitter.event;1592}15931594add(event: Event<T>): IDisposable {1595const e = { event: event, listener: null };1596this.events.push(e);15971598if (this.hasListeners) {1599this.hook(e);1600}16011602const dispose = () => {1603if (this.hasListeners) {1604this.unhook(e);1605}16061607const idx = this.events.indexOf(e);1608this.events.splice(idx, 1);1609};16101611return toDisposable(createSingleCallFunction(dispose));1612}16131614private onFirstListenerAdd(): void {1615this.hasListeners = true;1616this.events.forEach(e => this.hook(e));1617}16181619private onLastListenerRemove(): void {1620this.hasListeners = false;1621this.events.forEach(e => this.unhook(e));1622}16231624private hook(e: { event: Event<T>; listener: IDisposable | null }): void {1625e.listener = e.event(r => this.emitter.fire(r));1626}16271628private unhook(e: { event: Event<T>; listener: IDisposable | null }): void {1629e.listener?.dispose();1630e.listener = null;1631}16321633dispose(): void {1634this.emitter.dispose();16351636for (const e of this.events) {1637e.listener?.dispose();1638}1639this.events = [];1640}1641}16421643export interface IDynamicListEventMultiplexer<TEventType> extends IDisposable {1644readonly event: Event<TEventType>;1645}1646export class DynamicListEventMultiplexer<TItem, TEventType> implements IDynamicListEventMultiplexer<TEventType> {1647private readonly _store = new DisposableStore();16481649readonly event: Event<TEventType>;16501651constructor(1652items: TItem[],1653onAddItem: Event<TItem>,1654onRemoveItem: Event<TItem>,1655getEvent: (item: TItem) => Event<TEventType>1656) {1657const multiplexer = this._store.add(new EventMultiplexer<TEventType>());1658const itemListeners = this._store.add(new DisposableMap<TItem, IDisposable>());16591660function addItem(instance: TItem) {1661itemListeners.set(instance, multiplexer.add(getEvent(instance)));1662}16631664// Existing items1665for (const instance of items) {1666addItem(instance);1667}16681669// Added items1670this._store.add(onAddItem(instance => {1671addItem(instance);1672}));16731674// Removed items1675this._store.add(onRemoveItem(instance => {1676itemListeners.deleteAndDispose(instance);1677}));16781679this.event = multiplexer.event;1680}16811682dispose() {1683this._store.dispose();1684}1685}16861687/**1688* The EventBufferer is useful in situations in which you want1689* to delay firing your events during some code.1690* You can wrap that code and be sure that the event will not1691* be fired during that wrap.1692*1693* ```1694* const emitter: Emitter;1695* const delayer = new EventDelayer();1696* const delayedEvent = delayer.wrapEvent(emitter.event);1697*1698* delayedEvent(console.log);1699*1700* delayer.bufferEvents(() => {1701* emitter.fire(); // event will not be fired yet1702* });1703*1704* // event will only be fired at this point1705* ```1706*/1707export class EventBufferer {17081709private data: { buffers: Function[] }[] = [];17101711wrapEvent<T>(event: Event<T>): Event<T>;1712wrapEvent<T>(event: Event<T>, reduce: (last: T | undefined, event: T) => T): Event<T>;1713wrapEvent<T, O>(event: Event<T>, reduce: (last: O | undefined, event: T) => O, initial: O): Event<O>;1714wrapEvent<T, O>(event: Event<T>, reduce?: (last: T | O | undefined, event: T) => T | O, initial?: O): Event<O | T> {1715return (listener, thisArgs?, disposables?) => {1716return event(i => {1717const data = this.data[this.data.length - 1];17181719// Non-reduce scenario1720if (!reduce) {1721// Buffering case1722if (data) {1723data.buffers.push(() => listener.call(thisArgs, i));1724} else {1725// Not buffering case1726listener.call(thisArgs, i);1727}1728return;1729}17301731// Reduce scenario1732const reduceData = data as typeof data & {1733/**1734* The accumulated items that will be reduced.1735*/1736items?: T[];1737/**1738* The reduced result cached to be shared with other listeners.1739*/1740reducedResult?: T | O;1741};17421743// Not buffering case1744if (!reduceData) {1745// TODO: Is there a way to cache this reduce call for all listeners?1746listener.call(thisArgs, reduce(initial, i));1747return;1748}17491750// Buffering case1751reduceData.items ??= [];1752reduceData.items.push(i);1753if (reduceData.buffers.length === 0) {1754// Include a single buffered function that will reduce all events when we're done buffering events1755data.buffers.push(() => {1756// cache the reduced result so that the value can be shared across all listeners1757reduceData.reducedResult ??= initial1758? reduceData.items!.reduce(reduce as (last: O | undefined, event: T) => O, initial)1759: reduceData.items!.reduce(reduce as (last: T | undefined, event: T) => T);1760listener.call(thisArgs, reduceData.reducedResult);1761});1762}1763}, undefined, disposables);1764};1765}17661767bufferEvents<R = void>(fn: () => R): R {1768const data = { buffers: new Array<Function>() };1769this.data.push(data);1770const r = fn();1771this.data.pop();1772data.buffers.forEach(flush => flush());1773return r;1774}1775}17761777/**1778* A Relay is an event forwarder which functions as a replugabble event pipe.1779* Once created, you can connect an input event to it and it will simply forward1780* events from that input event through its own `event` property. The `input`1781* can be changed at any point in time.1782*/1783export class Relay<T> implements IDisposable {17841785private listening = false;1786private inputEvent: Event<T> = Event.None;1787private inputEventListener: IDisposable = Disposable.None;17881789private readonly emitter = new Emitter<T>({1790onDidAddFirstListener: () => {1791this.listening = true;1792this.inputEventListener = this.inputEvent(this.emitter.fire, this.emitter);1793},1794onDidRemoveLastListener: () => {1795this.listening = false;1796this.inputEventListener.dispose();1797}1798});17991800readonly event: Event<T> = this.emitter.event;18011802set input(event: Event<T>) {1803this.inputEvent = event;18041805if (this.listening) {1806this.inputEventListener.dispose();1807this.inputEventListener = event(this.emitter.fire, this.emitter);1808}1809}18101811dispose() {1812this.inputEventListener.dispose();1813this.emitter.dispose();1814}1815}18161817export interface IValueWithChangeEvent<T> {1818readonly onDidChange: Event<void>;1819get value(): T;1820}18211822export class ValueWithChangeEvent<T> implements IValueWithChangeEvent<T> {1823public static const<T>(value: T): IValueWithChangeEvent<T> {1824return new ConstValueWithChangeEvent(value);1825}18261827private readonly _onDidChange = new Emitter<void>();1828readonly onDidChange: Event<void> = this._onDidChange.event;18291830constructor(private _value: T) { }18311832get value(): T {1833return this._value;1834}18351836set value(value: T) {1837if (value !== this._value) {1838this._value = value;1839this._onDidChange.fire(undefined);1840}1841}1842}18431844class ConstValueWithChangeEvent<T> implements IValueWithChangeEvent<T> {1845public readonly onDidChange: Event<void> = Event.None;18461847constructor(readonly value: T) { }1848}18491850/**1851* @param handleItem Is called for each item in the set (but only the first time the item is seen in the set).1852* The returned disposable is disposed if the item is no longer in the set.1853*/1854export function trackSetChanges<T>(getData: () => ReadonlySet<T>, onDidChangeData: Event<unknown>, handleItem: (d: T) => IDisposable): IDisposable {1855const map = new DisposableMap<T, IDisposable>();1856let oldData = new Set(getData());1857for (const d of oldData) {1858map.set(d, handleItem(d));1859}18601861const store = new DisposableStore();1862store.add(onDidChangeData(() => {1863const newData = getData();1864const diff = diffSets(oldData, newData);1865for (const r of diff.removed) {1866map.deleteAndDispose(r);1867}1868for (const a of diff.added) {1869map.set(a, handleItem(a));1870}1871oldData = new Set(newData);1872}));1873store.add(map);1874return store;1875}187618771878function addToDisposables(result: IDisposable, disposables: DisposableStore | IDisposable[] | undefined) {1879if (disposables instanceof DisposableStore) {1880disposables.add(result);1881} else if (Array.isArray(disposables)) {1882disposables.push(result);1883}1884}18851886function disposeAndRemove(result: IDisposable, disposables: DisposableStore | IDisposable[] | undefined) {1887if (disposables instanceof DisposableStore) {1888disposables.delete(result);1889} else if (Array.isArray(disposables)) {1890const index = disposables.indexOf(result);1891if (index !== -1) {1892disposables.splice(index, 1);1893}1894}1895result.dispose();1896}189718981899