event.js 41 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068
  1. import { onUnexpectedError } from './errors.js';
  2. import { once as onceFn } from './functional.js';
  3. import { combinedDisposable, Disposable, DisposableStore, SafeDisposable, toDisposable } from './lifecycle.js';
  4. import { LinkedList } from './linkedList.js';
  5. import { StopWatch } from './stopwatch.js';
  6. // -----------------------------------------------------------------------------------------------------------------------
  7. // Uncomment the next line to print warnings whenever an emitter with listeners is disposed. That is a sign of code smell.
  8. // -----------------------------------------------------------------------------------------------------------------------
  9. const _enableDisposeWithListenerWarning = false;
  10. // _enableDisposeWithListenerWarning = Boolean("TRUE"); // causes a linter warning so that it cannot be pushed
  11. // -----------------------------------------------------------------------------------------------------------------------
  12. // Uncomment the next line to print warnings whenever a snapshotted event is used repeatedly without cleanup.
  13. // See https://github.com/microsoft/vscode/issues/142851
  14. // -----------------------------------------------------------------------------------------------------------------------
  15. const _enableSnapshotPotentialLeakWarning = false;
  16. export var Event;
  17. (function (Event) {
  18. Event.None = () => Disposable.None;
  19. function _addLeakageTraceLogic(options) {
  20. if (_enableSnapshotPotentialLeakWarning) {
  21. const { onDidAddListener: origListenerDidAdd } = options;
  22. const stack = Stacktrace.create();
  23. let count = 0;
  24. options.onDidAddListener = () => {
  25. if (++count === 2) {
  26. console.warn('snapshotted emitter LIKELY used public and SHOULD HAVE BEEN created with DisposableStore. snapshotted here');
  27. stack.print();
  28. }
  29. origListenerDidAdd === null || origListenerDidAdd === void 0 ? void 0 : origListenerDidAdd();
  30. };
  31. }
  32. }
  33. /**
  34. * Given an event, returns another event which debounces calls and defers the listeners to a later task via a shared
  35. * `setTimeout`. The event is converted into a signal (`Event<void>`) to avoid additional object creation as a
  36. * result of merging events and to try prevent race conditions that could arise when using related deferred and
  37. * non-deferred events.
  38. *
  39. * This is useful for deferring non-critical work (eg. general UI updates) to ensure it does not block critical work
  40. * (eg. latency of keypress to text rendered).
  41. *
  42. * *NOTE* that this function returns an `Event` and it MUST be called with a `DisposableStore` whenever the returned
  43. * event is accessible to "third parties", e.g the event is a public property. Otherwise a leaked listener on the
  44. * returned event causes this utility to leak a listener on the original event.
  45. *
  46. * @param event The event source for the new event.
  47. * @param disposable A disposable store to add the new EventEmitter to.
  48. */
  49. function defer(event, disposable) {
  50. return debounce(event, () => void 0, 0, undefined, true, undefined, disposable);
  51. }
  52. Event.defer = defer;
  53. /**
  54. * Given an event, returns another event which only fires once.
  55. *
  56. * @param event The event source for the new event.
  57. */
  58. function once(event) {
  59. return (listener, thisArgs = null, disposables) => {
  60. // we need this, in case the event fires during the listener call
  61. let didFire = false;
  62. let result = undefined;
  63. result = event(e => {
  64. if (didFire) {
  65. return;
  66. }
  67. else if (result) {
  68. result.dispose();
  69. }
  70. else {
  71. didFire = true;
  72. }
  73. return listener.call(thisArgs, e);
  74. }, null, disposables);
  75. if (didFire) {
  76. result.dispose();
  77. }
  78. return result;
  79. };
  80. }
  81. Event.once = once;
  82. /**
  83. * Maps an event of one type into an event of another type using a mapping function, similar to how
  84. * `Array.prototype.map` works.
  85. *
  86. * *NOTE* that this function returns an `Event` and it MUST be called with a `DisposableStore` whenever the returned
  87. * event is accessible to "third parties", e.g the event is a public property. Otherwise a leaked listener on the
  88. * returned event causes this utility to leak a listener on the original event.
  89. *
  90. * @param event The event source for the new event.
  91. * @param map The mapping function.
  92. * @param disposable A disposable store to add the new EventEmitter to.
  93. */
  94. function map(event, map, disposable) {
  95. return snapshot((listener, thisArgs = null, disposables) => event(i => listener.call(thisArgs, map(i)), null, disposables), disposable);
  96. }
  97. Event.map = map;
  98. /**
  99. * Wraps an event in another event that performs some function on the event object before firing.
  100. *
  101. * *NOTE* that this function returns an `Event` and it MUST be called with a `DisposableStore` whenever the returned
  102. * event is accessible to "third parties", e.g the event is a public property. Otherwise a leaked listener on the
  103. * returned event causes this utility to leak a listener on the original event.
  104. *
  105. * @param event The event source for the new event.
  106. * @param each The function to perform on the event object.
  107. * @param disposable A disposable store to add the new EventEmitter to.
  108. */
  109. function forEach(event, each, disposable) {
  110. return snapshot((listener, thisArgs = null, disposables) => event(i => { each(i); listener.call(thisArgs, i); }, null, disposables), disposable);
  111. }
  112. Event.forEach = forEach;
  113. function filter(event, filter, disposable) {
  114. return snapshot((listener, thisArgs = null, disposables) => event(e => filter(e) && listener.call(thisArgs, e), null, disposables), disposable);
  115. }
  116. Event.filter = filter;
  117. /**
  118. * Given an event, returns the same event but typed as `Event<void>`.
  119. */
  120. function signal(event) {
  121. return event;
  122. }
  123. Event.signal = signal;
  124. function any(...events) {
  125. return (listener, thisArgs = null, disposables) => combinedDisposable(...events.map(event => event(e => listener.call(thisArgs, e), null, disposables)));
  126. }
  127. Event.any = any;
  128. /**
  129. * *NOTE* that this function returns an `Event` and it MUST be called with a `DisposableStore` whenever the returned
  130. * event is accessible to "third parties", e.g the event is a public property. Otherwise a leaked listener on the
  131. * returned event causes this utility to leak a listener on the original event.
  132. */
  133. function reduce(event, merge, initial, disposable) {
  134. let output = initial;
  135. return map(event, e => {
  136. output = merge(output, e);
  137. return output;
  138. }, disposable);
  139. }
  140. Event.reduce = reduce;
  141. function snapshot(event, disposable) {
  142. let listener;
  143. const options = {
  144. onWillAddFirstListener() {
  145. listener = event(emitter.fire, emitter);
  146. },
  147. onDidRemoveLastListener() {
  148. listener === null || listener === void 0 ? void 0 : listener.dispose();
  149. }
  150. };
  151. if (!disposable) {
  152. _addLeakageTraceLogic(options);
  153. }
  154. const emitter = new Emitter(options);
  155. disposable === null || disposable === void 0 ? void 0 : disposable.add(emitter);
  156. return emitter.event;
  157. }
  158. function debounce(event, merge, delay = 100, leading = false, flushOnListenerRemove = false, leakWarningThreshold, disposable) {
  159. let subscription;
  160. let output = undefined;
  161. let handle = undefined;
  162. let numDebouncedCalls = 0;
  163. let doFire;
  164. const options = {
  165. leakWarningThreshold,
  166. onWillAddFirstListener() {
  167. subscription = event(cur => {
  168. numDebouncedCalls++;
  169. output = merge(output, cur);
  170. if (leading && !handle) {
  171. emitter.fire(output);
  172. output = undefined;
  173. }
  174. doFire = () => {
  175. const _output = output;
  176. output = undefined;
  177. handle = undefined;
  178. if (!leading || numDebouncedCalls > 1) {
  179. emitter.fire(_output);
  180. }
  181. numDebouncedCalls = 0;
  182. };
  183. if (typeof delay === 'number') {
  184. clearTimeout(handle);
  185. handle = setTimeout(doFire, delay);
  186. }
  187. else {
  188. if (handle === undefined) {
  189. handle = 0;
  190. queueMicrotask(doFire);
  191. }
  192. }
  193. });
  194. },
  195. onWillRemoveListener() {
  196. if (flushOnListenerRemove && numDebouncedCalls > 0) {
  197. doFire === null || doFire === void 0 ? void 0 : doFire();
  198. }
  199. },
  200. onDidRemoveLastListener() {
  201. doFire = undefined;
  202. subscription.dispose();
  203. }
  204. };
  205. if (!disposable) {
  206. _addLeakageTraceLogic(options);
  207. }
  208. const emitter = new Emitter(options);
  209. disposable === null || disposable === void 0 ? void 0 : disposable.add(emitter);
  210. return emitter.event;
  211. }
  212. Event.debounce = debounce;
  213. /**
  214. * Debounces an event, firing after some delay (default=0) with an array of all event original objects.
  215. *
  216. * *NOTE* that this function returns an `Event` and it MUST be called with a `DisposableStore` whenever the returned
  217. * event is accessible to "third parties", e.g the event is a public property. Otherwise a leaked listener on the
  218. * returned event causes this utility to leak a listener on the original event.
  219. */
  220. function accumulate(event, delay = 0, disposable) {
  221. return Event.debounce(event, (last, e) => {
  222. if (!last) {
  223. return [e];
  224. }
  225. last.push(e);
  226. return last;
  227. }, delay, undefined, true, undefined, disposable);
  228. }
  229. Event.accumulate = accumulate;
  230. /**
  231. * Filters an event such that some condition is _not_ met more than once in a row, effectively ensuring duplicate
  232. * event objects from different sources do not fire the same event object.
  233. *
  234. * *NOTE* that this function returns an `Event` and it MUST be called with a `DisposableStore` whenever the returned
  235. * event is accessible to "third parties", e.g the event is a public property. Otherwise a leaked listener on the
  236. * returned event causes this utility to leak a listener on the original event.
  237. *
  238. * @param event The event source for the new event.
  239. * @param equals The equality condition.
  240. * @param disposable A disposable store to add the new EventEmitter to.
  241. *
  242. * @example
  243. * ```
  244. * // Fire only one time when a single window is opened or focused
  245. * Event.latch(Event.any(onDidOpenWindow, onDidFocusWindow))
  246. * ```
  247. */
  248. function latch(event, equals = (a, b) => a === b, disposable) {
  249. let firstCall = true;
  250. let cache;
  251. return filter(event, value => {
  252. const shouldEmit = firstCall || !equals(value, cache);
  253. firstCall = false;
  254. cache = value;
  255. return shouldEmit;
  256. }, disposable);
  257. }
  258. Event.latch = latch;
  259. /**
  260. * Splits an event whose parameter is a union type into 2 separate events for each type in the union.
  261. *
  262. * *NOTE* that this function returns an `Event` and it MUST be called with a `DisposableStore` whenever the returned
  263. * event is accessible to "third parties", e.g the event is a public property. Otherwise a leaked listener on the
  264. * returned event causes this utility to leak a listener on the original event.
  265. *
  266. * @example
  267. * ```
  268. * const event = new EventEmitter<number | undefined>().event;
  269. * const [numberEvent, undefinedEvent] = Event.split(event, isUndefined);
  270. * ```
  271. *
  272. * @param event The event source for the new event.
  273. * @param isT A function that determines what event is of the first type.
  274. * @param disposable A disposable store to add the new EventEmitter to.
  275. */
  276. function split(event, isT, disposable) {
  277. return [
  278. Event.filter(event, isT, disposable),
  279. Event.filter(event, e => !isT(e), disposable),
  280. ];
  281. }
  282. Event.split = split;
  283. /**
  284. * Buffers an event until it has a listener attached.
  285. *
  286. * *NOTE* that this function returns an `Event` and it MUST be called with a `DisposableStore` whenever the returned
  287. * event is accessible to "third parties", e.g the event is a public property. Otherwise a leaked listener on the
  288. * returned event causes this utility to leak a listener on the original event.
  289. *
  290. * @param event The event source for the new event.
  291. * @param flushAfterTimeout Determines whether to flush the buffer after a timeout immediately or after a
  292. * `setTimeout` when the first event listener is added.
  293. * @param _buffer Internal: A source event array used for tests.
  294. *
  295. * @example
  296. * ```
  297. * // Start accumulating events, when the first listener is attached, flush
  298. * // the event after a timeout such that multiple listeners attached before
  299. * // the timeout would receive the event
  300. * this.onInstallExtension = Event.buffer(service.onInstallExtension, true);
  301. * ```
  302. */
  303. function buffer(event, flushAfterTimeout = false, _buffer = []) {
  304. let buffer = _buffer.slice();
  305. let listener = event(e => {
  306. if (buffer) {
  307. buffer.push(e);
  308. }
  309. else {
  310. emitter.fire(e);
  311. }
  312. });
  313. const flush = () => {
  314. buffer === null || buffer === void 0 ? void 0 : buffer.forEach(e => emitter.fire(e));
  315. buffer = null;
  316. };
  317. const emitter = new Emitter({
  318. onWillAddFirstListener() {
  319. if (!listener) {
  320. listener = event(e => emitter.fire(e));
  321. }
  322. },
  323. onDidAddFirstListener() {
  324. if (buffer) {
  325. if (flushAfterTimeout) {
  326. setTimeout(flush);
  327. }
  328. else {
  329. flush();
  330. }
  331. }
  332. },
  333. onDidRemoveLastListener() {
  334. if (listener) {
  335. listener.dispose();
  336. }
  337. listener = null;
  338. }
  339. });
  340. return emitter.event;
  341. }
  342. Event.buffer = buffer;
  343. class ChainableEvent {
  344. constructor(event) {
  345. this.event = event;
  346. this.disposables = new DisposableStore();
  347. }
  348. /** @see {@link Event.map} */
  349. map(fn) {
  350. return new ChainableEvent(map(this.event, fn, this.disposables));
  351. }
  352. /** @see {@link Event.forEach} */
  353. forEach(fn) {
  354. return new ChainableEvent(forEach(this.event, fn, this.disposables));
  355. }
  356. filter(fn) {
  357. return new ChainableEvent(filter(this.event, fn, this.disposables));
  358. }
  359. /** @see {@link Event.reduce} */
  360. reduce(merge, initial) {
  361. return new ChainableEvent(reduce(this.event, merge, initial, this.disposables));
  362. }
  363. /** @see {@link Event.reduce} */
  364. latch() {
  365. return new ChainableEvent(latch(this.event, undefined, this.disposables));
  366. }
  367. debounce(merge, delay = 100, leading = false, flushOnListenerRemove = false, leakWarningThreshold) {
  368. return new ChainableEvent(debounce(this.event, merge, delay, leading, flushOnListenerRemove, leakWarningThreshold, this.disposables));
  369. }
  370. /**
  371. * Attach a listener to the event.
  372. */
  373. on(listener, thisArgs, disposables) {
  374. return this.event(listener, thisArgs, disposables);
  375. }
  376. /** @see {@link Event.once} */
  377. once(listener, thisArgs, disposables) {
  378. return once(this.event)(listener, thisArgs, disposables);
  379. }
  380. dispose() {
  381. this.disposables.dispose();
  382. }
  383. }
  384. /**
  385. * Wraps the event in an {@link IChainableEvent}, allowing a more functional programming style.
  386. *
  387. * @example
  388. * ```
  389. * // Normal
  390. * const onEnterPressNormal = Event.filter(
  391. * Event.map(onKeyPress.event, e => new StandardKeyboardEvent(e)),
  392. * e.keyCode === KeyCode.Enter
  393. * ).event;
  394. *
  395. * // Using chain
  396. * const onEnterPressChain = Event.chain(onKeyPress.event)
  397. * .map(e => new StandardKeyboardEvent(e))
  398. * .filter(e => e.keyCode === KeyCode.Enter)
  399. * .event;
  400. * ```
  401. */
  402. function chain(event) {
  403. return new ChainableEvent(event);
  404. }
  405. Event.chain = chain;
  406. /**
  407. * Creates an {@link Event} from a node event emitter.
  408. */
  409. function fromNodeEventEmitter(emitter, eventName, map = id => id) {
  410. const fn = (...args) => result.fire(map(...args));
  411. const onFirstListenerAdd = () => emitter.on(eventName, fn);
  412. const onLastListenerRemove = () => emitter.removeListener(eventName, fn);
  413. const result = new Emitter({ onWillAddFirstListener: onFirstListenerAdd, onDidRemoveLastListener: onLastListenerRemove });
  414. return result.event;
  415. }
  416. Event.fromNodeEventEmitter = fromNodeEventEmitter;
  417. /**
  418. * Creates an {@link Event} from a DOM event emitter.
  419. */
  420. function fromDOMEventEmitter(emitter, eventName, map = id => id) {
  421. const fn = (...args) => result.fire(map(...args));
  422. const onFirstListenerAdd = () => emitter.addEventListener(eventName, fn);
  423. const onLastListenerRemove = () => emitter.removeEventListener(eventName, fn);
  424. const result = new Emitter({ onWillAddFirstListener: onFirstListenerAdd, onDidRemoveLastListener: onLastListenerRemove });
  425. return result.event;
  426. }
  427. Event.fromDOMEventEmitter = fromDOMEventEmitter;
  428. /**
  429. * Creates a promise out of an event, using the {@link Event.once} helper.
  430. */
  431. function toPromise(event) {
  432. return new Promise(resolve => once(event)(resolve));
  433. }
  434. Event.toPromise = toPromise;
  435. /**
  436. * Adds a listener to an event and calls the listener immediately with undefined as the event object.
  437. *
  438. * @example
  439. * ```
  440. * // Initialize the UI and update it when dataChangeEvent fires
  441. * runAndSubscribe(dataChangeEvent, () => this._updateUI());
  442. * ```
  443. */
  444. function runAndSubscribe(event, handler) {
  445. handler(undefined);
  446. return event(e => handler(e));
  447. }
  448. Event.runAndSubscribe = runAndSubscribe;
  449. /**
  450. * Adds a listener to an event and calls the listener immediately with undefined as the event object. A new
  451. * {@link DisposableStore} is passed to the listener which is disposed when the returned disposable is disposed.
  452. */
  453. function runAndSubscribeWithStore(event, handler) {
  454. let store = null;
  455. function run(e) {
  456. store === null || store === void 0 ? void 0 : store.dispose();
  457. store = new DisposableStore();
  458. handler(e, store);
  459. }
  460. run(undefined);
  461. const disposable = event(e => run(e));
  462. return toDisposable(() => {
  463. disposable.dispose();
  464. store === null || store === void 0 ? void 0 : store.dispose();
  465. });
  466. }
  467. Event.runAndSubscribeWithStore = runAndSubscribeWithStore;
  468. class EmitterObserver {
  469. constructor(_observable, store) {
  470. this._observable = _observable;
  471. this._counter = 0;
  472. this._hasChanged = false;
  473. const options = {
  474. onWillAddFirstListener: () => {
  475. _observable.addObserver(this);
  476. },
  477. onDidRemoveLastListener: () => {
  478. _observable.removeObserver(this);
  479. }
  480. };
  481. if (!store) {
  482. _addLeakageTraceLogic(options);
  483. }
  484. this.emitter = new Emitter(options);
  485. if (store) {
  486. store.add(this.emitter);
  487. }
  488. }
  489. beginUpdate(_observable) {
  490. // assert(_observable === this.obs);
  491. this._counter++;
  492. }
  493. handlePossibleChange(_observable) {
  494. // assert(_observable === this.obs);
  495. }
  496. handleChange(_observable, _change) {
  497. // assert(_observable === this.obs);
  498. this._hasChanged = true;
  499. }
  500. endUpdate(_observable) {
  501. // assert(_observable === this.obs);
  502. this._counter--;
  503. if (this._counter === 0) {
  504. this._observable.reportChanges();
  505. if (this._hasChanged) {
  506. this._hasChanged = false;
  507. this.emitter.fire(this._observable.get());
  508. }
  509. }
  510. }
  511. }
  512. /**
  513. * Creates an event emitter that is fired when the observable changes.
  514. * Each listeners subscribes to the emitter.
  515. */
  516. function fromObservable(obs, store) {
  517. const observer = new EmitterObserver(obs, store);
  518. return observer.emitter.event;
  519. }
  520. Event.fromObservable = fromObservable;
  521. /**
  522. * Each listener is attached to the observable directly.
  523. */
  524. function fromObservableLight(observable) {
  525. return (listener) => {
  526. let count = 0;
  527. let didChange = false;
  528. const observer = {
  529. beginUpdate() {
  530. count++;
  531. },
  532. endUpdate() {
  533. count--;
  534. if (count === 0) {
  535. observable.reportChanges();
  536. if (didChange) {
  537. didChange = false;
  538. listener();
  539. }
  540. }
  541. },
  542. handlePossibleChange() {
  543. // noop
  544. },
  545. handleChange() {
  546. didChange = true;
  547. }
  548. };
  549. observable.addObserver(observer);
  550. return {
  551. dispose() {
  552. observable.removeObserver(observer);
  553. }
  554. };
  555. };
  556. }
  557. Event.fromObservableLight = fromObservableLight;
  558. })(Event || (Event = {}));
  559. export class EventProfiling {
  560. constructor(name) {
  561. this.listenerCount = 0;
  562. this.invocationCount = 0;
  563. this.elapsedOverall = 0;
  564. this.durations = [];
  565. this.name = `${name}_${EventProfiling._idPool++}`;
  566. EventProfiling.all.add(this);
  567. }
  568. start(listenerCount) {
  569. this._stopWatch = new StopWatch(true);
  570. this.listenerCount = listenerCount;
  571. }
  572. stop() {
  573. if (this._stopWatch) {
  574. const elapsed = this._stopWatch.elapsed();
  575. this.durations.push(elapsed);
  576. this.elapsedOverall += elapsed;
  577. this.invocationCount += 1;
  578. this._stopWatch = undefined;
  579. }
  580. }
  581. }
  582. EventProfiling.all = new Set();
  583. EventProfiling._idPool = 0;
  584. let _globalLeakWarningThreshold = -1;
  585. class LeakageMonitor {
  586. constructor(threshold, name = Math.random().toString(18).slice(2, 5)) {
  587. this.threshold = threshold;
  588. this.name = name;
  589. this._warnCountdown = 0;
  590. }
  591. dispose() {
  592. var _a;
  593. (_a = this._stacks) === null || _a === void 0 ? void 0 : _a.clear();
  594. }
  595. check(stack, listenerCount) {
  596. const threshold = this.threshold;
  597. if (threshold <= 0 || listenerCount < threshold) {
  598. return undefined;
  599. }
  600. if (!this._stacks) {
  601. this._stacks = new Map();
  602. }
  603. const count = (this._stacks.get(stack.value) || 0);
  604. this._stacks.set(stack.value, count + 1);
  605. this._warnCountdown -= 1;
  606. if (this._warnCountdown <= 0) {
  607. // only warn on first exceed and then every time the limit
  608. // is exceeded by 50% again
  609. this._warnCountdown = threshold * 0.5;
  610. // find most frequent listener and print warning
  611. let topStack;
  612. let topCount = 0;
  613. for (const [stack, count] of this._stacks) {
  614. if (!topStack || topCount < count) {
  615. topStack = stack;
  616. topCount = count;
  617. }
  618. }
  619. console.warn(`[${this.name}] potential listener LEAK detected, having ${listenerCount} listeners already. MOST frequent listener (${topCount}):`);
  620. console.warn(topStack);
  621. }
  622. return () => {
  623. const count = (this._stacks.get(stack.value) || 0);
  624. this._stacks.set(stack.value, count - 1);
  625. };
  626. }
  627. }
  628. class Stacktrace {
  629. static create() {
  630. var _a;
  631. return new Stacktrace((_a = new Error().stack) !== null && _a !== void 0 ? _a : '');
  632. }
  633. constructor(value) {
  634. this.value = value;
  635. }
  636. print() {
  637. console.warn(this.value.split('\n').slice(2).join('\n'));
  638. }
  639. }
  640. class Listener {
  641. constructor(callback, callbackThis, stack) {
  642. this.callback = callback;
  643. this.callbackThis = callbackThis;
  644. this.stack = stack;
  645. this.subscription = new SafeDisposable();
  646. }
  647. invoke(e) {
  648. this.callback.call(this.callbackThis, e);
  649. }
  650. }
  651. /**
  652. * The Emitter can be used to expose an Event to the public
  653. * to fire it from the insides.
  654. * Sample:
  655. class Document {
  656. private readonly _onDidChange = new Emitter<(value:string)=>any>();
  657. public onDidChange = this._onDidChange.event;
  658. // getter-style
  659. // get onDidChange(): Event<(value:string)=>any> {
  660. // return this._onDidChange.event;
  661. // }
  662. private _doIt() {
  663. //...
  664. this._onDidChange.fire(value);
  665. }
  666. }
  667. */
  668. export class Emitter {
  669. constructor(options) {
  670. var _a, _b, _c, _d, _e;
  671. this._disposed = false;
  672. this._options = options;
  673. this._leakageMon = _globalLeakWarningThreshold > 0 || ((_a = this._options) === null || _a === void 0 ? void 0 : _a.leakWarningThreshold) ? new LeakageMonitor((_c = (_b = this._options) === null || _b === void 0 ? void 0 : _b.leakWarningThreshold) !== null && _c !== void 0 ? _c : _globalLeakWarningThreshold) : undefined;
  674. this._perfMon = ((_d = this._options) === null || _d === void 0 ? void 0 : _d._profName) ? new EventProfiling(this._options._profName) : undefined;
  675. this._deliveryQueue = (_e = this._options) === null || _e === void 0 ? void 0 : _e.deliveryQueue;
  676. }
  677. dispose() {
  678. var _a, _b, _c, _d;
  679. if (!this._disposed) {
  680. this._disposed = true;
  681. // It is bad to have listeners at the time of disposing an emitter, it is worst to have listeners keep the emitter
  682. // alive via the reference that's embedded in their disposables. Therefore we loop over all remaining listeners and
  683. // unset their subscriptions/disposables. Looping and blaming remaining listeners is done on next tick because the
  684. // the following programming pattern is very popular:
  685. //
  686. // const someModel = this._disposables.add(new ModelObject()); // (1) create and register model
  687. // this._disposables.add(someModel.onDidChange(() => { ... }); // (2) subscribe and register model-event listener
  688. // ...later...
  689. // this._disposables.dispose(); disposes (1) then (2): don't warn after (1) but after the "overall dispose" is done
  690. if (this._listeners) {
  691. if (_enableDisposeWithListenerWarning) {
  692. const listeners = Array.from(this._listeners);
  693. queueMicrotask(() => {
  694. var _a;
  695. for (const listener of listeners) {
  696. if (listener.subscription.isset()) {
  697. listener.subscription.unset();
  698. (_a = listener.stack) === null || _a === void 0 ? void 0 : _a.print();
  699. }
  700. }
  701. });
  702. }
  703. this._listeners.clear();
  704. }
  705. (_a = this._deliveryQueue) === null || _a === void 0 ? void 0 : _a.clear(this);
  706. (_c = (_b = this._options) === null || _b === void 0 ? void 0 : _b.onDidRemoveLastListener) === null || _c === void 0 ? void 0 : _c.call(_b);
  707. (_d = this._leakageMon) === null || _d === void 0 ? void 0 : _d.dispose();
  708. }
  709. }
  710. /**
  711. * For the public to allow to subscribe
  712. * to events from this Emitter
  713. */
  714. get event() {
  715. if (!this._event) {
  716. this._event = (callback, thisArgs, disposables) => {
  717. var _a, _b, _c;
  718. if (!this._listeners) {
  719. this._listeners = new LinkedList();
  720. }
  721. if (this._leakageMon && this._listeners.size > this._leakageMon.threshold * 3) {
  722. console.warn(`[${this._leakageMon.name}] REFUSES to accept new listeners because it exceeded its threshold by far`);
  723. return Disposable.None;
  724. }
  725. const firstListener = this._listeners.isEmpty();
  726. if (firstListener && ((_a = this._options) === null || _a === void 0 ? void 0 : _a.onWillAddFirstListener)) {
  727. this._options.onWillAddFirstListener(this);
  728. }
  729. let removeMonitor;
  730. let stack;
  731. if (this._leakageMon && this._listeners.size >= Math.ceil(this._leakageMon.threshold * 0.2)) {
  732. // check and record this emitter for potential leakage
  733. stack = Stacktrace.create();
  734. removeMonitor = this._leakageMon.check(stack, this._listeners.size + 1);
  735. }
  736. if (_enableDisposeWithListenerWarning) {
  737. stack = stack !== null && stack !== void 0 ? stack : Stacktrace.create();
  738. }
  739. const listener = new Listener(callback, thisArgs, stack);
  740. const removeListener = this._listeners.push(listener);
  741. if (firstListener && ((_b = this._options) === null || _b === void 0 ? void 0 : _b.onDidAddFirstListener)) {
  742. this._options.onDidAddFirstListener(this);
  743. }
  744. if ((_c = this._options) === null || _c === void 0 ? void 0 : _c.onDidAddListener) {
  745. this._options.onDidAddListener(this, callback, thisArgs);
  746. }
  747. const result = listener.subscription.set(() => {
  748. var _a, _b;
  749. removeMonitor === null || removeMonitor === void 0 ? void 0 : removeMonitor();
  750. if (!this._disposed) {
  751. (_b = (_a = this._options) === null || _a === void 0 ? void 0 : _a.onWillRemoveListener) === null || _b === void 0 ? void 0 : _b.call(_a, this);
  752. removeListener();
  753. if (this._options && this._options.onDidRemoveLastListener) {
  754. const hasListeners = (this._listeners && !this._listeners.isEmpty());
  755. if (!hasListeners) {
  756. this._options.onDidRemoveLastListener(this);
  757. }
  758. }
  759. }
  760. });
  761. if (disposables instanceof DisposableStore) {
  762. disposables.add(result);
  763. }
  764. else if (Array.isArray(disposables)) {
  765. disposables.push(result);
  766. }
  767. return result;
  768. };
  769. }
  770. return this._event;
  771. }
  772. /**
  773. * To be kept private to fire an event to
  774. * subscribers
  775. */
  776. fire(event) {
  777. var _a, _b, _c;
  778. if (this._listeners) {
  779. // put all [listener,event]-pairs into delivery queue
  780. // then emit all event. an inner/nested event might be
  781. // the driver of this
  782. if (!this._deliveryQueue) {
  783. this._deliveryQueue = new PrivateEventDeliveryQueue((_a = this._options) === null || _a === void 0 ? void 0 : _a.onListenerError);
  784. }
  785. for (const listener of this._listeners) {
  786. this._deliveryQueue.push(this, listener, event);
  787. }
  788. // start/stop performance insight collection
  789. (_b = this._perfMon) === null || _b === void 0 ? void 0 : _b.start(this._deliveryQueue.size);
  790. this._deliveryQueue.deliver();
  791. (_c = this._perfMon) === null || _c === void 0 ? void 0 : _c.stop();
  792. }
  793. }
  794. hasListeners() {
  795. if (!this._listeners) {
  796. return false;
  797. }
  798. return !this._listeners.isEmpty();
  799. }
  800. }
  801. export class EventDeliveryQueue {
  802. constructor(_onListenerError = onUnexpectedError) {
  803. this._onListenerError = _onListenerError;
  804. this._queue = new LinkedList();
  805. }
  806. get size() {
  807. return this._queue.size;
  808. }
  809. push(emitter, listener, event) {
  810. this._queue.push(new EventDeliveryQueueElement(emitter, listener, event));
  811. }
  812. clear(emitter) {
  813. const newQueue = new LinkedList();
  814. for (const element of this._queue) {
  815. if (element.emitter !== emitter) {
  816. newQueue.push(element);
  817. }
  818. }
  819. this._queue = newQueue;
  820. }
  821. deliver() {
  822. while (this._queue.size > 0) {
  823. const element = this._queue.shift();
  824. try {
  825. element.listener.invoke(element.event);
  826. }
  827. catch (e) {
  828. this._onListenerError(e);
  829. }
  830. }
  831. }
  832. }
  833. /**
  834. * An `EventDeliveryQueue` that is guaranteed to be used by a single `Emitter`.
  835. */
  836. class PrivateEventDeliveryQueue extends EventDeliveryQueue {
  837. clear(emitter) {
  838. // Here we can just clear the entire linked list because
  839. // all elements are guaranteed to belong to this emitter
  840. this._queue.clear();
  841. }
  842. }
  843. class EventDeliveryQueueElement {
  844. constructor(emitter, listener, event) {
  845. this.emitter = emitter;
  846. this.listener = listener;
  847. this.event = event;
  848. }
  849. }
  850. export class PauseableEmitter extends Emitter {
  851. constructor(options) {
  852. super(options);
  853. this._isPaused = 0;
  854. this._eventQueue = new LinkedList();
  855. this._mergeFn = options === null || options === void 0 ? void 0 : options.merge;
  856. }
  857. pause() {
  858. this._isPaused++;
  859. }
  860. resume() {
  861. if (this._isPaused !== 0 && --this._isPaused === 0) {
  862. if (this._mergeFn) {
  863. // use the merge function to create a single composite
  864. // event. make a copy in case firing pauses this emitter
  865. if (this._eventQueue.size > 0) {
  866. const events = Array.from(this._eventQueue);
  867. this._eventQueue.clear();
  868. super.fire(this._mergeFn(events));
  869. }
  870. }
  871. else {
  872. // no merging, fire each event individually and test
  873. // that this emitter isn't paused halfway through
  874. while (!this._isPaused && this._eventQueue.size !== 0) {
  875. super.fire(this._eventQueue.shift());
  876. }
  877. }
  878. }
  879. }
  880. fire(event) {
  881. if (this._listeners) {
  882. if (this._isPaused !== 0) {
  883. this._eventQueue.push(event);
  884. }
  885. else {
  886. super.fire(event);
  887. }
  888. }
  889. }
  890. }
  891. export class DebounceEmitter extends PauseableEmitter {
  892. constructor(options) {
  893. var _a;
  894. super(options);
  895. this._delay = (_a = options.delay) !== null && _a !== void 0 ? _a : 100;
  896. }
  897. fire(event) {
  898. if (!this._handle) {
  899. this.pause();
  900. this._handle = setTimeout(() => {
  901. this._handle = undefined;
  902. this.resume();
  903. }, this._delay);
  904. }
  905. super.fire(event);
  906. }
  907. }
  908. /**
  909. * An emitter which queue all events and then process them at the
  910. * end of the event loop.
  911. */
  912. export class MicrotaskEmitter extends Emitter {
  913. constructor(options) {
  914. super(options);
  915. this._queuedEvents = [];
  916. this._mergeFn = options === null || options === void 0 ? void 0 : options.merge;
  917. }
  918. fire(event) {
  919. if (!this.hasListeners()) {
  920. return;
  921. }
  922. this._queuedEvents.push(event);
  923. if (this._queuedEvents.length === 1) {
  924. queueMicrotask(() => {
  925. if (this._mergeFn) {
  926. super.fire(this._mergeFn(this._queuedEvents));
  927. }
  928. else {
  929. this._queuedEvents.forEach(e => super.fire(e));
  930. }
  931. this._queuedEvents = [];
  932. });
  933. }
  934. }
  935. }
  936. export class EventMultiplexer {
  937. constructor() {
  938. this.hasListeners = false;
  939. this.events = [];
  940. this.emitter = new Emitter({
  941. onWillAddFirstListener: () => this.onFirstListenerAdd(),
  942. onDidRemoveLastListener: () => this.onLastListenerRemove()
  943. });
  944. }
  945. get event() {
  946. return this.emitter.event;
  947. }
  948. add(event) {
  949. const e = { event: event, listener: null };
  950. this.events.push(e);
  951. if (this.hasListeners) {
  952. this.hook(e);
  953. }
  954. const dispose = () => {
  955. if (this.hasListeners) {
  956. this.unhook(e);
  957. }
  958. const idx = this.events.indexOf(e);
  959. this.events.splice(idx, 1);
  960. };
  961. return toDisposable(onceFn(dispose));
  962. }
  963. onFirstListenerAdd() {
  964. this.hasListeners = true;
  965. this.events.forEach(e => this.hook(e));
  966. }
  967. onLastListenerRemove() {
  968. this.hasListeners = false;
  969. this.events.forEach(e => this.unhook(e));
  970. }
  971. hook(e) {
  972. e.listener = e.event(r => this.emitter.fire(r));
  973. }
  974. unhook(e) {
  975. if (e.listener) {
  976. e.listener.dispose();
  977. }
  978. e.listener = null;
  979. }
  980. dispose() {
  981. this.emitter.dispose();
  982. }
  983. }
  984. /**
  985. * The EventBufferer is useful in situations in which you want
  986. * to delay firing your events during some code.
  987. * You can wrap that code and be sure that the event will not
  988. * be fired during that wrap.
  989. *
  990. * ```
  991. * const emitter: Emitter;
  992. * const delayer = new EventDelayer();
  993. * const delayedEvent = delayer.wrapEvent(emitter.event);
  994. *
  995. * delayedEvent(console.log);
  996. *
  997. * delayer.bufferEvents(() => {
  998. * emitter.fire(); // event will not be fired yet
  999. * });
  1000. *
  1001. * // event will only be fired at this point
  1002. * ```
  1003. */
  1004. export class EventBufferer {
  1005. constructor() {
  1006. this.buffers = [];
  1007. }
  1008. wrapEvent(event) {
  1009. return (listener, thisArgs, disposables) => {
  1010. return event(i => {
  1011. const buffer = this.buffers[this.buffers.length - 1];
  1012. if (buffer) {
  1013. buffer.push(() => listener.call(thisArgs, i));
  1014. }
  1015. else {
  1016. listener.call(thisArgs, i);
  1017. }
  1018. }, undefined, disposables);
  1019. };
  1020. }
  1021. bufferEvents(fn) {
  1022. const buffer = [];
  1023. this.buffers.push(buffer);
  1024. const r = fn();
  1025. this.buffers.pop();
  1026. buffer.forEach(flush => flush());
  1027. return r;
  1028. }
  1029. }
  1030. /**
  1031. * A Relay is an event forwarder which functions as a replugabble event pipe.
  1032. * Once created, you can connect an input event to it and it will simply forward
  1033. * events from that input event through its own `event` property. The `input`
  1034. * can be changed at any point in time.
  1035. */
  1036. export class Relay {
  1037. constructor() {
  1038. this.listening = false;
  1039. this.inputEvent = Event.None;
  1040. this.inputEventListener = Disposable.None;
  1041. this.emitter = new Emitter({
  1042. onDidAddFirstListener: () => {
  1043. this.listening = true;
  1044. this.inputEventListener = this.inputEvent(this.emitter.fire, this.emitter);
  1045. },
  1046. onDidRemoveLastListener: () => {
  1047. this.listening = false;
  1048. this.inputEventListener.dispose();
  1049. }
  1050. });
  1051. this.event = this.emitter.event;
  1052. }
  1053. set input(event) {
  1054. this.inputEvent = event;
  1055. if (this.listening) {
  1056. this.inputEventListener.dispose();
  1057. this.inputEventListener = event(this.emitter.fire, this.emitter);
  1058. }
  1059. }
  1060. dispose() {
  1061. this.inputEventListener.dispose();
  1062. this.emitter.dispose();
  1063. }
  1064. }