@xterm/xterm 5.6.0-beta.7 → 5.6.0-beta.70

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (118) hide show
  1. package/README.md +6 -3
  2. package/css/xterm.css +71 -4
  3. package/lib/xterm.js +1 -1
  4. package/lib/xterm.js.map +1 -1
  5. package/lib/xterm.mjs +53 -0
  6. package/lib/xterm.mjs.map +7 -0
  7. package/package.json +43 -33
  8. package/src/browser/AccessibilityManager.ts +53 -25
  9. package/src/browser/{Terminal.ts → CoreBrowserTerminal.ts} +135 -146
  10. package/src/browser/Linkifier.ts +26 -14
  11. package/src/browser/LocalizableStrings.ts +15 -4
  12. package/src/browser/{Types.d.ts → Types.ts} +67 -15
  13. package/src/browser/Viewport.ts +143 -370
  14. package/src/browser/decorations/BufferDecorationRenderer.ts +14 -9
  15. package/src/browser/decorations/OverviewRulerRenderer.ts +40 -44
  16. package/src/browser/public/Terminal.ts +25 -19
  17. package/src/browser/renderer/dom/DomRenderer.ts +14 -16
  18. package/src/browser/renderer/shared/CharAtlasUtils.ts +4 -0
  19. package/src/browser/renderer/shared/CustomGlyphs.ts +6 -0
  20. package/src/browser/renderer/shared/DevicePixelObserver.ts +1 -2
  21. package/src/browser/renderer/shared/TextureAtlas.ts +3 -3
  22. package/src/browser/renderer/shared/{Types.d.ts → Types.ts} +4 -4
  23. package/src/browser/services/CharSizeService.ts +6 -6
  24. package/src/browser/services/CoreBrowserService.ts +15 -15
  25. package/src/browser/services/LinkProviderService.ts +2 -2
  26. package/src/browser/services/RenderService.ts +20 -20
  27. package/src/browser/services/SelectionService.ts +8 -8
  28. package/src/browser/services/Services.ts +13 -13
  29. package/src/browser/services/ThemeService.ts +17 -56
  30. package/src/browser/shared/Constants.ts +8 -0
  31. package/src/common/CircularList.ts +5 -5
  32. package/src/common/CoreTerminal.ts +35 -41
  33. package/src/common/InputHandler.ts +34 -28
  34. package/src/common/{Types.d.ts → Types.ts} +11 -17
  35. package/src/common/buffer/Buffer.ts +5 -1
  36. package/src/common/buffer/BufferSet.ts +5 -5
  37. package/src/common/buffer/Marker.ts +4 -4
  38. package/src/common/buffer/{Types.d.ts → Types.ts} +2 -2
  39. package/src/common/input/WriteBuffer.ts +3 -3
  40. package/src/common/parser/EscapeSequenceParser.ts +4 -4
  41. package/src/common/public/BufferNamespaceApi.ts +3 -3
  42. package/src/common/services/BufferService.ts +7 -7
  43. package/src/common/services/CoreMouseService.ts +5 -3
  44. package/src/common/services/CoreService.ts +6 -6
  45. package/src/common/services/DecorationService.ts +8 -9
  46. package/src/common/services/LogService.ts +2 -2
  47. package/src/common/services/OptionsService.ts +5 -5
  48. package/src/common/services/Services.ts +24 -17
  49. package/src/common/services/UnicodeService.ts +2 -2
  50. package/src/vs/base/browser/browser.ts +141 -0
  51. package/src/vs/base/browser/canIUse.ts +49 -0
  52. package/src/vs/base/browser/dom.ts +2369 -0
  53. package/src/vs/base/browser/fastDomNode.ts +316 -0
  54. package/src/vs/base/browser/globalPointerMoveMonitor.ts +112 -0
  55. package/src/vs/base/browser/iframe.ts +135 -0
  56. package/src/vs/base/browser/keyboardEvent.ts +213 -0
  57. package/src/vs/base/browser/mouseEvent.ts +229 -0
  58. package/src/vs/base/browser/touch.ts +372 -0
  59. package/src/vs/base/browser/ui/scrollbar/abstractScrollbar.ts +303 -0
  60. package/src/vs/base/browser/ui/scrollbar/horizontalScrollbar.ts +114 -0
  61. package/src/vs/base/browser/ui/scrollbar/scrollableElement.ts +720 -0
  62. package/src/vs/base/browser/ui/scrollbar/scrollableElementOptions.ts +165 -0
  63. package/src/vs/base/browser/ui/scrollbar/scrollbarArrow.ts +114 -0
  64. package/src/vs/base/browser/ui/scrollbar/scrollbarState.ts +243 -0
  65. package/src/vs/base/browser/ui/scrollbar/scrollbarVisibilityController.ts +118 -0
  66. package/src/vs/base/browser/ui/scrollbar/verticalScrollbar.ts +116 -0
  67. package/src/vs/base/browser/ui/widget.ts +57 -0
  68. package/src/vs/base/browser/window.ts +14 -0
  69. package/src/vs/base/common/arrays.ts +887 -0
  70. package/src/vs/base/common/arraysFind.ts +202 -0
  71. package/src/vs/base/common/assert.ts +71 -0
  72. package/src/vs/base/common/async.ts +1992 -0
  73. package/src/vs/base/common/cancellation.ts +148 -0
  74. package/src/vs/base/common/charCode.ts +450 -0
  75. package/src/vs/base/common/collections.ts +140 -0
  76. package/src/vs/base/common/decorators.ts +130 -0
  77. package/src/vs/base/common/equals.ts +146 -0
  78. package/src/vs/base/common/errors.ts +303 -0
  79. package/src/vs/base/common/event.ts +1778 -0
  80. package/src/vs/base/common/functional.ts +32 -0
  81. package/src/vs/base/common/hash.ts +316 -0
  82. package/src/vs/base/common/iterator.ts +159 -0
  83. package/src/vs/base/common/keyCodes.ts +526 -0
  84. package/src/vs/base/common/keybindings.ts +284 -0
  85. package/src/vs/base/common/lazy.ts +47 -0
  86. package/src/vs/base/common/lifecycle.ts +801 -0
  87. package/src/vs/base/common/linkedList.ts +142 -0
  88. package/src/vs/base/common/map.ts +202 -0
  89. package/src/vs/base/common/numbers.ts +98 -0
  90. package/src/vs/base/common/observable.ts +76 -0
  91. package/src/vs/base/common/observableInternal/api.ts +31 -0
  92. package/src/vs/base/common/observableInternal/autorun.ts +281 -0
  93. package/src/vs/base/common/observableInternal/base.ts +489 -0
  94. package/src/vs/base/common/observableInternal/debugName.ts +145 -0
  95. package/src/vs/base/common/observableInternal/derived.ts +428 -0
  96. package/src/vs/base/common/observableInternal/lazyObservableValue.ts +146 -0
  97. package/src/vs/base/common/observableInternal/logging.ts +328 -0
  98. package/src/vs/base/common/observableInternal/promise.ts +209 -0
  99. package/src/vs/base/common/observableInternal/utils.ts +610 -0
  100. package/src/vs/base/common/platform.ts +281 -0
  101. package/src/vs/base/common/scrollable.ts +522 -0
  102. package/src/vs/base/common/sequence.ts +34 -0
  103. package/src/vs/base/common/stopwatch.ts +43 -0
  104. package/src/vs/base/common/strings.ts +557 -0
  105. package/src/vs/base/common/symbols.ts +9 -0
  106. package/src/vs/base/common/uint.ts +59 -0
  107. package/src/vs/patches/nls.ts +90 -0
  108. package/src/vs/typings/base-common.d.ts +20 -0
  109. package/src/vs/typings/require.d.ts +42 -0
  110. package/src/vs/typings/thenable.d.ts +12 -0
  111. package/src/vs/typings/vscode-globals-nls.d.ts +36 -0
  112. package/src/vs/typings/vscode-globals-product.d.ts +33 -0
  113. package/typings/xterm.d.ts +59 -15
  114. package/src/browser/Lifecycle.ts +0 -33
  115. package/src/common/EventEmitter.ts +0 -78
  116. package/src/common/Lifecycle.ts +0 -108
  117. /package/src/browser/selection/{Types.d.ts → Types.ts} +0 -0
  118. /package/src/common/parser/{Types.d.ts → Types.ts} +0 -0
