UNPKG

react-repl-plus

Version:

React component for editing React components, it is similar to vue repl

1,330 lines (1,319 loc) 712 kB
(function () { 'use strict'; /*--------------------------------------------------------------------------------------------- * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ // Avoid circular dependency on EventEmitter by implementing a subset of the interface. class ErrorHandler { constructor() { this.listeners = []; this.unexpectedErrorHandler = function (e) { setTimeout(() => { if (e.stack) { if (ErrorNoTelemetry.isErrorNoTelemetry(e)) { throw new ErrorNoTelemetry(e.message + '\n\n' + e.stack); } throw new Error(e.message + '\n\n' + e.stack); } throw e; }, 0); }; } emit(e) { this.listeners.forEach((listener) => { listener(e); }); } onUnexpectedError(e) { this.unexpectedErrorHandler(e); this.emit(e); } // For external errors, we don't want the listeners to be called onUnexpectedExternalError(e) { this.unexpectedErrorHandler(e); } } const errorHandler = new ErrorHandler(); function onUnexpectedError(e) { // ignore errors from cancelled promises if (!isCancellationError(e)) { errorHandler.onUnexpectedError(e); } return undefined; } function transformErrorForSerialization(error) { if (error instanceof Error) { const { name, message } = error; const stack = error.stacktrace || error.stack; return { $isError: true, name, message, stack, noTelemetry: ErrorNoTelemetry.isErrorNoTelemetry(error) }; } // return as is return error; } const canceledName = 'Canceled'; /** * Checks if the given error is a promise in canceled state */ function isCancellationError(error) { if (error instanceof CancellationError) { return true; } return error instanceof Error && error.name === canceledName && error.message === canceledName; } // !!!IMPORTANT!!! // Do NOT change this class because it is also used as an API-type. class CancellationError extends Error { constructor() { super(canceledName); this.name = this.message; } } /** * Error that when thrown won't be logged in telemetry as an unhandled error. */ class ErrorNoTelemetry extends Error { constructor(msg) { super(msg); this.name = 'CodeExpectedError'; } static fromError(err) { if (err instanceof ErrorNoTelemetry) { return err; } const result = new ErrorNoTelemetry(); result.message = err.message; result.stack = err.stack; return result; } static isErrorNoTelemetry(err) { return err.name === 'CodeExpectedError'; } } /** * This error indicates a bug. * Do not throw this for invalid user input. * Only catch this error to recover gracefully from bugs. */ class BugIndicatingError extends Error { constructor(message) { super(message || 'An unexpected bug occurred.'); Object.setPrototypeOf(this, BugIndicatingError.prototype); // Because we know for sure only buggy code throws this, // we definitely want to break here and fix the bug. // eslint-disable-next-line no-debugger // debugger; } } /*--------------------------------------------------------------------------------------------- * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ /** * Given a function, returns a function that is only calling that function once. */ function createSingleCallFunction(fn, fnDidRunCallback) { const _this = this; let didCall = false; let result; return function () { if (didCall) { return result; } didCall = true; { result = fn.apply(_this, arguments); } return result; }; } /*--------------------------------------------------------------------------------------------- * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ var Iterable; (function (Iterable) { function is(thing) { return thing && typeof thing === 'object' && typeof thing[Symbol.iterator] === 'function'; } Iterable.is = is; const _empty = Object.freeze([]); function empty() { return _empty; } Iterable.empty = empty; function* single(element) { yield element; } Iterable.single = single; function wrap(iterableOrElement) { if (is(iterableOrElement)) { return iterableOrElement; } else { return single(iterableOrElement); } } Iterable.wrap = wrap; function from(iterable) { return iterable || _empty; } Iterable.from = from; function* reverse(array) { for (let i = array.length - 1; i >= 0; i--) { yield array[i]; } } Iterable.reverse = reverse; function isEmpty(iterable) { return !iterable || iterable[Symbol.iterator]().next().done === true; } Iterable.isEmpty = isEmpty; function first(iterable) { return iterable[Symbol.iterator]().next().value; } Iterable.first = first; function some(iterable, predicate) { let i = 0; for (const element of iterable) { if (predicate(element, i++)) { return true; } } return false; } Iterable.some = some; function find(iterable, predicate) { for (const element of iterable) { if (predicate(element)) { return element; } } return undefined; } Iterable.find = find; function* filter(iterable, predicate) { for (const element of iterable) { if (predicate(element)) { yield element; } } } Iterable.filter = filter; function* map(iterable, fn) { let index = 0; for (const element of iterable) { yield fn(element, index++); } } Iterable.map = map; function* flatMap(iterable, fn) { let index = 0; for (const element of iterable) { yield* fn(element, index++); } } Iterable.flatMap = flatMap; function* concat(...iterables) { for (const iterable of iterables) { yield* iterable; } } Iterable.concat = concat; function reduce(iterable, reducer, initialValue) { let value = initialValue; for (const element of iterable) { value = reducer(value, element); } return value; } Iterable.reduce = reduce; /** * Returns an iterable slice of the array, with the same semantics as `array.slice()`. */ function* slice(arr, from, to = arr.length) { if (from < 0) { from += arr.length; } if (to < 0) { to += arr.length; } else if (to > arr.length) { to = arr.length; } for (; from < to; from++) { yield arr[from]; } } Iterable.slice = slice; /** * Consumes `atMost` elements from iterable and returns the consumed elements, * and an iterable for the rest of the elements. */ function consume(iterable, atMost = Number.POSITIVE_INFINITY) { const consumed = []; if (atMost === 0) { return [consumed, iterable]; } const iterator = iterable[Symbol.iterator](); for (let i = 0; i < atMost; i++) { const next = iterator.next(); if (next.done) { return [consumed, Iterable.empty()]; } consumed.push(next.value); } return [consumed, { [Symbol.iterator]() { return iterator; } }]; } Iterable.consume = consume; async function asyncToArray(iterable) { const result = []; for await (const item of iterable) { result.push(item); } return Promise.resolve(result); } Iterable.asyncToArray = asyncToArray; })(Iterable || (Iterable = {})); function trackDisposable(x) { return x; } function setParentOfDisposable(child, parent) { } function dispose(arg) { if (Iterable.is(arg)) { const errors = []; for (const d of arg) { if (d) { try { d.dispose(); } catch (e) { errors.push(e); } } } if (errors.length === 1) { throw errors[0]; } else if (errors.length > 1) { throw new AggregateError(errors, 'Encountered errors while disposing of store'); } return Array.isArray(arg) ? [] : arg; } else if (arg) { arg.dispose(); return arg; } } /** * Combine multiple disposable values into a single {@link IDisposable}. */ function combinedDisposable(...disposables) { const parent = toDisposable(() => dispose(disposables)); return parent; } /** * Turn a function that implements dispose into an {@link IDisposable}. * * @param fn Clean up function, guaranteed to be called only **once**. */ function toDisposable(fn) { const self = trackDisposable({ dispose: createSingleCallFunction(() => { fn(); }) }); return self; } /** * Manages a collection of disposable values. * * This is the preferred way to manage multiple disposables. A `DisposableStore` is safer to work with than an * `IDisposable[]` as it considers edge cases, such as registering the same value multiple times or adding an item to a * store that has already been disposed of. */ class DisposableStore { static { this.DISABLE_DISPOSED_WARNING = false; } constructor() { this._toDispose = new Set(); this._isDisposed = false; } /** * Dispose of all registered disposables and mark this object as disposed. * * Any future disposables added to this object will be disposed of on `add`. */ dispose() { if (this._isDisposed) { return; } this._isDisposed = true; this.clear(); } /** * @return `true` if this object has been disposed of. */ get isDisposed() { return this._isDisposed; } /** * Dispose of all registered disposables but do not mark this object as disposed. */ clear() { if (this._toDispose.size === 0) { return; } try { dispose(this._toDispose); } finally { this._toDispose.clear(); } } /** * Add a new {@link IDisposable disposable} to the collection. */ add(o) { if (!o) { return o; } if (o === this) { throw new Error('Cannot register a disposable on itself!'); } if (this._isDisposed) { if (!DisposableStore.DISABLE_DISPOSED_WARNING) { console.warn(new Error('Trying to add a disposable to a DisposableStore that has already been disposed of. The added object will be leaked!').stack); } } else { this._toDispose.add(o); } return o; } /** * Deletes the value from the store, but does not dispose it. */ deleteAndLeak(o) { if (!o) { return; } if (this._toDispose.has(o)) { this._toDispose.delete(o); } } } /** * Abstract base class for a {@link IDisposable disposable} object. * * Subclasses can {@linkcode _register} disposables that will be automatically cleaned up when this object is disposed of. */ class Disposable { /** * A disposable that does nothing when it is disposed of. * * TODO: This should not be a static property. */ static { this.None = Object.freeze({ dispose() { } }); } constructor() { this._store = new DisposableStore(); setParentOfDisposable(this._store); } dispose() { this._store.dispose(); } /** * Adds `o` to the collection of disposables managed by this object. */ _register(o) { if (o === this) { throw new Error('Cannot register a disposable on itself!'); } return this._store.add(o); } } /*--------------------------------------------------------------------------------------------- * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ class Node { static { this.Undefined = new Node(undefined); } constructor(element) { this.element = element; this.next = Node.Undefined; this.prev = Node.Undefined; } } class LinkedList { constructor() { this._first = Node.Undefined; this._last = Node.Undefined; this._size = 0; } get size() { return this._size; } isEmpty() { return this._first === Node.Undefined; } clear() { let node = this._first; while (node !== Node.Undefined) { const next = node.next; node.prev = Node.Undefined; node.next = Node.Undefined; node = next; } this._first = Node.Undefined; this._last = Node.Undefined; this._size = 0; } unshift(element) { return this._insert(element, false); } push(element) { return this._insert(element, true); } _insert(element, atTheEnd) { const newNode = new Node(element); if (this._first === Node.Undefined) { this._first = newNode; this._last = newNode; } else if (atTheEnd) { // push const oldLast = this._last; this._last = newNode; newNode.prev = oldLast; oldLast.next = newNode; } else { // unshift const oldFirst = this._first; this._first = newNode; newNode.next = oldFirst; oldFirst.prev = newNode; } this._size += 1; let didRemove = false; return () => { if (!didRemove) { didRemove = true; this._remove(newNode); } }; } shift() { if (this._first === Node.Undefined) { return undefined; } else { const res = this._first.element; this._remove(this._first); return res; } } pop() { if (this._last === Node.Undefined) { return undefined; } else { const res = this._last.element; this._remove(this._last); return res; } } _remove(node) { if (node.prev !== Node.Undefined && node.next !== Node.Undefined) { // middle const anchor = node.prev; anchor.next = node.next; node.next.prev = anchor; } else if (node.prev === Node.Undefined && node.next === Node.Undefined) { // only node this._first = Node.Undefined; this._last = Node.Undefined; } else if (node.next === Node.Undefined) { // last this._last = this._last.prev; this._last.next = Node.Undefined; } else if (node.prev === Node.Undefined) { // first this._first = this._first.next; this._first.prev = Node.Undefined; } // done this._size -= 1; } *[Symbol.iterator]() { let node = this._first; while (node !== Node.Undefined) { yield node.element; node = node.next; } } } /*--------------------------------------------------------------------------------------------- * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ const hasPerformanceNow = (globalThis.performance && typeof globalThis.performance.now === 'function'); class StopWatch { static create(highResolution) { return new StopWatch(highResolution); } constructor(highResolution) { this._now = hasPerformanceNow && highResolution === false ? Date.now : globalThis.performance.now.bind(globalThis.performance); this._startTime = this._now(); this._stopTime = -1; } stop() { this._stopTime = this._now(); } reset() { this._startTime = this._now(); this._stopTime = -1; } elapsed() { if (this._stopTime !== -1) { return this._stopTime - this._startTime; } return this._now() - this._startTime; } } var Event; (function (Event) { Event.None = () => Disposable.None; /** * Given an event, returns another event which debounces calls and defers the listeners to a later task via a shared * `setTimeout`. The event is converted into a signal (`Event<void>`) to avoid additional object creation as a * result of merging events and to try prevent race conditions that could arise when using related deferred and * non-deferred events. * * This is useful for deferring non-critical work (eg. general UI updates) to ensure it does not block critical work * (eg. latency of keypress to text rendered). * * *NOTE* that this function returns an `Event` and it MUST be called with a `DisposableStore` whenever the returned * event is accessible to "third parties", e.g the event is a public property. Otherwise a leaked listener on the * returned event causes this utility to leak a listener on the original event. * * @param event The event source for the new event. * @param disposable A disposable store to add the new EventEmitter to. */ function defer(event, disposable) { return debounce(event, () => void 0, 0, undefined, true, undefined, disposable); } Event.defer = defer; /** * Given an event, returns another event which only fires once. * * @param event The event source for the new event. */ function once(event) { return (listener, thisArgs = null, disposables) => { // we need this, in case the event fires during the listener call let didFire = false; let result = undefined; result = event(e => { if (didFire) { return; } else if (result) { result.dispose(); } else { didFire = true; } return listener.call(thisArgs, e); }, null, disposables); if (didFire) { result.dispose(); } return result; }; } Event.once = once; /** * Given an event, returns another event which only fires once, and only when the condition is met. * * @param event The event source for the new event. */ function onceIf(event, condition) { return Event.once(Event.filter(event, condition)); } Event.onceIf = onceIf; /** * Maps an event of one type into an event of another type using a mapping function, similar to how * `Array.prototype.map` works. * * *NOTE* that this function returns an `Event` and it MUST be called with a `DisposableStore` whenever the returned * event is accessible to "third parties", e.g the event is a public property. Otherwise a leaked listener on the * returned event causes this utility to leak a listener on the original event. * * @param event The event source for the new event. * @param map The mapping function. * @param disposable A disposable store to add the new EventEmitter to. */ function map(event, map, disposable) { return snapshot((listener, thisArgs = null, disposables) => event(i => listener.call(thisArgs, map(i)), null, disposables), disposable); } Event.map = map; /** * Wraps an event in another event that performs some function on the event object before firing. * * *NOTE* that this function returns an `Event` and it MUST be called with a `DisposableStore` whenever the returned * event is accessible to "third parties", e.g the event is a public property. Otherwise a leaked listener on the * returned event causes this utility to leak a listener on the original event. * * @param event The event source for the new event. * @param each The function to perform on the event object. * @param disposable A disposable store to add the new EventEmitter to. */ function forEach(event, each, disposable) { return snapshot((listener, thisArgs = null, disposables) => event(i => { each(i); listener.call(thisArgs, i); }, null, disposables), disposable); } Event.forEach = forEach; function filter(event, filter, disposable) { return snapshot((listener, thisArgs = null, disposables) => event(e => filter(e) && listener.call(thisArgs, e), null, disposables), disposable); } Event.filter = filter; /** * Given an event, returns the same event but typed as `Event<void>`. */ function signal(event) { return event; } Event.signal = signal; function any(...events) { return (listener, thisArgs = null, disposables) => { const disposable = combinedDisposable(...events.map(event => event(e => listener.call(thisArgs, e)))); return addAndReturnDisposable(disposable, disposables); }; } Event.any = any; /** * *NOTE* that this function returns an `Event` and it MUST be called with a `DisposableStore` whenever the returned * event is accessible to "third parties", e.g the event is a public property. Otherwise a leaked listener on the * returned event causes this utility to leak a listener on the original event. */ function reduce(event, merge, initial, disposable) { let output = initial; return map(event, e => { output = merge(output, e); return output; }, disposable); } Event.reduce = reduce; function snapshot(event, disposable) { let listener; const options = { onWillAddFirstListener() { listener = event(emitter.fire, emitter); }, onDidRemoveLastListener() { listener?.dispose(); } }; const emitter = new Emitter(options); disposable?.add(emitter); return emitter.event; } /** * Adds the IDisposable to the store if it's set, and returns it. Useful to * Event function implementation. */ function addAndReturnDisposable(d, store) { if (store instanceof Array) { store.push(d); } else if (store) { store.add(d); } return d; } function debounce(event, merge, delay = 100, leading = false, flushOnListenerRemove = false, leakWarningThreshold, disposable) { let subscription; let output = undefined; let handle = undefined; let numDebouncedCalls = 0; let doFire; const options = { leakWarningThreshold, onWillAddFirstListener() { subscription = event(cur => { numDebouncedCalls++; output = merge(output, cur); if (leading && !handle) { emitter.fire(output); output = undefined; } doFire = () => { const _output = output; output = undefined; handle = undefined; if (!leading || numDebouncedCalls > 1) { emitter.fire(_output); } numDebouncedCalls = 0; }; if (typeof delay === 'number') { clearTimeout(handle); handle = setTimeout(doFire, delay); } else { if (handle === undefined) { handle = 0; queueMicrotask(doFire); } } }); }, onWillRemoveListener() { if (flushOnListenerRemove && numDebouncedCalls > 0) { doFire?.(); } }, onDidRemoveLastListener() { doFire = undefined; subscription.dispose(); } }; const emitter = new Emitter(options); disposable?.add(emitter); return emitter.event; } Event.debounce = debounce; /** * Debounces an event, firing after some delay (default=0) with an array of all event original objects. * * *NOTE* that this function returns an `Event` and it MUST be called with a `DisposableStore` whenever the returned * event is accessible to "third parties", e.g the event is a public property. Otherwise a leaked listener on the * returned event causes this utility to leak a listener on the original event. */ function accumulate(event, delay = 0, disposable) { return Event.debounce(event, (last, e) => { if (!last) { return [e]; } last.push(e); return last; }, delay, undefined, true, undefined, disposable); } Event.accumulate = accumulate; /** * Filters an event such that some condition is _not_ met more than once in a row, effectively ensuring duplicate * event objects from different sources do not fire the same event object. * * *NOTE* that this function returns an `Event` and it MUST be called with a `DisposableStore` whenever the returned * event is accessible to "third parties", e.g the event is a public property. Otherwise a leaked listener on the * returned event causes this utility to leak a listener on the original event. * * @param event The event source for the new event. * @param equals The equality condition. * @param disposable A disposable store to add the new EventEmitter to. * * @example * ``` * // Fire only one time when a single window is opened or focused * Event.latch(Event.any(onDidOpenWindow, onDidFocusWindow)) * ``` */ function latch(event, equals = (a, b) => a === b, disposable) { let firstCall = true; let cache; return filter(event, value => { const shouldEmit = firstCall || !equals(value, cache); firstCall = false; cache = value; return shouldEmit; }, disposable); } Event.latch = latch; /** * Splits an event whose parameter is a union type into 2 separate events for each type in the union. * * *NOTE* that this function returns an `Event` and it MUST be called with a `DisposableStore` whenever the returned * event is accessible to "third parties", e.g the event is a public property. Otherwise a leaked listener on the * returned event causes this utility to leak a listener on the original event. * * @example * ``` * const event = new EventEmitter<number | undefined>().event; * const [numberEvent, undefinedEvent] = Event.split(event, isUndefined); * ``` * * @param event The event source for the new event. * @param isT A function that determines what event is of the first type. * @param disposable A disposable store to add the new EventEmitter to. */ function split(event, isT, disposable) { return [ Event.filter(event, isT, disposable), Event.filter(event, e => !isT(e), disposable), ]; } Event.split = split; /** * Buffers an event until it has a listener attached. * * *NOTE* that this function returns an `Event` and it MUST be called with a `DisposableStore` whenever the returned * event is accessible to "third parties", e.g the event is a public property. Otherwise a leaked listener on the * returned event causes this utility to leak a listener on the original event. * * @param event The event source for the new event. * @param flushAfterTimeout Determines whether to flush the buffer after a timeout immediately or after a * `setTimeout` when the first event listener is added. * @param _buffer Internal: A source event array used for tests. * * @example * ``` * // Start accumulating events, when the first listener is attached, flush * // the event after a timeout such that multiple listeners attached before * // the timeout would receive the event * this.onInstallExtension = Event.buffer(service.onInstallExtension, true); * ``` */ function buffer(event, flushAfterTimeout = false, _buffer = [], disposable) { let buffer = _buffer.slice(); let listener = event(e => { if (buffer) { buffer.push(e); } else { emitter.fire(e); } }); if (disposable) { disposable.add(listener); } const flush = () => { buffer?.forEach(e => emitter.fire(e)); buffer = null; }; const emitter = new Emitter({ onWillAddFirstListener() { if (!listener) { listener = event(e => emitter.fire(e)); if (disposable) { disposable.add(listener); } } }, onDidAddFirstListener() { if (buffer) { if (flushAfterTimeout) { setTimeout(flush); } else { flush(); } } }, onDidRemoveLastListener() { if (listener) { listener.dispose(); } listener = null; } }); if (disposable) { disposable.add(emitter); } return emitter.event; } Event.buffer = buffer; /** * Wraps the event in an {@link IChainableEvent}, allowing a more functional programming style. * * @example * ``` * // Normal * const onEnterPressNormal = Event.filter( * Event.map(onKeyPress.event, e => new StandardKeyboardEvent(e)), * e.keyCode === KeyCode.Enter * ).event; * * // Using chain * const onEnterPressChain = Event.chain(onKeyPress.event, $ => $ * .map(e => new StandardKeyboardEvent(e)) * .filter(e => e.keyCode === KeyCode.Enter) * ); * ``` */ function chain(event, sythensize) { const fn = (listener, thisArgs, disposables) => { const cs = sythensize(new ChainableSynthesis()); return event(function (value) { const result = cs.evaluate(value); if (result !== HaltChainable) { listener.call(thisArgs, result); } }, undefined, disposables); }; return fn; } Event.chain = chain; const HaltChainable = Symbol('HaltChainable'); class ChainableSynthesis { constructor() { this.steps = []; } map(fn) { this.steps.push(fn); return this; } forEach(fn) { this.steps.push(v => { fn(v); return v; }); return this; } filter(fn) { this.steps.push(v => fn(v) ? v : HaltChainable); return this; } reduce(merge, initial) { let last = initial; this.steps.push(v => { last = merge(last, v); return last; }); return this; } latch(equals = (a, b) => a === b) { let firstCall = true; let cache; this.steps.push(value => { const shouldEmit = firstCall || !equals(value, cache); firstCall = false; cache = value; return shouldEmit ? value : HaltChainable; }); return this; } evaluate(value) { for (const step of this.steps) { value = step(value); if (value === HaltChainable) { break; } } return value; } } /** * Creates an {@link Event} from a node event emitter. */ function fromNodeEventEmitter(emitter, eventName, map = id => id) { const fn = (...args) => result.fire(map(...args)); const onFirstListenerAdd = () => emitter.on(eventName, fn); const onLastListenerRemove = () => emitter.removeListener(eventName, fn); const result = new Emitter({ onWillAddFirstListener: onFirstListenerAdd, onDidRemoveLastListener: onLastListenerRemove }); return result.event; } Event.fromNodeEventEmitter = fromNodeEventEmitter; /** * Creates an {@link Event} from a DOM event emitter. */ function fromDOMEventEmitter(emitter, eventName, map = id => id) { const fn = (...args) => result.fire(map(...args)); const onFirstListenerAdd = () => emitter.addEventListener(eventName, fn); const onLastListenerRemove = () => emitter.removeEventListener(eventName, fn); const result = new Emitter({ onWillAddFirstListener: onFirstListenerAdd, onDidRemoveLastListener: onLastListenerRemove }); return result.event; } Event.fromDOMEventEmitter = fromDOMEventEmitter; /** * Creates a promise out of an event, using the {@link Event.once} helper. */ function toPromise(event) { return new Promise(resolve => once(event)(resolve)); } Event.toPromise = toPromise; /** * Creates an event out of a promise that fires once when the promise is * resolved with the result of the promise or `undefined`. */ function fromPromise(promise) { const result = new Emitter(); promise.then(res => { result.fire(res); }, () => { result.fire(undefined); }).finally(() => { result.dispose(); }); return result.event; } Event.fromPromise = fromPromise; /** * A convenience function for forwarding an event to another emitter which * improves readability. * * This is similar to {@link Relay} but allows instantiating and forwarding * on a single line and also allows for multiple source events. * @param from The event to forward. * @param to The emitter to forward the event to. * @example * Event.forward(event, emitter); * // equivalent to * event(e => emitter.fire(e)); * // equivalent to * event(emitter.fire, emitter); */ function forward(from, to) { return from(e => to.fire(e)); } Event.forward = forward; function runAndSubscribe(event, handler, initial) { handler(initial); return event(e => handler(e)); } Event.runAndSubscribe = runAndSubscribe; class EmitterObserver { constructor(_observable, store) { this._observable = _observable; this._counter = 0; this._hasChanged = false; const options = { onWillAddFirstListener: () => { _observable.addObserver(this); // Communicate to the observable that we received its current value and would like to be notified about future changes. this._observable.reportChanges(); }, onDidRemoveLastListener: () => { _observable.removeObserver(this); } }; this.emitter = new Emitter(options); if (store) { store.add(this.emitter); } } beginUpdate(_observable) { // assert(_observable === this.obs); this._counter++; } handlePossibleChange(_observable) { // assert(_observable === this.obs); } handleChange(_observable, _change) { // assert(_observable === this.obs); this._hasChanged = true; } endUpdate(_observable) { // assert(_observable === this.obs); this._counter--; if (this._counter === 0) { this._observable.reportChanges(); if (this._hasChanged) { this._hasChanged = false; this.emitter.fire(this._observable.get()); } } } } /** * Creates an event emitter that is fired when the observable changes. * Each listeners subscribes to the emitter. */ function fromObservable(obs, store) { const observer = new EmitterObserver(obs, store); return observer.emitter.event; } Event.fromObservable = fromObservable; /** * Each listener is attached to the observable directly. */ function fromObservableLight(observable) { return (listener, thisArgs, disposables) => { let count = 0; let didChange = false; const observer = { beginUpdate() { count++; }, endUpdate() { count--; if (count === 0) { observable.reportChanges(); if (didChange) { didChange = false; listener.call(thisArgs); } } }, handlePossibleChange() { // noop }, handleChange() { didChange = true; } }; observable.addObserver(observer); observable.reportChanges(); const disposable = { dispose() { observable.removeObserver(observer); } }; if (disposables instanceof DisposableStore) { disposables.add(disposable); } else if (Array.isArray(disposables)) { disposables.push(disposable); } return disposable; }; } Event.fromObservableLight = fromObservableLight; })(Event || (Event = {})); class EventProfiling { static { this.all = new Set(); } static { this._idPool = 0; } constructor(name) { this.listenerCount = 0; this.invocationCount = 0; this.elapsedOverall = 0; this.durations = []; this.name = `${name}_${EventProfiling._idPool++}`; EventProfiling.all.add(this); } start(listenerCount) { this._stopWatch = new StopWatch(); this.listenerCount = listenerCount; } stop() { if (this._stopWatch) { const elapsed = this._stopWatch.elapsed(); this.durations.push(elapsed); this.elapsedOverall += elapsed; this.invocationCount += 1; this._stopWatch = undefined; } } } let _globalLeakWarningThreshold = -1; class LeakageMonitor { static { this._idPool = 1; } constructor(_errorHandler, threshold, name = (LeakageMonitor._idPool++).toString(16).padStart(3, '0')) { this._errorHandler = _errorHandler; this.threshold = threshold; this.name = name; this._warnCountdown = 0; } dispose() { this._stacks?.clear(); } check(stack, listenerCount) { const threshold = this.threshold; if (threshold <= 0 || listenerCount < threshold) { return undefined; } if (!this._stacks) { this._stacks = new Map(); } const count = (this._stacks.get(stack.value) || 0); this._stacks.set(stack.value, count + 1); this._warnCountdown -= 1; if (this._warnCountdown <= 0) { // only warn on first exceed and then every time the limit // is exceeded by 50% again this._warnCountdown = threshold * 0.5; const [topStack, topCount] = this.getMostFrequentStack(); const message = `[${this.name}] potential listener LEAK detected, having ${listenerCount} listeners already. MOST frequent listener (${topCount}):`; console.warn(message); console.warn(topStack); const error = new ListenerLeakError(message, topStack); this._errorHandler(error); } return () => { const count = (this._stacks.get(stack.value) || 0); this._stacks.set(stack.value, count - 1); }; } getMostFrequentStack() { if (!this._stacks) { return undefined; } let topStack; let topCount = 0; for (const [stack, count] of this._stacks) { if (!topStack || topCount < count) { topStack = [stack, count]; topCount = count; } } return topStack; } } class Stacktrace { static create() { const err = new Error(); return new Stacktrace(err.stack ?? ''); } constructor(value) { this.value = value; } print() { console.warn(this.value.split('\n').slice(2).join('\n')); } } // error that is logged when going over the configured listener threshold class ListenerLeakError extends Error { constructor(message, stack) { super(message); this.name = 'ListenerLeakError'; this.stack = stack; } } // SEVERE error that is logged when having gone way over the configured listener // threshold so that the emitter refuses to accept more listeners class ListenerRefusalError extends Error { constructor(message, stack) { super(message); this.name = 'ListenerRefusalError'; this.stack = stack; } } class UniqueContainer { constructor(value) { this.value = value; } } const compactionThreshold = 2; /** * The Emitter can be used to expose an Event to the public * to fire it from the insides. * Sample: class Document { private readonly _onDidChange = new Emitter<(value:string)=>any>(); public onDidChange = this._onDidChange.event; // getter-style // get onDidChange(): Event<(value:string)=>any> { // return this._onDidChange.event; // } private _doIt() { //... this._onDidChange.fire(value); } } */ class Emitter { constructor(options) { this._size = 0; this._options = options; this._leakageMon = (this._options?.leakWarningThreshold) ? new LeakageMonitor(options?.onListenerError ?? onUnexpectedError, this._options?.leakWarningThreshold ?? _globalLeakWarningThreshold) : undefined; this._perfMon =