react-repl-plus
Version:
React component for editing React components, it is similar to vue repl
1,330 lines (1,319 loc) • 712 kB
JavaScript
(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 =