@@ -0,0 +1,1992 @@
1
+ /*---------------------------------------------------------------------------------------------
2
+ * Copyright (c) Microsoft Corporation. All rights reserved.
3
+ * Licensed under the MIT License. See License.txt in the project root for license information.
4
+ *--------------------------------------------------------------------------------------------*/
5
+
6
+ import { CancellationToken, CancellationTokenSource } from 'vs/base/common/cancellation';
7
+ import { BugIndicatingError, CancellationError } from 'vs/base/common/errors';
8
+ import { Emitter, Event } from 'vs/base/common/event';
9
+ import { Disposable, DisposableStore, IDisposable, MutableDisposable, toDisposable } from 'vs/base/common/lifecycle';
10
+ import { setTimeout0 } from 'vs/base/common/platform';
11
+ import { MicrotaskDelay } from './symbols';
12
+ import { Lazy } from 'vs/base/common/lazy';
13
+
14
+ export function isThenable<T>(obj: unknown): obj is Promise<T> {
15
+ return !!obj && typeof (obj as unknown as Promise<T>).then === 'function';
16
+ }
17
+
18
+ export interface CancelablePromise<T> extends Promise<T> {
19
+ cancel(): void;
20
+ }
21
+
22
+ export function createCancelablePromise<T>(callback: (token: CancellationToken) => Promise<T>): CancelablePromise<T> {
23
+ const source = new CancellationTokenSource();
24
+
25
+ const thenable = callback(source.token);
26
+ const promise = new Promise<T>((resolve, reject) => {
27
+ const subscription = source.token.onCancellationRequested(() => {
28
+ subscription.dispose();
29
+ reject(new CancellationError());
30
+ });
31
+ Promise.resolve(thenable).then(value => {
32
+ subscription.dispose();
33
+ source.dispose();
34
+ resolve(value);
35
+ }, err => {
36
+ subscription.dispose();
37
+ source.dispose();
38
+ reject(err);
39
+ });
40
+ });
41
+
42
+ return <CancelablePromise<T>>new class {
43
+ cancel() {
44
+ source.cancel();
45
+ source.dispose();
46
+ }
47
+ then<TResult1 = T, TResult2 = never>(resolve?: ((value: T) => TResult1 | Promise<TResult1>) | undefined | null, reject?: ((reason: any) => TResult2 | Promise<TResult2>) | undefined | null): Promise<TResult1 | TResult2> {
48
+ return promise.then(resolve, reject);
49
+ }
50
+ catch<TResult = never>(reject?: ((reason: any) => TResult | Promise<TResult>) | undefined | null): Promise<T | TResult> {
51
+ return this.then(undefined, reject);
52
+ }
53
+ finally(onfinally?: (() => void) | undefined | null): Promise<T> {
54
+ return promise.finally(onfinally);
55
+ }
56
+ };
57
+ }
58
+
59
+ /**
60
+ * Returns a promise that resolves with `undefined` as soon as the passed token is cancelled.
61
+ * @see {@link raceCancellationError}
62
+ */
63
+ export function raceCancellation<T>(promise: Promise<T>, token: CancellationToken): Promise<T | undefined>;
64
+
65
+ /**
66
+ * Returns a promise that resolves with `defaultValue` as soon as the passed token is cancelled.
67
+ * @see {@link raceCancellationError}
68
+ */
69
+ export function raceCancellation<T>(promise: Promise<T>, token: CancellationToken, defaultValue: T): Promise<T>;
70
+
71
+ export function raceCancellation<T>(promise: Promise<T>, token: CancellationToken, defaultValue?: T): Promise<T | undefined> {
72
+ return new Promise((resolve, reject) => {
73
+ const ref = token.onCancellationRequested(() => {
74
+ ref.dispose();
75
+ resolve(defaultValue);
76
+ });
77
+ promise.then(resolve, reject).finally(() => ref.dispose());
78
+ });
79
+ }
80
+
81
+ /**
82
+ * Returns a promise that rejects with an {@CancellationError} as soon as the passed token is cancelled.
83
+ * @see {@link raceCancellation}
84
+ */
85
+ export function raceCancellationError<T>(promise: Promise<T>, token: CancellationToken): Promise<T> {
86
+ return new Promise((resolve, reject) => {
87
+ const ref = token.onCancellationRequested(() => {
88
+ ref.dispose();
89
+ reject(new CancellationError());
90
+ });
91
+ promise.then(resolve, reject).finally(() => ref.dispose());
92
+ });
93
+ }
94
+
95
+ /**
96
+ * Returns as soon as one of the promises resolves or rejects and cancels remaining promises
97
+ */
98
+ export async function raceCancellablePromises<T>(cancellablePromises: CancelablePromise<T>[]): Promise<T> {
99
+ let resolvedPromiseIndex = -1;
100
+ const promises = cancellablePromises.map((promise, index) => promise.then(result => { resolvedPromiseIndex = index; return result; }));
101
+ try {
102
+ const result = await Promise.race(promises);
103
+ return result;
104
+ } finally {
105
+ cancellablePromises.forEach((cancellablePromise, index) => {
106
+ if (index !== resolvedPromiseIndex) {
107
+ cancellablePromise.cancel();
108
+ }
109
+ });
110
+ }
111
+ }
112
+
113
+ export function raceTimeout<T>(promise: Promise<T>, timeout: number, onTimeout?: () => void): Promise<T | undefined> {
114
+ let promiseResolve: ((value: T | undefined) => void) | undefined = undefined;
115
+
116
+ const timer = setTimeout(() => {
117
+ promiseResolve?.(undefined);
118
+ onTimeout?.();
119
+ }, timeout);
120
+
121
+ return Promise.race([
122
+ promise.finally(() => clearTimeout(timer)),
123
+ new Promise<T | undefined>(resolve => promiseResolve = resolve)
124
+ ]);
125
+ }
126
+
127
+ export function asPromise<T>(callback: () => T | Thenable<T>): Promise<T> {
128
+ return new Promise<T>((resolve, reject) => {
129
+ const item = callback();
130
+ if (isThenable<T>(item)) {
131
+ item.then(resolve, reject);
132
+ } else {
133
+ resolve(item);
134
+ }
135
+ });
136
+ }
137
+
138
+ /**
139
+ * Creates and returns a new promise, plus its `resolve` and `reject` callbacks.
140
+ *
141
+ * Replace with standardized [`Promise.withResolvers`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/withResolvers) once it is supported
142
+ */
143
+ export function promiseWithResolvers<T>(): { promise: Promise<T>; resolve: (value: T | PromiseLike<T>) => void; reject: (err?: any) => void } {
144
+ let resolve: (value: T | PromiseLike<T>) => void;
145
+ let reject: (reason?: any) => void;
146
+ const promise = new Promise<T>((res, rej) => {
147
+ resolve = res;
148
+ reject = rej;
149
+ });
150
+ return { promise, resolve: resolve!, reject: reject! };
151
+ }
152
+
153
+ export interface ITask<T> {
154
+ (): T;
155
+ }
156
+
157
+ /**
158
+ * A helper to prevent accumulation of sequential async tasks.
159
+ *
160
+ * Imagine a mail man with the sole task of delivering letters. As soon as
161
+ * a letter submitted for delivery, he drives to the destination, delivers it
162
+ * and returns to his base. Imagine that during the trip, N more letters were submitted.
163
+ * When the mail man returns, he picks those N letters and delivers them all in a
164
+ * single trip. Even though N+1 submissions occurred, only 2 deliveries were made.
165
+ *
166
+ * The throttler implements this via the queue() method, by providing it a task
167
+ * factory. Following the example:
168
+ *
169
+ * const throttler = new Throttler();
170
+ * const letters = [];
171
+ *
172
+ * function deliver() {
173
+ * const lettersToDeliver = letters;
174
+ * letters = [];
175
+ * return makeTheTrip(lettersToDeliver);
176
+ * }
177
+ *
178
+ * function onLetterReceived(l) {
179
+ * letters.push(l);
180
+ * throttler.queue(deliver);
181
+ * }
182
+ */
183
+ export class Throttler implements IDisposable {
184
+
185
+ private activePromise: Promise<any> | null;
186
+ private queuedPromise: Promise<any> | null;
187
+ private queuedPromiseFactory: ITask<Promise<any>> | null;
188
+
189
+ private isDisposed = false;
190
+
191
+ constructor() {
192
+ this.activePromise = null;
193
+ this.queuedPromise = null;
194
+ this.queuedPromiseFactory = null;
195
+ }
196
+
197
+ queue<T>(promiseFactory: ITask<Promise<T>>): Promise<T> {
198
+ if (this.isDisposed) {
199
+ return Promise.reject(new Error('Throttler is disposed'));
200
+ }
201
+
202
+ if (this.activePromise) {
203
+ this.queuedPromiseFactory = promiseFactory;
204
+
205
+ if (!this.queuedPromise) {
206
+ const onComplete = () => {
207
+ this.queuedPromise = null;
208
+
209
+ if (this.isDisposed) {
210
+ return;
211
+ }
212
+
213
+ const result = this.queue(this.queuedPromiseFactory!);
214
+ this.queuedPromiseFactory = null;
215
+
216
+ return result;
217
+ };
218
+
219
+ this.queuedPromise = new Promise(resolve => {
220
+ this.activePromise!.then(onComplete, onComplete).then(resolve);
221
+ });
222
+ }
223
+
224
+ return new Promise((resolve, reject) => {
225
+ this.queuedPromise!.then(resolve, reject);
226
+ });
227
+ }
228
+
229
+ this.activePromise = promiseFactory();
230
+
231
+ return new Promise((resolve, reject) => {
232
+ this.activePromise!.then((result: T) => {
233
+ this.activePromise = null;
234
+ resolve(result);
235
+ }, (err: unknown) => {
236
+ this.activePromise = null;
237
+ reject(err);
238
+ });
239
+ });
240
+ }
241
+
242
+ dispose(): void {
243
+ this.isDisposed = true;
244
+ }
245
+ }
246
+
247
+ export class Sequencer {
248
+
249
+ private current: Promise<unknown> = Promise.resolve(null);
250
+
251
+ queue<T>(promiseTask: ITask<Promise<T>>): Promise<T> {
252
+ return this.current = this.current.then(() => promiseTask(), () => promiseTask());
253
+ }
254
+ }
255
+
256
+ export class SequencerByKey<TKey> {
257
+
258
+ private promiseMap = new Map<TKey, Promise<unknown>>();
259
+
260
+ queue<T>(key: TKey, promiseTask: ITask<Promise<T>>): Promise<T> {
261
+ const runningPromise = this.promiseMap.get(key) ?? Promise.resolve();
262
+ const newPromise = runningPromise
263
+ .catch(() => { })
264
+ .then(promiseTask)
265
+ .finally(() => {
266
+ if (this.promiseMap.get(key) === newPromise) {
267
+ this.promiseMap.delete(key);
268
+ }
269
+ });
270
+ this.promiseMap.set(key, newPromise);
271
+ return newPromise;
272
+ }
273
+ }
274
+
275
+ interface IScheduledLater extends IDisposable {
276
+ isTriggered(): boolean;
277
+ }
278
+
279
+ const timeoutDeferred = (timeout: number, fn: () => void): IScheduledLater => {
280
+ let scheduled = true;
281
+ const handle = setTimeout(() => {
282
+ scheduled = false;
283
+ fn();
284
+ }, timeout);
285
+ return {
286
+ isTriggered: () => scheduled,
287
+ dispose: () => {
288
+ clearTimeout(handle);
289
+ scheduled = false;
290
+ },
291
+ };
292
+ };
293
+
294
+ const microtaskDeferred = (fn: () => void): IScheduledLater => {
295
+ let scheduled = true;
296
+ queueMicrotask(() => {
297
+ if (scheduled) {
298
+ scheduled = false;
299
+ fn();
300
+ }
301
+ });
302
+
303
+ return {
304
+ isTriggered: () => scheduled,
305
+ dispose: () => { scheduled = false; },
306
+ };
307
+ };
308
+
309
+ /**
310
+ * A helper to delay (debounce) execution of a task that is being requested often.
311
+ *
312
+ * Following the throttler, now imagine the mail man wants to optimize the number of
313
+ * trips proactively. The trip itself can be long, so he decides not to make the trip
314
+ * as soon as a letter is submitted. Instead he waits a while, in case more
315
+ * letters are submitted. After said waiting period, if no letters were submitted, he
316
+ * decides to make the trip. Imagine that N more letters were submitted after the first
317
+ * one, all within a short period of time between each other. Even though N+1
318
+ * submissions occurred, only 1 delivery was made.
319
+ *
320
+ * The delayer offers this behavior via the trigger() method, into which both the task
321
+ * to be executed and the waiting period (delay) must be passed in as arguments. Following
322
+ * the example:
323
+ *
324
+ * const delayer = new Delayer(WAITING_PERIOD);
325
+ * const letters = [];
326
+ *
327
+ * function letterReceived(l) {
328
+ * letters.push(l);
329
+ * delayer.trigger(() => { return makeTheTrip(); });
330
+ * }
331
+ */
332
+ export class Delayer<T> implements IDisposable {
333
+
334
+ private deferred: IScheduledLater | null;
335
+ private completionPromise: Promise<any> | null;
336
+ private doResolve: ((value?: any | Promise<any>) => void) | null;
337
+ private doReject: ((err: any) => void) | null;
338
+ private task: ITask<T | Promise<T>> | null;
339
+
340
+ constructor(public defaultDelay: number | typeof MicrotaskDelay) {
341
+ this.deferred = null;
342
+ this.completionPromise = null;
343
+ this.doResolve = null;
344
+ this.doReject = null;
345
+ this.task = null;
346
+ }
347
+
348
+ trigger(task: ITask<T | Promise<T>>, delay = this.defaultDelay): Promise<T> {
349
+ this.task = task;
350
+ this.cancelTimeout();
351
+
352
+ if (!this.completionPromise) {
353
+ this.completionPromise = new Promise((resolve, reject) => {
354
+ this.doResolve = resolve;
355
+ this.doReject = reject;
356
+ }).then(() => {
357
+ this.completionPromise = null;
358
+ this.doResolve = null;
359
+ if (this.task) {
360
+ const task = this.task;
361
+ this.task = null;
362
+ return task();
363
+ }
364
+ return undefined;
365
+ });
366
+ }
367
+
368
+ const fn = () => {
369
+ this.deferred = null;
370
+ this.doResolve?.(null);
371
+ };
372
+
373
+ this.deferred = delay === MicrotaskDelay ? microtaskDeferred(fn) : timeoutDeferred(delay, fn);
374
+
375
+ return this.completionPromise;
376
+ }
377
+
378
+ isTriggered(): boolean {
379
+ return !!this.deferred?.isTriggered();
380
+ }
381
+
382
+ cancel(): void {
383
+ this.cancelTimeout();
384
+
385
+ if (this.completionPromise) {
386
+ this.doReject?.(new CancellationError());
387
+ this.completionPromise = null;
388
+ }
389
+ }
390
+
391
+ private cancelTimeout(): void {
392
+ this.deferred?.dispose();
393
+ this.deferred = null;
394
+ }
395
+
396
+ dispose(): void {
397
+ this.cancel();
398
+ }
399
+ }
400
+
401
+ /**
402
+ * A helper to delay execution of a task that is being requested often, while
403
+ * preventing accumulation of consecutive executions, while the task runs.
404
+ *
405
+ * The mail man is clever and waits for a certain amount of time, before going
406
+ * out to deliver letters. While the mail man is going out, more letters arrive
407
+ * and can only be delivered once he is back. Once he is back the mail man will
408
+ * do one more trip to deliver the letters that have accumulated while he was out.
409
+ */
410
+ export class ThrottledDelayer<T> {
411
+
412
+ private delayer: Delayer<Promise<T>>;
413
+ private throttler: Throttler;
414
+
415
+ constructor(defaultDelay: number) {
416
+ this.delayer = new Delayer(defaultDelay);
417
+ this.throttler = new Throttler();
418
+ }
419
+
420
+ trigger(promiseFactory: ITask<Promise<T>>, delay?: number): Promise<T> {
421
+ return this.delayer.trigger(() => this.throttler.queue(promiseFactory), delay) as unknown as Promise<T>;
422
+ }
423
+
424
+ isTriggered(): boolean {
425
+ return this.delayer.isTriggered();
426
+ }
427
+
428
+ cancel(): void {
429
+ this.delayer.cancel();
430
+ }
431
+
432
+ dispose(): void {
433
+ this.delayer.dispose();
434
+ this.throttler.dispose();
435
+ }
436
+ }
437
+
438
+ /**
439
+ * A barrier that is initially closed and then becomes opened permanently.
440
+ */
441
+ export class Barrier {
442
+ private _isOpen: boolean;
443
+ private _promise: Promise<boolean>;
444
+ private _completePromise!: (v: boolean) => void;
445
+
446
+ constructor() {
447
+ this._isOpen = false;
448
+ this._promise = new Promise<boolean>((c, e) => {
449
+ this._completePromise = c;
450
+ });
451
+ }
452
+
453
+ isOpen(): boolean {
454
+ return this._isOpen;
455
+ }
456
+
457
+ open(): void {
458
+ this._isOpen = true;
459
+ this._completePromise(true);
460
+ }
461
+
462
+ wait(): Promise<boolean> {
463
+ return this._promise;
464
+ }
465
+ }
466
+
467
+ /**
468
+ * A barrier that is initially closed and then becomes opened permanently after a certain period of
469
+ * time or when open is called explicitly
470
+ */
471
+ export class AutoOpenBarrier extends Barrier {
472
+
473
+ private readonly _timeout: any;
474
+
475
+ constructor(autoOpenTimeMs: number) {
476
+ super();
477
+ this._timeout = setTimeout(() => this.open(), autoOpenTimeMs);
478
+ }
479
+
480
+ override open(): void {
481
+ clearTimeout(this._timeout);
482
+ super.open();
483
+ }
484
+ }
485
+
486
+ export function timeout(millis: number): CancelablePromise<void>;
487
+ export function timeout(millis: number, token: CancellationToken): Promise<void>;
488
+ export function timeout(millis: number, token?: CancellationToken): CancelablePromise<void> | Promise<void> {
489
+ if (!token) {
490
+ return createCancelablePromise(token => timeout(millis, token));
491
+ }
492
+
493
+ return new Promise((resolve, reject) => {
494
+ const handle = setTimeout(() => {
495
+ disposable.dispose();
496
+ resolve();
497
+ }, millis);
498
+ const disposable = token.onCancellationRequested(() => {
499
+ clearTimeout(handle);
500
+ disposable.dispose();
501
+ reject(new CancellationError());
502
+ });
503
+ });
504
+ }
505
+
506
+ /**
507
+ * Creates a timeout that can be disposed using its returned value.
508
+ * @param handler The timeout handler.
509
+ * @param timeout An optional timeout in milliseconds.
510
+ * @param store An optional {@link DisposableStore} that will have the timeout disposable managed automatically.
511
+ *
512
+ * @example
513
+ * const store = new DisposableStore;
514
+ * // Call the timeout after 1000ms at which point it will be automatically
515
+ * // evicted from the store.
516
+ * const timeoutDisposable = disposableTimeout(() => {}, 1000, store);
517
+ *
518
+ * if (foo) {
519
+ * // Cancel the timeout and evict it from store.
520
+ * timeoutDisposable.dispose();
521
+ * }
522
+ */
523
+ export function disposableTimeout(handler: () => void, timeout = 0, store?: DisposableStore): IDisposable {
524
+ const timer = setTimeout(() => {
525
+ handler();
526
+ if (store) {
527
+ disposable.dispose();
528
+ }
529
+ }, timeout);
530
+ const disposable = toDisposable(() => {
531
+ clearTimeout(timer);
532
+ store?.deleteAndLeak(disposable);
533
+ });
534
+ store?.add(disposable);
535
+ return disposable;
536
+ }
537
+
538
+ /**
539
+ * Runs the provided list of promise factories in sequential order. The returned
540
+ * promise will complete to an array of results from each promise.
541
+ */
542
+
543
+ export function sequence<T>(promiseFactories: ITask<Promise<T>>[]): Promise<T[]> {
544
+ const results: T[] = [];
545
+ let index = 0;
546
+ const len = promiseFactories.length;
547
+
548
+ function next(): Promise<T> | null {
549
+ return index < len ? promiseFactories[index++]() : null;
550
+ }
551
+
552
+ function thenHandler(result: any): Promise<any> {
553
+ if (result !== undefined && result !== null) {
554
+ results.push(result);
555
+ }
556
+
557
+ const n = next();
558
+ if (n) {
559
+ return n.then(thenHandler);
560
+ }
561
+
562
+ return Promise.resolve(results);
563
+ }
564
+
565
+ return Promise.resolve(null).then(thenHandler);
566
+ }
567
+
568
+ export function first<T>(promiseFactories: ITask<Promise<T>>[], shouldStop: (t: T) => boolean = t => !!t, defaultValue: T | null = null): Promise<T | null> {
569
+ let index = 0;
570
+ const len = promiseFactories.length;
571
+
572
+ const loop: () => Promise<T | null> = () => {
573
+ if (index >= len) {
574
+ return Promise.resolve(defaultValue);
575
+ }
576
+
577
+ const factory = promiseFactories[index++];
578
+ const promise = Promise.resolve(factory());
579
+
580
+ return promise.then(result => {
581
+ if (shouldStop(result)) {
582
+ return Promise.resolve(result);
583
+ }
584
+
585
+ return loop();
586
+ });
587
+ };
588
+
589
+ return loop();
590
+ }
591
+
592
+ /**
593
+ * Returns the result of the first promise that matches the "shouldStop",
594
+ * running all promises in parallel. Supports cancelable promises.
595
+ */
596
+ export function firstParallel<T>(promiseList: Promise<T>[], shouldStop?: (t: T) => boolean, defaultValue?: T | null): Promise<T | null>;
597
+ export function firstParallel<T, R extends T>(promiseList: Promise<T>[], shouldStop: (t: T) => t is R, defaultValue?: R | null): Promise<R | null>;
598
+ export function firstParallel<T>(promiseList: Promise<T>[], shouldStop: (t: T) => boolean = t => !!t, defaultValue: T | null = null) {
599
+ if (promiseList.length === 0) {
600
+ return Promise.resolve(defaultValue);
601
+ }
602
+
603
+ let todo = promiseList.length;
604
+ const finish = () => {
605
+ todo = -1;
606
+ for (const promise of promiseList) {
607
+ (promise as Partial<CancelablePromise<T>>).cancel?.();
608
+ }
609
+ };
610
+
611
+ return new Promise<T | null>((resolve, reject) => {
612
+ for (const promise of promiseList) {
613
+ promise.then(result => {
614
+ if (--todo >= 0 && shouldStop(result)) {
615
+ finish();
616
+ resolve(result);
617
+ } else if (todo === 0) {
618
+ resolve(defaultValue);
619
+ }
620
+ })
621
+ .catch(err => {
622
+ if (--todo >= 0) {
623
+ finish();
624
+ reject(err);
625
+ }
626
+ });
627
+ }
628
+ });
629
+ }
630
+
631
+ interface ILimitedTaskFactory<T> {
632
+ factory: ITask<Promise<T>>;
633
+ c: (value: T | Promise<T>) => void;
634
+ e: (error?: unknown) => void;
635
+ }
636
+
637
+ export interface ILimiter<T> {
638
+
639
+ readonly size: number;
640
+
641
+ queue(factory: ITask<Promise<T>>): Promise<T>;
642
+
643
+ clear(): void;
644
+ }
645
+
646
+ /**
647
+ * A helper to queue N promises and run them all with a max degree of parallelism. The helper
648
+ * ensures that at any time no more than M promises are running at the same time.
649
+ */
650
+ export class Limiter<T> implements ILimiter<T> {
651
+
652
+ private _size = 0;
653
+ private _isDisposed = false;
654
+ private runningPromises: number;
655
+ private readonly maxDegreeOfParalellism: number;
656
+ private readonly outstandingPromises: ILimitedTaskFactory<T>[];
657
+ private readonly _onDrained: Emitter<void>;
658
+
659
+ constructor(maxDegreeOfParalellism: number) {
660
+ this.maxDegreeOfParalellism = maxDegreeOfParalellism;
661
+ this.outstandingPromises = [];
662
+ this.runningPromises = 0;
663
+ this._onDrained = new Emitter<void>();
664
+ }
665
+
666
+ /**
667
+ *
668
+ * @returns A promise that resolved when all work is done (onDrained) or when
669
+ * there is nothing to do
670
+ */
671
+ whenIdle(): Promise<void> {
672
+ return this.size > 0
673
+ ? Event.toPromise(this.onDrained)
674
+ : Promise.resolve();
675
+ }
676
+
677
+ get onDrained(): Event<void> {
678
+ return this._onDrained.event;
679
+ }
680
+
681
+ get size(): number {
682
+ return this._size;
683
+ }
684
+
685
+ queue(factory: ITask<Promise<T>>): Promise<T> {
686
+ if (this._isDisposed) {
687
+ throw new Error('Object has been disposed');
688
+ }
689
+ this._size++;
690
+
691
+ return new Promise<T>((c, e) => {
692
+ this.outstandingPromises.push({ factory, c, e });
693
+ this.consume();
694
+ });
695
+ }
696
+
697
+ private consume(): void {
698
+ while (this.outstandingPromises.length && this.runningPromises < this.maxDegreeOfParalellism) {
699
+ const iLimitedTask = this.outstandingPromises.shift()!;
700
+ this.runningPromises++;
701
+
702
+ const promise = iLimitedTask.factory();
703
+ promise.then(iLimitedTask.c, iLimitedTask.e);
704
+ promise.then(() => this.consumed(), () => this.consumed());
705
+ }
706
+ }
707
+
708
+ private consumed(): void {
709
+ if (this._isDisposed) {
710
+ return;
711
+ }
712
+ this.runningPromises--;
713
+ if (--this._size === 0) {
714
+ this._onDrained.fire();
715
+ }
716
+
717
+ if (this.outstandingPromises.length > 0) {
718
+ this.consume();
719
+ }
720
+ }
721
+
722
+ clear(): void {
723
+ if (this._isDisposed) {
724
+ throw new Error('Object has been disposed');
725
+ }
726
+ this.outstandingPromises.length = 0;
727
+ this._size = this.runningPromises;
728
+ }
729
+
730
+ dispose(): void {
731
+ this._isDisposed = true;
732
+ this.outstandingPromises.length = 0; // stop further processing
733
+ this._size = 0;
734
+ this._onDrained.dispose();
735
+ }
736
+ }
737
+
738
+ /**
739
+ * A queue is handles one promise at a time and guarantees that at any time only one promise is executing.
740
+ */
741
+ export class Queue<T> extends Limiter<T> {
742
+
743
+ constructor() {
744
+ super(1);
745
+ }
746
+ }
747
+
748
+ /**
749
+ * Same as `Queue`, ensures that only 1 task is executed at the same time. The difference to `Queue` is that
750
+ * there is only 1 task about to be scheduled next. As such, calling `queue` while a task is executing will
751
+ * replace the currently queued task until it executes.
752
+ *
753
+ * As such, the returned promise may not be from the factory that is passed in but from the next factory that
754
+ * is running after having called `queue`.
755
+ */
756
+ export class LimitedQueue {
757
+
758
+ private readonly sequentializer = new TaskSequentializer();
759
+
760
+ private tasks = 0;
761
+
762
+ queue(factory: ITask<Promise<void>>): Promise<void> {
763
+ if (!this.sequentializer.isRunning()) {
764
+ return this.sequentializer.run(this.tasks++, factory());
765
+ }
766
+
767
+ return this.sequentializer.queue(() => {
768
+ return this.sequentializer.run(this.tasks++, factory());
769
+ });
770
+ }
771
+ }
772
+
773
+ export class TimeoutTimer implements IDisposable {
774
+ private _token: any;
775
+ private _isDisposed = false;
776
+
777
+ constructor();
778
+ constructor(runner: () => void, timeout: number);
779
+ constructor(runner?: () => void, timeout?: number) {
780
+ this._token = -1;
781
+
782
+ if (typeof runner === 'function' && typeof timeout === 'number') {
783
+ this.setIfNotSet(runner, timeout);
784
+ }
785
+ }
786
+
787
+ dispose(): void {
788
+ this.cancel();
789
+ this._isDisposed = true;
790
+ }
791
+
792
+ cancel(): void {
793
+ if (this._token !== -1) {
794
+ clearTimeout(this._token);
795
+ this._token = -1;
796
+ }
797
+ }
798
+
799
+ cancelAndSet(runner: () => void, timeout: number): void {
800
+ if (this._isDisposed) {
801
+ throw new BugIndicatingError(`Calling 'cancelAndSet' on a disposed TimeoutTimer`);
802
+ }
803
+
804
+ this.cancel();
805
+ this._token = setTimeout(() => {
806
+ this._token = -1;
807
+ runner();
808
+ }, timeout);
809
+ }
810
+
811
+ setIfNotSet(runner: () => void, timeout: number): void {
812
+ if (this._isDisposed) {
813
+ throw new BugIndicatingError(`Calling 'setIfNotSet' on a disposed TimeoutTimer`);
814
+ }
815
+
816
+ if (this._token !== -1) {
817
+ // timer is already set
818
+ return;
819
+ }
820
+ this._token = setTimeout(() => {
821
+ this._token = -1;
822
+ runner();
823
+ }, timeout);
824
+ }
825
+ }
826
+
827
+ export class IntervalTimer implements IDisposable {
828
+
829
+ private disposable: IDisposable | undefined = undefined;
830
+ private isDisposed = false;
831
+
832
+ cancel(): void {
833
+ this.disposable?.dispose();
834
+ this.disposable = undefined;
835
+ }
836
+
837
+ cancelAndSet(runner: () => void, interval: number, context = globalThis): void {
838
+ if (this.isDisposed) {
839
+ throw new BugIndicatingError(`Calling 'cancelAndSet' on a disposed IntervalTimer`);
840
+ }
841
+
842
+ this.cancel();
843
+ const handle = context.setInterval(() => {
844
+ runner();
845
+ }, interval);
846
+
847
+ this.disposable = toDisposable(() => {
848
+ context.clearInterval(handle);
849
+ this.disposable = undefined;
850
+ });
851
+ }
852
+
853
+ dispose(): void {
854
+ this.cancel();
855
+ this.isDisposed = true;
856
+ }
857
+ }
858
+
859
+ export class RunOnceScheduler implements IDisposable {
860
+
861
+ protected runner: ((...args: unknown[]) => void) | null;
862
+
863
+ private timeoutToken: any;
864
+ private timeout: number;
865
+ private timeoutHandler: () => void;
866
+
867
+ constructor(runner: (...args: any[]) => void, delay: number) {
868
+ this.timeoutToken = -1;
869
+ this.runner = runner;
870
+ this.timeout = delay;
871
+ this.timeoutHandler = this.onTimeout.bind(this);
872
+ }
873
+
874
+ /**
875
+ * Dispose RunOnceScheduler
876
+ */
877
+ dispose(): void {
878
+ this.cancel();
879
+ this.runner = null;
880
+ }
881
+
882
+ /**
883
+ * Cancel current scheduled runner (if any).
884
+ */
885
+ cancel(): void {
886
+ if (this.isScheduled()) {
887
+ clearTimeout(this.timeoutToken);
888
+ this.timeoutToken = -1;
889
+ }
890
+ }
891
+
892
+ /**
893
+ * Cancel previous runner (if any) & schedule a new runner.
894
+ */
895
+ schedule(delay = this.timeout): void {
896
+ this.cancel();
897
+ this.timeoutToken = setTimeout(this.timeoutHandler, delay);
898
+ }
899
+
900
+ get delay(): number {
901
+ return this.timeout;
902
+ }
903
+
904
+ set delay(value: number) {
905
+ this.timeout = value;
906
+ }
907
+
908
+ /**
909
+ * Returns true if scheduled.
910
+ */
911
+ isScheduled(): boolean {
912
+ return this.timeoutToken !== -1;
913
+ }
914
+
915
+ flush(): void {
916
+ if (this.isScheduled()) {
917
+ this.cancel();
918
+ this.doRun();
919
+ }
920
+ }
921
+
922
+ private onTimeout() {
923
+ this.timeoutToken = -1;
924
+ if (this.runner) {
925
+ this.doRun();
926
+ }
927
+ }
928
+
929
+ protected doRun(): void {
930
+ this.runner?.();
931
+ }
932
+ }
933
+
934
+ /**
935
+ * Same as `RunOnceScheduler`, but doesn't count the time spent in sleep mode.
936
+ * > **NOTE**: Only offers 1s resolution.
937
+ *
938
+ * When calling `setTimeout` with 3hrs, and putting the computer immediately to sleep
939
+ * for 8hrs, `setTimeout` will fire **as soon as the computer wakes from sleep**. But
940
+ * this scheduler will execute 3hrs **after waking the computer from sleep**.
941
+ */
942
+ export class ProcessTimeRunOnceScheduler {
943
+
944
+ private runner: (() => void) | null;
945
+ private timeout: number;
946
+
947
+ private counter: number;
948
+ private intervalToken: any;
949
+ private intervalHandler: () => void;
950
+
951
+ constructor(runner: () => void, delay: number) {
952
+ if (delay % 1000 !== 0) {
953
+ console.warn(`ProcessTimeRunOnceScheduler resolution is 1s, ${delay}ms is not a multiple of 1000ms.`);
954
+ }
955
+ this.runner = runner;
956
+ this.timeout = delay;
957
+ this.counter = 0;
958
+ this.intervalToken = -1;
959
+ this.intervalHandler = this.onInterval.bind(this);
960
+ }
961
+
962
+ dispose(): void {
963
+ this.cancel();
964
+ this.runner = null;
965
+ }
966
+
967
+ cancel(): void {
968
+ if (this.isScheduled()) {
969
+ clearInterval(this.intervalToken);
970
+ this.intervalToken = -1;
971
+ }
972
+ }
973
+
974
+ /**
975
+ * Cancel previous runner (if any) & schedule a new runner.
976
+ */
977
+ schedule(delay = this.timeout): void {
978
+ if (delay % 1000 !== 0) {
979
+ console.warn(`ProcessTimeRunOnceScheduler resolution is 1s, ${delay}ms is not a multiple of 1000ms.`);
980
+ }
981
+ this.cancel();
982
+ this.counter = Math.ceil(delay / 1000);
983
+ this.intervalToken = setInterval(this.intervalHandler, 1000);
984
+ }
985
+
986
+ /**
987
+ * Returns true if scheduled.
988
+ */
989
+ isScheduled(): boolean {
990
+ return this.intervalToken !== -1;
991
+ }
992
+
993
+ private onInterval() {
994
+ this.counter--;
995
+ if (this.counter > 0) {
996
+ // still need to wait
997
+ return;
998
+ }
999
+
1000
+ // time elapsed
1001
+ clearInterval(this.intervalToken);
1002
+ this.intervalToken = -1;
1003
+ this.runner?.();
1004
+ }
1005
+ }
1006
+
1007
+ export class RunOnceWorker<T> extends RunOnceScheduler {
1008
+
1009
+ private units: T[] = [];
1010
+
1011
+ constructor(runner: (units: T[]) => void, timeout: number) {
1012
+ super(runner, timeout);
1013
+ }
1014
+
1015
+ work(unit: T): void {
1016
+ this.units.push(unit);
1017
+
1018
+ if (!this.isScheduled()) {
1019
+ this.schedule();
1020
+ }
1021
+ }
1022
+
1023
+ protected override doRun(): void {
1024
+ const units = this.units;
1025
+ this.units = [];
1026
+
1027
+ this.runner?.(units);
1028
+ }
1029
+
1030
+ override dispose(): void {
1031
+ this.units = [];
1032
+
1033
+ super.dispose();
1034
+ }
1035
+ }
1036
+
1037
+ export interface IThrottledWorkerOptions {
1038
+
1039
+ /**
1040
+ * maximum of units the worker will pass onto handler at once
1041
+ */
1042
+ maxWorkChunkSize: number;
1043
+
1044
+ /**
1045
+ * maximum of units the worker will keep in memory for processing
1046
+ */
1047
+ maxBufferedWork: number | undefined;
1048
+
1049
+ /**
1050
+ * delay before processing the next round of chunks when chunk size exceeds limits
1051
+ */
1052
+ throttleDelay: number;
1053
+ }
1054
+
1055
+ /**
1056
+ * The `ThrottledWorker` will accept units of work `T`
1057
+ * to handle. The contract is:
1058
+ * * there is a maximum of units the worker can handle at once (via `maxWorkChunkSize`)
1059
+ * * there is a maximum of units the worker will keep in memory for processing (via `maxBufferedWork`)
1060
+ * * after having handled `maxWorkChunkSize` units, the worker needs to rest (via `throttleDelay`)
1061
+ */
1062
+ export class ThrottledWorker<T> extends Disposable {
1063
+
1064
+ private readonly pendingWork: T[] = [];
1065
+
1066
+ private readonly throttler = this._register(new MutableDisposable<RunOnceScheduler>());
1067
+ private disposed = false;
1068
+
1069
+ constructor(
1070
+ private options: IThrottledWorkerOptions,
1071
+ private readonly handler: (units: T[]) => void
1072
+ ) {
1073
+ super();
1074
+ }
1075
+
1076
+ /**
1077
+ * The number of work units that are pending to be processed.
1078
+ */
1079
+ get pending(): number { return this.pendingWork.length; }
1080
+
1081
+ /**
1082
+ * Add units to be worked on. Use `pending` to figure out
1083
+ * how many units are not yet processed after this method
1084
+ * was called.
1085
+ *
1086
+ * @returns whether the work was accepted or not. If the
1087
+ * worker is disposed, it will not accept any more work.
1088
+ * If the number of pending units would become larger
1089
+ * than `maxPendingWork`, more work will also not be accepted.
1090
+ */
1091
+ work(units: readonly T[]): boolean {
1092
+ if (this.disposed) {
1093
+ return false; // work not accepted: disposed
1094
+ }
1095
+
1096
+ // Check for reaching maximum of pending work
1097
+ if (typeof this.options.maxBufferedWork === 'number') {
1098
+
1099
+ // Throttled: simple check if pending + units exceeds max pending
1100
+ if (this.throttler.value) {
1101
+ if (this.pending + units.length > this.options.maxBufferedWork) {
1102
+ return false; // work not accepted: too much pending work
1103
+ }
1104
+ }
1105
+
1106
+ // Unthrottled: same as throttled, but account for max chunk getting
1107
+ // worked on directly without being pending
1108
+ else {
1109
+ if (this.pending + units.length - this.options.maxWorkChunkSize > this.options.maxBufferedWork) {
1110
+ return false; // work not accepted: too much pending work
1111
+ }
1112
+ }
1113
+ }
1114
+
1115
+ // Add to pending units first
1116
+ for (const unit of units) {
1117
+ this.pendingWork.push(unit);
1118
+ }
1119
+
1120
+ // If not throttled, start working directly
1121
+ // Otherwise, when the throttle delay has
1122
+ // past, pending work will be worked again.
1123
+ if (!this.throttler.value) {
1124
+ this.doWork();
1125
+ }
1126
+
1127
+ return true; // work accepted
1128
+ }
1129
+
1130
+ private doWork(): void {
1131
+
1132
+ // Extract chunk to handle and handle it
1133
+ this.handler(this.pendingWork.splice(0, this.options.maxWorkChunkSize));
1134
+
1135
+ // If we have remaining work, schedule it after a delay
1136
+ if (this.pendingWork.length > 0) {
1137
+ this.throttler.value = new RunOnceScheduler(() => {
1138
+ this.throttler.clear();
1139
+
1140
+ this.doWork();
1141
+ }, this.options.throttleDelay);
1142
+ this.throttler.value.schedule();
1143
+ }
1144
+ }
1145
+
1146
+ override dispose(): void {
1147
+ super.dispose();
1148
+
1149
+ this.disposed = true;
1150
+ }
1151
+ }
1152
+
1153
+ //#region -- run on idle tricks ------------
1154
+
1155
+ export interface IdleDeadline {
1156
+ readonly didTimeout: boolean;
1157
+ timeRemaining(): number;
1158
+ }
1159
+
1160
+ type IdleApi = Pick<typeof globalThis, 'requestIdleCallback' | 'cancelIdleCallback'>;
1161
+
1162
+
1163
+ /**
1164
+ * Execute the callback the next time the browser is idle, returning an
1165
+ * {@link IDisposable} that will cancel the callback when disposed. This wraps
1166
+ * [requestIdleCallback] so it will fallback to [setTimeout] if the environment
1167
+ * doesn't support it.
1168
+ *
1169
+ * @param callback The callback to run when idle, this includes an
1170
+ * [IdleDeadline] that provides the time alloted for the idle callback by the
1171
+ * browser. Not respecting this deadline will result in a degraded user
1172
+ * experience.
1173
+ * @param timeout A timeout at which point to queue no longer wait for an idle
1174
+ * callback but queue it on the regular event loop (like setTimeout). Typically
1175
+ * this should not be used.
1176
+ *
1177
+ * [IdleDeadline]: https://developer.mozilla.org/en-US/docs/Web/API/IdleDeadline
1178
+ * [requestIdleCallback]: https://developer.mozilla.org/en-US/docs/Web/API/Window/requestIdleCallback
1179
+ * [setTimeout]: https://developer.mozilla.org/en-US/docs/Web/API/Window/setTimeout
1180
+ *
1181
+ * **Note** that there is `dom.ts#runWhenWindowIdle` which is better suited when running inside a browser
1182
+ * context
1183
+ */
1184
+ export let runWhenGlobalIdle: (callback: (idle: IdleDeadline) => void, timeout?: number) => IDisposable;
1185
+
1186
+ export let _runWhenIdle: (targetWindow: IdleApi, callback: (idle: IdleDeadline) => void, timeout?: number) => IDisposable;
1187
+
1188
+ (function () {
1189
+ if (typeof globalThis.requestIdleCallback !== 'function' || typeof globalThis.cancelIdleCallback !== 'function') {
1190
+ _runWhenIdle = (_targetWindow, runner) => {
1191
+ setTimeout0(() => {
1192
+ if (disposed) {
1193
+ return;
1194
+ }
1195
+ const end = Date.now() + 15; // one frame at 64fps
1196
+ const deadline: IdleDeadline = {
1197
+ didTimeout: true,
1198
+ timeRemaining() {
1199
+ return Math.max(0, end - Date.now());
1200
+ }
1201
+ };
1202
+ runner(Object.freeze(deadline));
1203
+ });
1204
+ let disposed = false;
1205
+ return {
1206
+ dispose() {
1207
+ if (disposed) {
1208
+ return;
1209
+ }
1210
+ disposed = true;
1211
+ }
1212
+ };
1213
+ };
1214
+ } else {
1215
+ _runWhenIdle = (targetWindow: IdleApi, runner, timeout?) => {
1216
+ const handle: number = targetWindow.requestIdleCallback(runner, typeof timeout === 'number' ? { timeout } : undefined);
1217
+ let disposed = false;
1218
+ return {
1219
+ dispose() {
1220
+ if (disposed) {
1221
+ return;
1222
+ }
1223
+ disposed = true;
1224
+ targetWindow.cancelIdleCallback(handle);
1225
+ }
1226
+ };
1227
+ };
1228
+ }
1229
+ runWhenGlobalIdle = (runner) => _runWhenIdle(globalThis, runner);
1230
+ })();
1231
+
1232
+ export abstract class AbstractIdleValue<T> {
1233
+
1234
+ private readonly _executor: () => void;
1235
+ private readonly _handle: IDisposable;
1236
+
1237
+ private _didRun: boolean = false;
1238
+ private _value?: T;
1239
+ private _error: unknown;
1240
+
1241
+ constructor(targetWindow: IdleApi, executor: () => T) {
1242
+ this._executor = () => {
1243
+ try {
1244
+ this._value = executor();
1245
+ } catch (err) {
1246
+ this._error = err;
1247
+ } finally {
1248
+ this._didRun = true;
1249
+ }
1250
+ };
1251
+ this._handle = _runWhenIdle(targetWindow, () => this._executor());
1252
+ }
1253
+
1254
+ dispose(): void {
1255
+ this._handle.dispose();
1256
+ }
1257
+
1258
+ get value(): T {
1259
+ if (!this._didRun) {
1260
+ this._handle.dispose();
1261
+ this._executor();
1262
+ }
1263
+ if (this._error) {
1264
+ throw this._error;
1265
+ }
1266
+ return this._value!;
1267
+ }
1268
+
1269
+ get isInitialized(): boolean {
1270
+ return this._didRun;
1271
+ }
1272
+ }
1273
+
1274
+ /**
1275
+ * An `IdleValue` that always uses the current window (which might be throttled or inactive)
1276
+ *
1277
+ * **Note** that there is `dom.ts#WindowIdleValue` which is better suited when running inside a browser
1278
+ * context
1279
+ */
1280
+ export class GlobalIdleValue<T> extends AbstractIdleValue<T> {
1281
+
1282
+ constructor(executor: () => T) {
1283
+ super(globalThis, executor);
1284
+ }
1285
+ }
1286
+
1287
+ //#endregion
1288
+
1289
+ export async function retry<T>(task: ITask<Promise<T>>, delay: number, retries: number): Promise<T> {
1290
+ let lastError: Error | undefined;
1291
+
1292
+ for (let i = 0; i < retries; i++) {
1293
+ try {
1294
+ return await task();
1295
+ } catch (error) {
1296
+ lastError = error;
1297
+
1298
+ await timeout(delay);
1299
+ }
1300
+ }
1301
+
1302
+ throw lastError;
1303
+ }
1304
+
1305
+ //#region Task Sequentializer
1306
+
1307
+ interface IRunningTask {
1308
+ readonly taskId: number;
1309
+ readonly cancel: () => void;
1310
+ readonly promise: Promise<void>;
1311
+ }
1312
+
1313
+ interface IQueuedTask {
1314
+ readonly promise: Promise<void>;
1315
+ readonly promiseResolve: () => void;
1316
+ readonly promiseReject: (error: Error) => void;
1317
+ run: ITask<Promise<void>>;
1318
+ }
1319
+
1320
+ export interface ITaskSequentializerWithRunningTask {
1321
+ readonly running: Promise<void>;
1322
+ }
1323
+
1324
+ export interface ITaskSequentializerWithQueuedTask {
1325
+ readonly queued: IQueuedTask;
1326
+ }
1327
+
1328
+ /**
1329
+ * @deprecated use `LimitedQueue` instead for an easier to use API
1330
+ */
1331
+ export class TaskSequentializer {
1332
+
1333
+ private _running?: IRunningTask;
1334
+ private _queued?: IQueuedTask;
1335
+
1336
+ isRunning(taskId?: number): this is ITaskSequentializerWithRunningTask {
1337
+ if (typeof taskId === 'number') {
1338
+ return this._running?.taskId === taskId;
1339
+ }
1340
+
1341
+ return !!this._running;
1342
+ }
1343
+
1344
+ get running(): Promise<void> | undefined {
1345
+ return this._running?.promise;
1346
+ }
1347
+
1348
+ cancelRunning(): void {
1349
+ this._running?.cancel();
1350
+ }
1351
+
1352
+ run(taskId: number, promise: Promise<void>, onCancel?: () => void,): Promise<void> {
1353
+ this._running = { taskId, cancel: () => onCancel?.(), promise };
1354
+
1355
+ promise.then(() => this.doneRunning(taskId), () => this.doneRunning(taskId));
1356
+
1357
+ return promise;
1358
+ }
1359
+
1360
+ private doneRunning(taskId: number): void {
1361
+ if (this._running && taskId === this._running.taskId) {
1362
+
1363
+ // only set running to done if the promise finished that is associated with that taskId
1364
+ this._running = undefined;
1365
+
1366
+ // schedule the queued task now that we are free if we have any
1367
+ this.runQueued();
1368
+ }
1369
+ }
1370
+
1371
+ private runQueued(): void {
1372
+ if (this._queued) {
1373
+ const queued = this._queued;
1374
+ this._queued = undefined;
1375
+
1376
+ // Run queued task and complete on the associated promise
1377
+ queued.run().then(queued.promiseResolve, queued.promiseReject);
1378
+ }
1379
+ }
1380
+
1381
+ /**
1382
+ * Note: the promise to schedule as next run MUST itself call `run`.
1383
+ * Otherwise, this sequentializer will report `false` for `isRunning`
1384
+ * even when this task is running. Missing this detail means that
1385
+ * suddenly multiple tasks will run in parallel.
1386
+ */
1387
+ queue(run: ITask<Promise<void>>): Promise<void> {
1388
+
1389
+ // this is our first queued task, so we create associated promise with it
1390
+ // so that we can return a promise that completes when the task has
1391
+ // completed.
1392
+ if (!this._queued) {
1393
+ const { promise, resolve: promiseResolve, reject: promiseReject } = promiseWithResolvers<void>();
1394
+ this._queued = {
1395
+ run,
1396
+ promise,
1397
+ promiseResolve: promiseResolve!,
1398
+ promiseReject: promiseReject!
1399
+ };
1400
+ }
1401
+
1402
+ // we have a previous queued task, just overwrite it
1403
+ else {
1404
+ this._queued.run = run;
1405
+ }
1406
+
1407
+ return this._queued.promise;
1408
+ }
1409
+
1410
+ hasQueued(): this is ITaskSequentializerWithQueuedTask {
1411
+ return !!this._queued;
1412
+ }
1413
+
1414
+ async join(): Promise<void> {
1415
+ return this._queued?.promise ?? this._running?.promise;
1416
+ }
1417
+ }
1418
+
1419
+ //#endregion
1420
+
1421
+ //#region
1422
+
1423
+ /**
1424
+ * The `IntervalCounter` allows to count the number
1425
+ * of calls to `increment()` over a duration of
1426
+ * `interval`. This utility can be used to conditionally
1427
+ * throttle a frequent task when a certain threshold
1428
+ * is reached.
1429
+ */
1430
+ export class IntervalCounter {
1431
+
1432
+ private lastIncrementTime = 0;
1433
+
1434
+ private value = 0;
1435
+
1436
+ constructor(private readonly interval: number, private readonly nowFn = () => Date.now()) { }
1437
+
1438
+ increment(): number {
1439
+ const now = this.nowFn();
1440
+
1441
+ // We are outside of the range of `interval` and as such
1442
+ // start counting from 0 and remember the time
1443
+ if (now - this.lastIncrementTime > this.interval) {
1444
+ this.lastIncrementTime = now;
1445
+ this.value = 0;
1446
+ }
1447
+
1448
+ this.value++;
1449
+
1450
+ return this.value;
1451
+ }
1452
+ }
1453
+
1454
+ //#endregion
1455
+
1456
+ //#region
1457
+
1458
+ export type ValueCallback<T = unknown> = (value: T | Promise<T>) => void;
1459
+
1460
+ const enum DeferredOutcome {
1461
+ Resolved,
1462
+ Rejected
1463
+ }
1464
+
1465
+ /**
1466
+ * Creates a promise whose resolution or rejection can be controlled imperatively.
1467
+ */
1468
+ export class DeferredPromise<T> {
1469
+
1470
+ private completeCallback!: ValueCallback<T>;
1471
+ private errorCallback!: (err: unknown) => void;
1472
+ private outcome?: { outcome: DeferredOutcome.Rejected; value: any } | { outcome: DeferredOutcome.Resolved; value: T };
1473
+
1474
+ public get isRejected() {
1475
+ return this.outcome?.outcome === DeferredOutcome.Rejected;
1476
+ }
1477
+
1478
+ public get isResolved() {
1479
+ return this.outcome?.outcome === DeferredOutcome.Resolved;
1480
+ }
1481
+
1482
+ public get isSettled() {
1483
+ return !!this.outcome;
1484
+ }
1485
+
1486
+ public get value() {
1487
+ return this.outcome?.outcome === DeferredOutcome.Resolved ? this.outcome?.value : undefined;
1488
+ }
1489
+
1490
+ public readonly p: Promise<T>;
1491
+
1492
+ constructor() {
1493
+ this.p = new Promise<T>((c, e) => {
1494
+ this.completeCallback = c;
1495
+ this.errorCallback = e;
1496
+ });
1497
+ }
1498
+
1499
+ public complete(value: T) {
1500
+ return new Promise<void>(resolve => {
1501
+ this.completeCallback(value);
1502
+ this.outcome = { outcome: DeferredOutcome.Resolved, value };
1503
+ resolve();
1504
+ });
1505
+ }
1506
+
1507
+ public error(err: unknown) {
1508
+ return new Promise<void>(resolve => {
1509
+ this.errorCallback(err);
1510
+ this.outcome = { outcome: DeferredOutcome.Rejected, value: err };
1511
+ resolve();
1512
+ });
1513
+ }
1514
+
1515
+ public cancel() {
1516
+ return this.error(new CancellationError());
1517
+ }
1518
+ }
1519
+
1520
+ //#endregion
1521
+
1522
+ //#region Promises
1523
+
1524
+ export namespace Promises {
1525
+
1526
+ /**
1527
+ * A drop-in replacement for `Promise.all` with the only difference
1528
+ * that the method awaits every promise to either fulfill or reject.
1529
+ *
1530
+ * Similar to `Promise.all`, only the first error will be returned
1531
+ * if any.
1532
+ */
1533
+ export async function settled<T>(promises: Promise<T>[]): Promise<T[]> {
1534
+ let firstError: Error | undefined = undefined;
1535
+
1536
+ const result = await Promise.all(promises.map(promise => promise.then(value => value, error => {
1537
+ if (!firstError) {
1538
+ firstError = error;
1539
+ }
1540
+
1541
+ return undefined; // do not rethrow so that other promises can settle
1542
+ })));
1543
+
1544
+ if (typeof firstError !== 'undefined') {
1545
+ throw firstError;
1546
+ }
1547
+
1548
+ return result as unknown as T[]; // cast is needed and protected by the `throw` above
1549
+ }
1550
+
1551
+ /**
1552
+ * A helper to create a new `Promise<T>` with a body that is a promise
1553
+ * itself. By default, an error that raises from the async body will
1554
+ * end up as a unhandled rejection, so this utility properly awaits the
1555
+ * body and rejects the promise as a normal promise does without async
1556
+ * body.
1557
+ *
1558
+ * This method should only be used in rare cases where otherwise `async`
1559
+ * cannot be used (e.g. when callbacks are involved that require this).
1560
+ */
1561
+ export function withAsyncBody<T, E = Error>(bodyFn: (resolve: (value: T) => unknown, reject: (error: E) => unknown) => Promise<unknown>): Promise<T> {
1562
+ // eslint-disable-next-line no-async-promise-executor
1563
+ return new Promise<T>(async (resolve, reject) => {
1564
+ try {
1565
+ await bodyFn(resolve, reject);
1566
+ } catch (error) {
1567
+ reject(error);
1568
+ }
1569
+ });
1570
+ }
1571
+ }
1572
+
1573
+ export class StatefulPromise<T> {
1574
+ private _value: T | undefined = undefined;
1575
+ get value(): T | undefined { return this._value; }
1576
+
1577
+ private _error: unknown = undefined;
1578
+ get error(): unknown { return this._error; }
1579
+
1580
+ private _isResolved = false;
1581
+ get isResolved() { return this._isResolved; }
1582
+
1583
+ public readonly promise: Promise<T>;
1584
+
1585
+ constructor(promise: Promise<T>) {
1586
+ this.promise = promise.then(
1587
+ value => {
1588
+ this._value = value;
1589
+ this._isResolved = true;
1590
+ return value;
1591
+ },
1592
+ error => {
1593
+ this._error = error;
1594
+ this._isResolved = true;
1595
+ throw error;
1596
+ }
1597
+ );
1598
+ }
1599
+
1600
+ /**
1601
+ * Returns the resolved value.
1602
+ * Throws if the promise is not resolved yet.
1603
+ */
1604
+ public requireValue(): T {
1605
+ if (!this._isResolved) {
1606
+ throw new BugIndicatingError('Promise is not resolved yet');
1607
+ }
1608
+ if (this._error) {
1609
+ throw this._error;
1610
+ }
1611
+ return this._value!;
1612
+ }
1613
+ }
1614
+
1615
+ export class LazyStatefulPromise<T> {
1616
+ private readonly _promise = new Lazy(() => new StatefulPromise(this._compute()));
1617
+
1618
+ constructor(
1619
+ private readonly _compute: () => Promise<T>,
1620
+ ) { }
1621
+
1622
+ /**
1623
+ * Returns the resolved value.
1624
+ * Throws if the promise is not resolved yet.
1625
+ */
1626
+ public requireValue(): T {
1627
+ return this._promise.value.requireValue();
1628
+ }
1629
+
1630
+ /**
1631
+ * Returns the promise (and triggers a computation of the promise if not yet done so).
1632
+ */
1633
+ public getPromise(): Promise<T> {
1634
+ return this._promise.value.promise;
1635
+ }
1636
+
1637
+ /**
1638
+ * Reads the current value without triggering a computation of the promise.
1639
+ */
1640
+ public get currentValue(): T | undefined {
1641
+ return this._promise.rawValue?.value;
1642
+ }
1643
+ }
1644
+
1645
+ //#endregion
1646
+
1647
+ //#region
1648
+
1649
+ const enum AsyncIterableSourceState {
1650
+ Initial,
1651
+ DoneOK,
1652
+ DoneError,
1653
+ }
1654
+
1655
+ /**
1656
+ * An object that allows to emit async values asynchronously or bring the iterable to an error state using `reject()`.
1657
+ * This emitter is valid only for the duration of the executor (until the promise returned by the executor settles).
1658
+ */
1659
+ export interface AsyncIterableEmitter<T> {
1660
+ /**
1661
+ * The value will be appended at the end.
1662
+ *
1663
+ * **NOTE** If `reject()` has already been called, this method has no effect.
1664
+ */
1665
+ emitOne(value: T): void;
1666
+ /**
1667
+ * The values will be appended at the end.
1668
+ *
1669
+ * **NOTE** If `reject()` has already been called, this method has no effect.
1670
+ */
1671
+ emitMany(values: T[]): void;
1672
+ /**
1673
+ * Writing an error will permanently invalidate this iterable.
1674
+ * The current users will receive an error thrown, as will all future users.
1675
+ *
1676
+ * **NOTE** If `reject()` have already been called, this method has no effect.
1677
+ */
1678
+ reject(error: Error): void;
1679
+ }
1680
+
1681
+ /**
1682
+ * An executor for the `AsyncIterableObject` that has access to an emitter.
1683
+ */
1684
+ export interface AsyncIterableExecutor<T> {
1685
+ /**
1686
+ * @param emitter An object that allows to emit async values valid only for the duration of the executor.
1687
+ */
1688
+ (emitter: AsyncIterableEmitter<T>): void | Promise<void>;
1689
+ }
1690
+
1691
+ /**
1692
+ * A rich implementation for an `AsyncIterable<T>`.
1693
+ */
1694
+ export class AsyncIterableObject<T> implements AsyncIterable<T> {
1695
+
1696
+ public static fromArray<T>(items: T[]): AsyncIterableObject<T> {
1697
+ return new AsyncIterableObject<T>((writer) => {
1698
+ writer.emitMany(items);
1699
+ });
1700
+ }
1701
+
1702
+ public static fromPromise<T>(promise: Promise<T[]>): AsyncIterableObject<T> {
1703
+ return new AsyncIterableObject<T>(async (emitter) => {
1704
+ emitter.emitMany(await promise);
1705
+ });
1706
+ }
1707
+
1708
+ public static fromPromises<T>(promises: Promise<T>[]): AsyncIterableObject<T> {
1709
+ return new AsyncIterableObject<T>(async (emitter) => {
1710
+ await Promise.all(promises.map(async (p) => emitter.emitOne(await p)));
1711
+ });
1712
+ }
1713
+
1714
+ public static merge<T>(iterables: AsyncIterable<T>[]): AsyncIterableObject<T> {
1715
+ return new AsyncIterableObject(async (emitter) => {
1716
+ await Promise.all(iterables.map(async (iterable) => {
1717
+ for await (const item of iterable) {
1718
+ emitter.emitOne(item);
1719
+ }
1720
+ }));
1721
+ });
1722
+ }
1723
+
1724
+ public static EMPTY = AsyncIterableObject.fromArray<any>([]);
1725
+
1726
+ private _state: AsyncIterableSourceState;
1727
+ private _results: T[];
1728
+ private _error: Error | null;
1729
+ private readonly _onReturn?: () => void | Promise<void>;
1730
+ private readonly _onStateChanged: Emitter<void>;
1731
+
1732
+ constructor(executor: AsyncIterableExecutor<T>, onReturn?: () => void | Promise<void>) {
1733
+ this._state = AsyncIterableSourceState.Initial;
1734
+ this._results = [];
1735
+ this._error = null;
1736
+ this._onReturn = onReturn;
1737
+ this._onStateChanged = new Emitter<void>();
1738
+
1739
+ queueMicrotask(async () => {
1740
+ const writer: AsyncIterableEmitter<T> = {
1741
+ emitOne: (item) => this.emitOne(item),
1742
+ emitMany: (items) => this.emitMany(items),
1743
+ reject: (error) => this.reject(error)
1744
+ };
1745
+ try {
1746
+ await Promise.resolve(executor(writer));
1747
+ this.resolve();
1748
+ } catch (err) {
1749
+ this.reject(err);
1750
+ } finally {
1751
+ writer.emitOne = undefined!;
1752
+ writer.emitMany = undefined!;
1753
+ writer.reject = undefined!;
1754
+ }
1755
+ });
1756
+ }
1757
+
1758
+ [Symbol.asyncIterator](): AsyncIterator<T, undefined, undefined> {
1759
+ let i = 0;
1760
+ return {
1761
+ next: async () => {
1762
+ do {
1763
+ if (this._state === AsyncIterableSourceState.DoneError) {
1764
+ throw this._error;
1765
+ }
1766
+ if (i < this._results.length) {
1767
+ return { done: false, value: this._results[i++] };
1768
+ }
1769
+ if (this._state === AsyncIterableSourceState.DoneOK) {
1770
+ return { done: true, value: undefined };
1771
+ }
1772
+ await Event.toPromise(this._onStateChanged.event);
1773
+ } while (true);
1774
+ },
1775
+ return: async () => {
1776
+ this._onReturn?.();
1777
+ return { done: true, value: undefined };
1778
+ }
1779
+ };
1780
+ }
1781
+
1782
+ public static map<T, R>(iterable: AsyncIterable<T>, mapFn: (item: T) => R): AsyncIterableObject<R> {
1783
+ return new AsyncIterableObject<R>(async (emitter) => {
1784
+ for await (const item of iterable) {
1785
+ emitter.emitOne(mapFn(item));
1786
+ }
1787
+ });
1788
+ }
1789
+
1790
+ public map<R>(mapFn: (item: T) => R): AsyncIterableObject<R> {
1791
+ return AsyncIterableObject.map(this, mapFn);
1792
+ }
1793
+
1794
+ public static filter<T>(iterable: AsyncIterable<T>, filterFn: (item: T) => boolean): AsyncIterableObject<T> {
1795
+ return new AsyncIterableObject<T>(async (emitter) => {
1796
+ for await (const item of iterable) {
1797
+ if (filterFn(item)) {
1798
+ emitter.emitOne(item);
1799
+ }
1800
+ }
1801
+ });
1802
+ }
1803
+
1804
+ public filter(filterFn: (item: T) => boolean): AsyncIterableObject<T> {
1805
+ return AsyncIterableObject.filter(this, filterFn);
1806
+ }
1807
+
1808
+ public static coalesce<T>(iterable: AsyncIterable<T | undefined | null>): AsyncIterableObject<T> {
1809
+ return <AsyncIterableObject<T>>AsyncIterableObject.filter(iterable, item => !!item);
1810
+ }
1811
+
1812
+ public coalesce(): AsyncIterableObject<NonNullable<T>> {
1813
+ return AsyncIterableObject.coalesce(this) as AsyncIterableObject<NonNullable<T>>;
1814
+ }
1815
+
1816
+ public static async toPromise<T>(iterable: AsyncIterable<T>): Promise<T[]> {
1817
+ const result: T[] = [];
1818
+ for await (const item of iterable) {
1819
+ result.push(item);
1820
+ }
1821
+ return result;
1822
+ }
1823
+
1824
+ public toPromise(): Promise<T[]> {
1825
+ return AsyncIterableObject.toPromise(this);
1826
+ }
1827
+
1828
+ /**
1829
+ * The value will be appended at the end.
1830
+ *
1831
+ * **NOTE** If `resolve()` or `reject()` have already been called, this method has no effect.
1832
+ */
1833
+ private emitOne(value: T): void {
1834
+ if (this._state !== AsyncIterableSourceState.Initial) {
1835
+ return;
1836
+ }
1837
+ // it is important to add new values at the end,
1838
+ // as we may have iterators already running on the array
1839
+ this._results.push(value);
1840
+ this._onStateChanged.fire();
1841
+ }
1842
+
1843
+ /**
1844
+ * The values will be appended at the end.
1845
+ *
1846
+ * **NOTE** If `resolve()` or `reject()` have already been called, this method has no effect.
1847
+ */
1848
+ private emitMany(values: T[]): void {
1849
+ if (this._state !== AsyncIterableSourceState.Initial) {
1850
+ return;
1851
+ }
1852
+ // it is important to add new values at the end,
1853
+ // as we may have iterators already running on the array
1854
+ this._results = this._results.concat(values);
1855
+ this._onStateChanged.fire();
1856
+ }
1857
+
1858
+ /**
1859
+ * Calling `resolve()` will mark the result array as complete.
1860
+ *
1861
+ * **NOTE** `resolve()` must be called, otherwise all consumers of this iterable will hang indefinitely, similar to a non-resolved promise.
1862
+ * **NOTE** If `resolve()` or `reject()` have already been called, this method has no effect.
1863
+ */
1864
+ private resolve(): void {
1865
+ if (this._state !== AsyncIterableSourceState.Initial) {
1866
+ return;
1867
+ }
1868
+ this._state = AsyncIterableSourceState.DoneOK;
1869
+ this._onStateChanged.fire();
1870
+ }
1871
+
1872
+ /**
1873
+ * Writing an error will permanently invalidate this iterable.
1874
+ * The current users will receive an error thrown, as will all future users.
1875
+ *
1876
+ * **NOTE** If `resolve()` or `reject()` have already been called, this method has no effect.
1877
+ */
1878
+ private reject(error: Error) {
1879
+ if (this._state !== AsyncIterableSourceState.Initial) {
1880
+ return;
1881
+ }
1882
+ this._state = AsyncIterableSourceState.DoneError;
1883
+ this._error = error;
1884
+ this._onStateChanged.fire();
1885
+ }
1886
+ }
1887
+
1888
+ export class CancelableAsyncIterableObject<T> extends AsyncIterableObject<T> {
1889
+ constructor(
1890
+ private readonly _source: CancellationTokenSource,
1891
+ executor: AsyncIterableExecutor<T>
1892
+ ) {
1893
+ super(executor);
1894
+ }
1895
+
1896
+ cancel(): void {
1897
+ this._source.cancel();
1898
+ }
1899
+ }
1900
+
1901
+ export function createCancelableAsyncIterable<T>(callback: (token: CancellationToken) => AsyncIterable<T>): CancelableAsyncIterableObject<T> {
1902
+ const source = new CancellationTokenSource();
1903
+ const innerIterable = callback(source.token);
1904
+
1905
+ return new CancelableAsyncIterableObject<T>(source, async (emitter) => {
1906
+ const subscription = source.token.onCancellationRequested(() => {
1907
+ subscription.dispose();
1908
+ source.dispose();
1909
+ emitter.reject(new CancellationError());
1910
+ });
1911
+ try {
1912
+ for await (const item of innerIterable) {
1913
+ if (source.token.isCancellationRequested) {
1914
+ // canceled in the meantime
1915
+ return;
1916
+ }
1917
+ emitter.emitOne(item);
1918
+ }
1919
+ subscription.dispose();
1920
+ source.dispose();
1921
+ } catch (err) {
1922
+ subscription.dispose();
1923
+ source.dispose();
1924
+ emitter.reject(err);
1925
+ }
1926
+ });
1927
+ }
1928
+
1929
+ export class AsyncIterableSource<T> {
1930
+
1931
+ private readonly _deferred = new DeferredPromise<void>();
1932
+ private readonly _asyncIterable: AsyncIterableObject<T>;
1933
+
1934
+ private _errorFn: (error: Error) => void;
1935
+ private _emitFn: (item: T) => void;
1936
+
1937
+ /**
1938
+ *
1939
+ * @param onReturn A function that will be called when consuming the async iterable
1940
+ * has finished by the consumer, e.g the for-await-loop has be existed (break, return) early.
1941
+ * This is NOT called when resolving this source by its owner.
1942
+ */
1943
+ constructor(onReturn?: () => Promise<void> | void) {
1944
+ this._asyncIterable = new AsyncIterableObject(emitter => {
1945
+
1946
+ if (earlyError) {
1947
+ emitter.reject(earlyError);
1948
+ return;
1949
+ }
1950
+ if (earlyItems) {
1951
+ emitter.emitMany(earlyItems);
1952
+ }
1953
+ this._errorFn = (error: Error) => emitter.reject(error);
1954
+ this._emitFn = (item: T) => emitter.emitOne(item);
1955
+ return this._deferred.p;
1956
+ }, onReturn);
1957
+
1958
+ let earlyError: Error | undefined;
1959
+ let earlyItems: T[] | undefined;
1960
+
1961
+ this._emitFn = (item: T) => {
1962
+ if (!earlyItems) {
1963
+ earlyItems = [];
1964
+ }
1965
+ earlyItems.push(item);
1966
+ };
1967
+ this._errorFn = (error: Error) => {
1968
+ if (!earlyError) {
1969
+ earlyError = error;
1970
+ }
1971
+ };
1972
+ }
1973
+
1974
+ get asyncIterable(): AsyncIterableObject<T> {
1975
+ return this._asyncIterable;
1976
+ }
1977
+
1978
+ resolve(): void {
1979
+ this._deferred.complete();
1980
+ }
1981
+
1982
+ reject(error: Error): void {
1983
+ this._errorFn(error);
1984
+ this._deferred.complete();
1985
+ }
1986
+
1987
+ emitOne(item: T): void {
1988
+ this._emitFn(item);
1989
+ }
1990
+ }
1991
+
1992
+ //#endregion