123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584 |
- import { onUnexpectedError } from './errors.js';
- import { combinedDisposable, Disposable, DisposableStore, toDisposable } from './lifecycle.js';
- import { LinkedList } from './linkedList.js';
- import { StopWatch } from './stopwatch.js';
- export var Event;
- (function (Event) {
- Event.None = () => Disposable.None;
- /**
- * Given an event, returns another event which only fires once.
- */
- 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;
- 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;
- /**
- * @deprecated DO NOT use, this leaks memory
- */
- function map(event, map) {
- return snapshot((listener, thisArgs = null, disposables) => event(i => listener.call(thisArgs, map(i)), null, disposables));
- }
- Event.map = map;
- /**
- * @deprecated DO NOT use, this leaks memory
- */
- function forEach(event, each) {
- return snapshot((listener, thisArgs = null, disposables) => event(i => { each(i); listener.call(thisArgs, i); }, null, disposables));
- }
- Event.forEach = forEach;
- function filter(event, filter) {
- return snapshot((listener, thisArgs = null, disposables) => event(e => filter(e) && listener.call(thisArgs, e), null, disposables));
- }
- 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) => combinedDisposable(...events.map(event => event(e => listener.call(thisArgs, e), null, disposables)));
- }
- Event.any = any;
- /**
- * @deprecated DO NOT use, this leaks memory
- */
- function reduce(event, merge, initial) {
- let output = initial;
- return map(event, e => {
- output = merge(output, e);
- return output;
- });
- }
- Event.reduce = reduce;
- /**
- * @deprecated DO NOT use, this leaks memory
- */
- function snapshot(event) {
- let listener;
- const emitter = new Emitter({
- onFirstListenerAdd() {
- listener = event(emitter.fire, emitter);
- },
- onLastListenerRemove() {
- listener.dispose();
- }
- });
- return emitter.event;
- }
- /**
- * @deprecated DO NOT use, this leaks memory
- */
- function debounce(event, merge, delay = 100, leading = false, leakWarningThreshold) {
- let subscription;
- let output = undefined;
- let handle = undefined;
- let numDebouncedCalls = 0;
- const emitter = new Emitter({
- leakWarningThreshold,
- onFirstListenerAdd() {
- subscription = event(cur => {
- numDebouncedCalls++;
- output = merge(output, cur);
- if (leading && !handle) {
- emitter.fire(output);
- output = undefined;
- }
- clearTimeout(handle);
- handle = setTimeout(() => {
- const _output = output;
- output = undefined;
- handle = undefined;
- if (!leading || numDebouncedCalls > 1) {
- emitter.fire(_output);
- }
- numDebouncedCalls = 0;
- }, delay);
- });
- },
- onLastListenerRemove() {
- subscription.dispose();
- }
- });
- return emitter.event;
- }
- Event.debounce = debounce;
- /**
- * @deprecated DO NOT use, this leaks memory
- */
- function latch(event, equals = (a, b) => a === b) {
- let firstCall = true;
- let cache;
- return filter(event, value => {
- const shouldEmit = firstCall || !equals(value, cache);
- firstCall = false;
- cache = value;
- return shouldEmit;
- });
- }
- Event.latch = latch;
- /**
- * @deprecated DO NOT use, this leaks memory
- */
- function split(event, isT) {
- return [
- Event.filter(event, isT),
- Event.filter(event, e => !isT(e)),
- ];
- }
- Event.split = split;
- /**
- * @deprecated DO NOT use, this leaks memory
- */
- function buffer(event, flushAfterTimeout = false, _buffer = []) {
- let buffer = _buffer.slice();
- let listener = event(e => {
- if (buffer) {
- buffer.push(e);
- }
- else {
- emitter.fire(e);
- }
- });
- const flush = () => {
- if (buffer) {
- buffer.forEach(e => emitter.fire(e));
- }
- buffer = null;
- };
- const emitter = new Emitter({
- onFirstListenerAdd() {
- if (!listener) {
- listener = event(e => emitter.fire(e));
- }
- },
- onFirstListenerDidAdd() {
- if (buffer) {
- if (flushAfterTimeout) {
- setTimeout(flush);
- }
- else {
- flush();
- }
- }
- },
- onLastListenerRemove() {
- if (listener) {
- listener.dispose();
- }
- listener = null;
- }
- });
- return emitter.event;
- }
- Event.buffer = buffer;
- class ChainableEvent {
- constructor(event) {
- this.event = event;
- }
- map(fn) {
- return new ChainableEvent(map(this.event, fn));
- }
- forEach(fn) {
- return new ChainableEvent(forEach(this.event, fn));
- }
- filter(fn) {
- return new ChainableEvent(filter(this.event, fn));
- }
- reduce(merge, initial) {
- return new ChainableEvent(reduce(this.event, merge, initial));
- }
- latch() {
- return new ChainableEvent(latch(this.event));
- }
- debounce(merge, delay = 100, leading = false, leakWarningThreshold) {
- return new ChainableEvent(debounce(this.event, merge, delay, leading, leakWarningThreshold));
- }
- on(listener, thisArgs, disposables) {
- return this.event(listener, thisArgs, disposables);
- }
- once(listener, thisArgs, disposables) {
- return once(this.event)(listener, thisArgs, disposables);
- }
- }
- /**
- * @deprecated DO NOT use, this leaks memory
- */
- function chain(event) {
- return new ChainableEvent(event);
- }
- Event.chain = chain;
- 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({ onFirstListenerAdd, onLastListenerRemove });
- return result.event;
- }
- Event.fromNodeEventEmitter = fromNodeEventEmitter;
- 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({ onFirstListenerAdd, onLastListenerRemove });
- return result.event;
- }
- Event.fromDOMEventEmitter = fromDOMEventEmitter;
- function toPromise(event) {
- return new Promise(resolve => once(event)(resolve));
- }
- Event.toPromise = toPromise;
- })(Event || (Event = {}));
- class EventProfiling {
- constructor(name) {
- this._listenerCount = 0;
- this._invocationCount = 0;
- this._elapsedOverall = 0;
- this._name = `${name}_${EventProfiling._idPool++}`;
- }
- start(listenerCount) {
- this._stopWatch = new StopWatch(true);
- this._listenerCount = listenerCount;
- }
- stop() {
- if (this._stopWatch) {
- const elapsed = this._stopWatch.elapsed();
- this._elapsedOverall += elapsed;
- this._invocationCount += 1;
- console.info(`did FIRE ${this._name}: elapsed_ms: ${elapsed.toFixed(5)}, listener: ${this._listenerCount} (elapsed_overall: ${this._elapsedOverall.toFixed(2)}, invocations: ${this._invocationCount})`);
- this._stopWatch = undefined;
- }
- }
- }
- EventProfiling._idPool = 0;
- let _globalLeakWarningThreshold = -1;
- class LeakageMonitor {
- constructor(customThreshold, name = Math.random().toString(18).slice(2, 5)) {
- this.customThreshold = customThreshold;
- this.name = name;
- this._warnCountdown = 0;
- }
- dispose() {
- if (this._stacks) {
- this._stacks.clear();
- }
- }
- check(listenerCount) {
- let threshold = _globalLeakWarningThreshold;
- if (typeof this.customThreshold === 'number') {
- threshold = this.customThreshold;
- }
- if (threshold <= 0 || listenerCount < threshold) {
- return undefined;
- }
- if (!this._stacks) {
- this._stacks = new Map();
- }
- const stack = new Error().stack.split('\n').slice(3).join('\n');
- const count = (this._stacks.get(stack) || 0);
- this._stacks.set(stack, 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;
- // find most frequent listener and print warning
- let topStack;
- let topCount = 0;
- for (const [stack, count] of this._stacks) {
- if (!topStack || topCount < count) {
- topStack = stack;
- topCount = count;
- }
- }
- console.warn(`[${this.name}] potential listener LEAK detected, having ${listenerCount} listeners already. MOST frequent listener (${topCount}):`);
- console.warn(topStack);
- }
- return () => {
- const count = (this._stacks.get(stack) || 0);
- this._stacks.set(stack, count - 1);
- };
- }
- }
- /**
- * 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);
- }
- }
- */
- export class Emitter {
- constructor(options) {
- var _a;
- this._disposed = false;
- this._options = options;
- this._leakageMon = _globalLeakWarningThreshold > 0 ? new LeakageMonitor(this._options && this._options.leakWarningThreshold) : undefined;
- this._perfMon = ((_a = this._options) === null || _a === void 0 ? void 0 : _a._profName) ? new EventProfiling(this._options._profName) : undefined;
- }
- /**
- * For the public to allow to subscribe
- * to events from this Emitter
- */
- get event() {
- if (!this._event) {
- this._event = (listener, thisArgs, disposables) => {
- var _a;
- if (!this._listeners) {
- this._listeners = new LinkedList();
- }
- const firstListener = this._listeners.isEmpty();
- if (firstListener && this._options && this._options.onFirstListenerAdd) {
- this._options.onFirstListenerAdd(this);
- }
- const remove = this._listeners.push(!thisArgs ? listener : [listener, thisArgs]);
- if (firstListener && this._options && this._options.onFirstListenerDidAdd) {
- this._options.onFirstListenerDidAdd(this);
- }
- if (this._options && this._options.onListenerDidAdd) {
- this._options.onListenerDidAdd(this, listener, thisArgs);
- }
- // check and record this emitter for potential leakage
- const removeMonitor = (_a = this._leakageMon) === null || _a === void 0 ? void 0 : _a.check(this._listeners.size);
- const result = toDisposable(() => {
- if (removeMonitor) {
- removeMonitor();
- }
- if (!this._disposed) {
- remove();
- if (this._options && this._options.onLastListenerRemove) {
- const hasListeners = (this._listeners && !this._listeners.isEmpty());
- if (!hasListeners) {
- this._options.onLastListenerRemove(this);
- }
- }
- }
- });
- if (disposables instanceof DisposableStore) {
- disposables.add(result);
- }
- else if (Array.isArray(disposables)) {
- disposables.push(result);
- }
- return result;
- };
- }
- return this._event;
- }
- /**
- * To be kept private to fire an event to
- * subscribers
- */
- fire(event) {
- var _a, _b;
- if (this._listeners) {
- // put all [listener,event]-pairs into delivery queue
- // then emit all event. an inner/nested event might be
- // the driver of this
- if (!this._deliveryQueue) {
- this._deliveryQueue = new LinkedList();
- }
- for (let listener of this._listeners) {
- this._deliveryQueue.push([listener, event]);
- }
- // start/stop performance insight collection
- (_a = this._perfMon) === null || _a === void 0 ? void 0 : _a.start(this._deliveryQueue.size);
- while (this._deliveryQueue.size > 0) {
- const [listener, event] = this._deliveryQueue.shift();
- try {
- if (typeof listener === 'function') {
- listener.call(undefined, event);
- }
- else {
- listener[0].call(listener[1], event);
- }
- }
- catch (e) {
- onUnexpectedError(e);
- }
- }
- (_b = this._perfMon) === null || _b === void 0 ? void 0 : _b.stop();
- }
- }
- dispose() {
- var _a, _b, _c, _d, _e;
- if (!this._disposed) {
- this._disposed = true;
- (_a = this._listeners) === null || _a === void 0 ? void 0 : _a.clear();
- (_b = this._deliveryQueue) === null || _b === void 0 ? void 0 : _b.clear();
- (_d = (_c = this._options) === null || _c === void 0 ? void 0 : _c.onLastListenerRemove) === null || _d === void 0 ? void 0 : _d.call(_c);
- (_e = this._leakageMon) === null || _e === void 0 ? void 0 : _e.dispose();
- }
- }
- }
- export class PauseableEmitter extends Emitter {
- constructor(options) {
- super(options);
- this._isPaused = 0;
- this._eventQueue = new LinkedList();
- this._mergeFn = options === null || options === void 0 ? void 0 : options.merge;
- }
- pause() {
- this._isPaused++;
- }
- resume() {
- if (this._isPaused !== 0 && --this._isPaused === 0) {
- if (this._mergeFn) {
- // use the merge function to create a single composite
- // event. make a copy in case firing pauses this emitter
- const events = Array.from(this._eventQueue);
- this._eventQueue.clear();
- super.fire(this._mergeFn(events));
- }
- else {
- // no merging, fire each event individually and test
- // that this emitter isn't paused halfway through
- while (!this._isPaused && this._eventQueue.size !== 0) {
- super.fire(this._eventQueue.shift());
- }
- }
- }
- }
- fire(event) {
- if (this._listeners) {
- if (this._isPaused !== 0) {
- this._eventQueue.push(event);
- }
- else {
- super.fire(event);
- }
- }
- }
- }
- export class DebounceEmitter extends PauseableEmitter {
- constructor(options) {
- var _a;
- super(options);
- this._delay = (_a = options.delay) !== null && _a !== void 0 ? _a : 100;
- }
- fire(event) {
- if (!this._handle) {
- this.pause();
- this._handle = setTimeout(() => {
- this._handle = undefined;
- this.resume();
- }, this._delay);
- }
- super.fire(event);
- }
- }
- /**
- * The EventBufferer is useful in situations in which you want
- * to delay firing your events during some code.
- * You can wrap that code and be sure that the event will not
- * be fired during that wrap.
- *
- * ```
- * const emitter: Emitter;
- * const delayer = new EventDelayer();
- * const delayedEvent = delayer.wrapEvent(emitter.event);
- *
- * delayedEvent(console.log);
- *
- * delayer.bufferEvents(() => {
- * emitter.fire(); // event will not be fired yet
- * });
- *
- * // event will only be fired at this point
- * ```
- */
- export class EventBufferer {
- constructor() {
- this.buffers = [];
- }
- wrapEvent(event) {
- return (listener, thisArgs, disposables) => {
- return event(i => {
- const buffer = this.buffers[this.buffers.length - 1];
- if (buffer) {
- buffer.push(() => listener.call(thisArgs, i));
- }
- else {
- listener.call(thisArgs, i);
- }
- }, undefined, disposables);
- };
- }
- bufferEvents(fn) {
- const buffer = [];
- this.buffers.push(buffer);
- const r = fn();
- this.buffers.pop();
- buffer.forEach(flush => flush());
- return r;
- }
- }
- /**
- * A Relay is an event forwarder which functions as a replugabble event pipe.
- * Once created, you can connect an input event to it and it will simply forward
- * events from that input event through its own `event` property. The `input`
- * can be changed at any point in time.
- */
- export class Relay {
- constructor() {
- this.listening = false;
- this.inputEvent = Event.None;
- this.inputEventListener = Disposable.None;
- this.emitter = new Emitter({
- onFirstListenerDidAdd: () => {
- this.listening = true;
- this.inputEventListener = this.inputEvent(this.emitter.fire, this.emitter);
- },
- onLastListenerRemove: () => {
- this.listening = false;
- this.inputEventListener.dispose();
- }
- });
- this.event = this.emitter.event;
- }
- set input(event) {
- this.inputEvent = event;
- if (this.listening) {
- this.inputEventListener.dispose();
- this.inputEventListener = event(this.emitter.fire, this.emitter);
- }
- }
- dispose() {
- this.inputEventListener.dispose();
- this.emitter.dispose();
- }
- }
|