event.js 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584
  1. import { onUnexpectedError } from './errors.js';
  2. import { combinedDisposable, Disposable, DisposableStore, toDisposable } from './lifecycle.js';
  3. import { LinkedList } from './linkedList.js';
  4. import { StopWatch } from './stopwatch.js';
  5. export var Event;
  6. (function (Event) {
  7. Event.None = () => Disposable.None;
  8. /**
  9. * Given an event, returns another event which only fires once.
  10. */
  11. function once(event) {
  12. return (listener, thisArgs = null, disposables) => {
  13. // we need this, in case the event fires during the listener call
  14. let didFire = false;
  15. let result;
  16. result = event(e => {
  17. if (didFire) {
  18. return;
  19. }
  20. else if (result) {
  21. result.dispose();
  22. }
  23. else {
  24. didFire = true;
  25. }
  26. return listener.call(thisArgs, e);
  27. }, null, disposables);
  28. if (didFire) {
  29. result.dispose();
  30. }
  31. return result;
  32. };
  33. }
  34. Event.once = once;
  35. /**
  36. * @deprecated DO NOT use, this leaks memory
  37. */
  38. function map(event, map) {
  39. return snapshot((listener, thisArgs = null, disposables) => event(i => listener.call(thisArgs, map(i)), null, disposables));
  40. }
  41. Event.map = map;
  42. /**
  43. * @deprecated DO NOT use, this leaks memory
  44. */
  45. function forEach(event, each) {
  46. return snapshot((listener, thisArgs = null, disposables) => event(i => { each(i); listener.call(thisArgs, i); }, null, disposables));
  47. }
  48. Event.forEach = forEach;
  49. function filter(event, filter) {
  50. return snapshot((listener, thisArgs = null, disposables) => event(e => filter(e) && listener.call(thisArgs, e), null, disposables));
  51. }
  52. Event.filter = filter;
  53. /**
  54. * Given an event, returns the same event but typed as `Event<void>`.
  55. */
  56. function signal(event) {
  57. return event;
  58. }
  59. Event.signal = signal;
  60. function any(...events) {
  61. return (listener, thisArgs = null, disposables) => combinedDisposable(...events.map(event => event(e => listener.call(thisArgs, e), null, disposables)));
  62. }
  63. Event.any = any;
  64. /**
  65. * @deprecated DO NOT use, this leaks memory
  66. */
  67. function reduce(event, merge, initial) {
  68. let output = initial;
  69. return map(event, e => {
  70. output = merge(output, e);
  71. return output;
  72. });
  73. }
  74. Event.reduce = reduce;
  75. /**
  76. * @deprecated DO NOT use, this leaks memory
  77. */
  78. function snapshot(event) {
  79. let listener;
  80. const emitter = new Emitter({
  81. onFirstListenerAdd() {
  82. listener = event(emitter.fire, emitter);
  83. },
  84. onLastListenerRemove() {
  85. listener.dispose();
  86. }
  87. });
  88. return emitter.event;
  89. }
  90. /**
  91. * @deprecated DO NOT use, this leaks memory
  92. */
  93. function debounce(event, merge, delay = 100, leading = false, leakWarningThreshold) {
  94. let subscription;
  95. let output = undefined;
  96. let handle = undefined;
  97. let numDebouncedCalls = 0;
  98. const emitter = new Emitter({
  99. leakWarningThreshold,
  100. onFirstListenerAdd() {
  101. subscription = event(cur => {
  102. numDebouncedCalls++;
  103. output = merge(output, cur);
  104. if (leading && !handle) {
  105. emitter.fire(output);
  106. output = undefined;
  107. }
  108. clearTimeout(handle);
  109. handle = setTimeout(() => {
  110. const _output = output;
  111. output = undefined;
  112. handle = undefined;
  113. if (!leading || numDebouncedCalls > 1) {
  114. emitter.fire(_output);
  115. }
  116. numDebouncedCalls = 0;
  117. }, delay);
  118. });
  119. },
  120. onLastListenerRemove() {
  121. subscription.dispose();
  122. }
  123. });
  124. return emitter.event;
  125. }
  126. Event.debounce = debounce;
  127. /**
  128. * @deprecated DO NOT use, this leaks memory
  129. */
  130. function latch(event, equals = (a, b) => a === b) {
  131. let firstCall = true;
  132. let cache;
  133. return filter(event, value => {
  134. const shouldEmit = firstCall || !equals(value, cache);
  135. firstCall = false;
  136. cache = value;
  137. return shouldEmit;
  138. });
  139. }
  140. Event.latch = latch;
  141. /**
  142. * @deprecated DO NOT use, this leaks memory
  143. */
  144. function split(event, isT) {
  145. return [
  146. Event.filter(event, isT),
  147. Event.filter(event, e => !isT(e)),
  148. ];
  149. }
  150. Event.split = split;
  151. /**
  152. * @deprecated DO NOT use, this leaks memory
  153. */
  154. function buffer(event, flushAfterTimeout = false, _buffer = []) {
  155. let buffer = _buffer.slice();
  156. let listener = event(e => {
  157. if (buffer) {
  158. buffer.push(e);
  159. }
  160. else {
  161. emitter.fire(e);
  162. }
  163. });
  164. const flush = () => {
  165. if (buffer) {
  166. buffer.forEach(e => emitter.fire(e));
  167. }
  168. buffer = null;
  169. };
  170. const emitter = new Emitter({
  171. onFirstListenerAdd() {
  172. if (!listener) {
  173. listener = event(e => emitter.fire(e));
  174. }
  175. },
  176. onFirstListenerDidAdd() {
  177. if (buffer) {
  178. if (flushAfterTimeout) {
  179. setTimeout(flush);
  180. }
  181. else {
  182. flush();
  183. }
  184. }
  185. },
  186. onLastListenerRemove() {
  187. if (listener) {
  188. listener.dispose();
  189. }
  190. listener = null;
  191. }
  192. });
  193. return emitter.event;
  194. }
  195. Event.buffer = buffer;
  196. class ChainableEvent {
  197. constructor(event) {
  198. this.event = event;
  199. }
  200. map(fn) {
  201. return new ChainableEvent(map(this.event, fn));
  202. }
  203. forEach(fn) {
  204. return new ChainableEvent(forEach(this.event, fn));
  205. }
  206. filter(fn) {
  207. return new ChainableEvent(filter(this.event, fn));
  208. }
  209. reduce(merge, initial) {
  210. return new ChainableEvent(reduce(this.event, merge, initial));
  211. }
  212. latch() {
  213. return new ChainableEvent(latch(this.event));
  214. }
  215. debounce(merge, delay = 100, leading = false, leakWarningThreshold) {
  216. return new ChainableEvent(debounce(this.event, merge, delay, leading, leakWarningThreshold));
  217. }
  218. on(listener, thisArgs, disposables) {
  219. return this.event(listener, thisArgs, disposables);
  220. }
  221. once(listener, thisArgs, disposables) {
  222. return once(this.event)(listener, thisArgs, disposables);
  223. }
  224. }
  225. /**
  226. * @deprecated DO NOT use, this leaks memory
  227. */
  228. function chain(event) {
  229. return new ChainableEvent(event);
  230. }
  231. Event.chain = chain;
  232. function fromNodeEventEmitter(emitter, eventName, map = id => id) {
  233. const fn = (...args) => result.fire(map(...args));
  234. const onFirstListenerAdd = () => emitter.on(eventName, fn);
  235. const onLastListenerRemove = () => emitter.removeListener(eventName, fn);
  236. const result = new Emitter({ onFirstListenerAdd, onLastListenerRemove });
  237. return result.event;
  238. }
  239. Event.fromNodeEventEmitter = fromNodeEventEmitter;
  240. function fromDOMEventEmitter(emitter, eventName, map = id => id) {
  241. const fn = (...args) => result.fire(map(...args));
  242. const onFirstListenerAdd = () => emitter.addEventListener(eventName, fn);
  243. const onLastListenerRemove = () => emitter.removeEventListener(eventName, fn);
  244. const result = new Emitter({ onFirstListenerAdd, onLastListenerRemove });
  245. return result.event;
  246. }
  247. Event.fromDOMEventEmitter = fromDOMEventEmitter;
  248. function toPromise(event) {
  249. return new Promise(resolve => once(event)(resolve));
  250. }
  251. Event.toPromise = toPromise;
  252. })(Event || (Event = {}));
  253. class EventProfiling {
  254. constructor(name) {
  255. this._listenerCount = 0;
  256. this._invocationCount = 0;
  257. this._elapsedOverall = 0;
  258. this._name = `${name}_${EventProfiling._idPool++}`;
  259. }
  260. start(listenerCount) {
  261. this._stopWatch = new StopWatch(true);
  262. this._listenerCount = listenerCount;
  263. }
  264. stop() {
  265. if (this._stopWatch) {
  266. const elapsed = this._stopWatch.elapsed();
  267. this._elapsedOverall += elapsed;
  268. this._invocationCount += 1;
  269. console.info(`did FIRE ${this._name}: elapsed_ms: ${elapsed.toFixed(5)}, listener: ${this._listenerCount} (elapsed_overall: ${this._elapsedOverall.toFixed(2)}, invocations: ${this._invocationCount})`);
  270. this._stopWatch = undefined;
  271. }
  272. }
  273. }
  274. EventProfiling._idPool = 0;
  275. let _globalLeakWarningThreshold = -1;
  276. class LeakageMonitor {
  277. constructor(customThreshold, name = Math.random().toString(18).slice(2, 5)) {
  278. this.customThreshold = customThreshold;
  279. this.name = name;
  280. this._warnCountdown = 0;
  281. }
  282. dispose() {
  283. if (this._stacks) {
  284. this._stacks.clear();
  285. }
  286. }
  287. check(listenerCount) {
  288. let threshold = _globalLeakWarningThreshold;
  289. if (typeof this.customThreshold === 'number') {
  290. threshold = this.customThreshold;
  291. }
  292. if (threshold <= 0 || listenerCount < threshold) {
  293. return undefined;
  294. }
  295. if (!this._stacks) {
  296. this._stacks = new Map();
  297. }
  298. const stack = new Error().stack.split('\n').slice(3).join('\n');
  299. const count = (this._stacks.get(stack) || 0);
  300. this._stacks.set(stack, count + 1);
  301. this._warnCountdown -= 1;
  302. if (this._warnCountdown <= 0) {
  303. // only warn on first exceed and then every time the limit
  304. // is exceeded by 50% again
  305. this._warnCountdown = threshold * 0.5;
  306. // find most frequent listener and print warning
  307. let topStack;
  308. let topCount = 0;
  309. for (const [stack, count] of this._stacks) {
  310. if (!topStack || topCount < count) {
  311. topStack = stack;
  312. topCount = count;
  313. }
  314. }
  315. console.warn(`[${this.name}] potential listener LEAK detected, having ${listenerCount} listeners already. MOST frequent listener (${topCount}):`);
  316. console.warn(topStack);
  317. }
  318. return () => {
  319. const count = (this._stacks.get(stack) || 0);
  320. this._stacks.set(stack, count - 1);
  321. };
  322. }
  323. }
  324. /**
  325. * The Emitter can be used to expose an Event to the public
  326. * to fire it from the insides.
  327. * Sample:
  328. class Document {
  329. private readonly _onDidChange = new Emitter<(value:string)=>any>();
  330. public onDidChange = this._onDidChange.event;
  331. // getter-style
  332. // get onDidChange(): Event<(value:string)=>any> {
  333. // return this._onDidChange.event;
  334. // }
  335. private _doIt() {
  336. //...
  337. this._onDidChange.fire(value);
  338. }
  339. }
  340. */
  341. export class Emitter {
  342. constructor(options) {
  343. var _a;
  344. this._disposed = false;
  345. this._options = options;
  346. this._leakageMon = _globalLeakWarningThreshold > 0 ? new LeakageMonitor(this._options && this._options.leakWarningThreshold) : undefined;
  347. this._perfMon = ((_a = this._options) === null || _a === void 0 ? void 0 : _a._profName) ? new EventProfiling(this._options._profName) : undefined;
  348. }
  349. /**
  350. * For the public to allow to subscribe
  351. * to events from this Emitter
  352. */
  353. get event() {
  354. if (!this._event) {
  355. this._event = (listener, thisArgs, disposables) => {
  356. var _a;
  357. if (!this._listeners) {
  358. this._listeners = new LinkedList();
  359. }
  360. const firstListener = this._listeners.isEmpty();
  361. if (firstListener && this._options && this._options.onFirstListenerAdd) {
  362. this._options.onFirstListenerAdd(this);
  363. }
  364. const remove = this._listeners.push(!thisArgs ? listener : [listener, thisArgs]);
  365. if (firstListener && this._options && this._options.onFirstListenerDidAdd) {
  366. this._options.onFirstListenerDidAdd(this);
  367. }
  368. if (this._options && this._options.onListenerDidAdd) {
  369. this._options.onListenerDidAdd(this, listener, thisArgs);
  370. }
  371. // check and record this emitter for potential leakage
  372. const removeMonitor = (_a = this._leakageMon) === null || _a === void 0 ? void 0 : _a.check(this._listeners.size);
  373. const result = toDisposable(() => {
  374. if (removeMonitor) {
  375. removeMonitor();
  376. }
  377. if (!this._disposed) {
  378. remove();
  379. if (this._options && this._options.onLastListenerRemove) {
  380. const hasListeners = (this._listeners && !this._listeners.isEmpty());
  381. if (!hasListeners) {
  382. this._options.onLastListenerRemove(this);
  383. }
  384. }
  385. }
  386. });
  387. if (disposables instanceof DisposableStore) {
  388. disposables.add(result);
  389. }
  390. else if (Array.isArray(disposables)) {
  391. disposables.push(result);
  392. }
  393. return result;
  394. };
  395. }
  396. return this._event;
  397. }
  398. /**
  399. * To be kept private to fire an event to
  400. * subscribers
  401. */
  402. fire(event) {
  403. var _a, _b;
  404. if (this._listeners) {
  405. // put all [listener,event]-pairs into delivery queue
  406. // then emit all event. an inner/nested event might be
  407. // the driver of this
  408. if (!this._deliveryQueue) {
  409. this._deliveryQueue = new LinkedList();
  410. }
  411. for (let listener of this._listeners) {
  412. this._deliveryQueue.push([listener, event]);
  413. }
  414. // start/stop performance insight collection
  415. (_a = this._perfMon) === null || _a === void 0 ? void 0 : _a.start(this._deliveryQueue.size);
  416. while (this._deliveryQueue.size > 0) {
  417. const [listener, event] = this._deliveryQueue.shift();
  418. try {
  419. if (typeof listener === 'function') {
  420. listener.call(undefined, event);
  421. }
  422. else {
  423. listener[0].call(listener[1], event);
  424. }
  425. }
  426. catch (e) {
  427. onUnexpectedError(e);
  428. }
  429. }
  430. (_b = this._perfMon) === null || _b === void 0 ? void 0 : _b.stop();
  431. }
  432. }
  433. dispose() {
  434. var _a, _b, _c, _d, _e;
  435. if (!this._disposed) {
  436. this._disposed = true;
  437. (_a = this._listeners) === null || _a === void 0 ? void 0 : _a.clear();
  438. (_b = this._deliveryQueue) === null || _b === void 0 ? void 0 : _b.clear();
  439. (_d = (_c = this._options) === null || _c === void 0 ? void 0 : _c.onLastListenerRemove) === null || _d === void 0 ? void 0 : _d.call(_c);
  440. (_e = this._leakageMon) === null || _e === void 0 ? void 0 : _e.dispose();
  441. }
  442. }
  443. }
  444. export class PauseableEmitter extends Emitter {
  445. constructor(options) {
  446. super(options);
  447. this._isPaused = 0;
  448. this._eventQueue = new LinkedList();
  449. this._mergeFn = options === null || options === void 0 ? void 0 : options.merge;
  450. }
  451. pause() {
  452. this._isPaused++;
  453. }
  454. resume() {
  455. if (this._isPaused !== 0 && --this._isPaused === 0) {
  456. if (this._mergeFn) {
  457. // use the merge function to create a single composite
  458. // event. make a copy in case firing pauses this emitter
  459. const events = Array.from(this._eventQueue);
  460. this._eventQueue.clear();
  461. super.fire(this._mergeFn(events));
  462. }
  463. else {
  464. // no merging, fire each event individually and test
  465. // that this emitter isn't paused halfway through
  466. while (!this._isPaused && this._eventQueue.size !== 0) {
  467. super.fire(this._eventQueue.shift());
  468. }
  469. }
  470. }
  471. }
  472. fire(event) {
  473. if (this._listeners) {
  474. if (this._isPaused !== 0) {
  475. this._eventQueue.push(event);
  476. }
  477. else {
  478. super.fire(event);
  479. }
  480. }
  481. }
  482. }
  483. export class DebounceEmitter extends PauseableEmitter {
  484. constructor(options) {
  485. var _a;
  486. super(options);
  487. this._delay = (_a = options.delay) !== null && _a !== void 0 ? _a : 100;
  488. }
  489. fire(event) {
  490. if (!this._handle) {
  491. this.pause();
  492. this._handle = setTimeout(() => {
  493. this._handle = undefined;
  494. this.resume();
  495. }, this._delay);
  496. }
  497. super.fire(event);
  498. }
  499. }
  500. /**
  501. * The EventBufferer is useful in situations in which you want
  502. * to delay firing your events during some code.
  503. * You can wrap that code and be sure that the event will not
  504. * be fired during that wrap.
  505. *
  506. * ```
  507. * const emitter: Emitter;
  508. * const delayer = new EventDelayer();
  509. * const delayedEvent = delayer.wrapEvent(emitter.event);
  510. *
  511. * delayedEvent(console.log);
  512. *
  513. * delayer.bufferEvents(() => {
  514. * emitter.fire(); // event will not be fired yet
  515. * });
  516. *
  517. * // event will only be fired at this point
  518. * ```
  519. */
  520. export class EventBufferer {
  521. constructor() {
  522. this.buffers = [];
  523. }
  524. wrapEvent(event) {
  525. return (listener, thisArgs, disposables) => {
  526. return event(i => {
  527. const buffer = this.buffers[this.buffers.length - 1];
  528. if (buffer) {
  529. buffer.push(() => listener.call(thisArgs, i));
  530. }
  531. else {
  532. listener.call(thisArgs, i);
  533. }
  534. }, undefined, disposables);
  535. };
  536. }
  537. bufferEvents(fn) {
  538. const buffer = [];
  539. this.buffers.push(buffer);
  540. const r = fn();
  541. this.buffers.pop();
  542. buffer.forEach(flush => flush());
  543. return r;
  544. }
  545. }
  546. /**
  547. * A Relay is an event forwarder which functions as a replugabble event pipe.
  548. * Once created, you can connect an input event to it and it will simply forward
  549. * events from that input event through its own `event` property. The `input`
  550. * can be changed at any point in time.
  551. */
  552. export class Relay {
  553. constructor() {
  554. this.listening = false;
  555. this.inputEvent = Event.None;
  556. this.inputEventListener = Disposable.None;
  557. this.emitter = new Emitter({
  558. onFirstListenerDidAdd: () => {
  559. this.listening = true;
  560. this.inputEventListener = this.inputEvent(this.emitter.fire, this.emitter);
  561. },
  562. onLastListenerRemove: () => {
  563. this.listening = false;
  564. this.inputEventListener.dispose();
  565. }
  566. });
  567. this.event = this.emitter.event;
  568. }
  569. set input(event) {
  570. this.inputEvent = event;
  571. if (this.listening) {
  572. this.inputEventListener.dispose();
  573. this.inputEventListener = event(this.emitter.fire, this.emitter);
  574. }
  575. }
  576. dispose() {
  577. this.inputEventListener.dispose();
  578. this.emitter.dispose();
  579. }
  580. }