@opensumi/ide-utils 2.21.13 → 2.22.0
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.
- package/lib/arrays.d.ts +3 -3
- package/lib/arrays.d.ts.map +1 -1
- package/lib/arrays.js.map +1 -1
- package/lib/async.d.ts +3 -3
- package/lib/async.d.ts.map +1 -1
- package/lib/buffer.js +4 -4
- package/lib/buffer.js.map +1 -1
- package/lib/character-classifier.js +3 -3
- package/lib/character-classifier.js.map +1 -1
- package/lib/decorators.d.ts +1 -1
- package/lib/decorators.d.ts.map +1 -1
- package/lib/encoding.d.ts +7 -1
- package/lib/encoding.d.ts.map +1 -1
- package/lib/encoding.js +15 -2
- package/lib/encoding.js.map +1 -1
- package/lib/errors.d.ts +2 -2
- package/lib/errors.d.ts.map +1 -1
- package/lib/event.d.ts +2 -2
- package/lib/event.d.ts.map +1 -1
- package/lib/filters.d.ts +3 -3
- package/lib/filters.d.ts.map +1 -1
- package/lib/filters.js +25 -25
- package/lib/filters.js.map +1 -1
- package/lib/glob.d.ts +2 -2
- package/lib/glob.d.ts.map +1 -1
- package/lib/glob.js +1 -1
- package/lib/glob.js.map +1 -1
- package/lib/hash.js +10 -10
- package/lib/hash.js.map +1 -1
- package/lib/iterator.d.ts +1 -1
- package/lib/iterator.d.ts.map +1 -1
- package/lib/linked-text.d.ts +1 -1
- package/lib/linked-text.d.ts.map +1 -1
- package/lib/linked-text.js.map +1 -1
- package/lib/map.js +17 -17
- package/lib/map.js.map +1 -1
- package/lib/objects.d.ts +1 -0
- package/lib/objects.d.ts.map +1 -1
- package/lib/objects.js +38 -1
- package/lib/objects.js.map +1 -1
- package/lib/os.d.ts +1 -8
- package/lib/os.d.ts.map +1 -1
- package/lib/os.js +4 -8
- package/lib/os.js.map +1 -1
- package/lib/path.d.ts +1 -1
- package/lib/path.d.ts.map +1 -1
- package/lib/path.js +29 -29
- package/lib/path.js.map +1 -1
- package/lib/platform.js +8 -8
- package/lib/platform.js.map +1 -1
- package/lib/process.d.ts.map +1 -1
- package/lib/process.js +28 -13
- package/lib/process.js.map +1 -1
- package/lib/progress.js +3 -3
- package/lib/progress.js.map +1 -1
- package/lib/strings.js +9 -9
- package/lib/strings.js.map +1 -1
- package/lib/types.d.ts +5 -1
- package/lib/types.d.ts.map +1 -1
- package/lib/types.js +8 -1
- package/lib/types.js.map +1 -1
- package/lib/uint.js +4 -4
- package/lib/uint.js.map +1 -1
- package/lib/uri.js +8 -8
- package/lib/uri.js.map +1 -1
- package/lib/uuid.d.ts +1 -1
- package/lib/uuid.d.ts.map +1 -1
- package/lib/uuid.js +3 -2
- package/lib/uuid.js.map +1 -1
- package/package.json +8 -6
- package/src/ansi.ts +10 -0
- package/src/argv.ts +57 -0
- package/src/arrays.ts +328 -0
- package/src/async.ts +580 -0
- package/src/buffer.ts +290 -0
- package/src/cache.ts +37 -0
- package/src/cancellation.ts +136 -0
- package/src/charCode.ts +425 -0
- package/src/character-classifier.ts +82 -0
- package/src/const/encoding.ts +242 -0
- package/src/const/index.ts +1 -0
- package/src/date.ts +36 -0
- package/src/decorators.ts +166 -0
- package/src/disposable.ts +377 -0
- package/src/encoding.ts +278 -0
- package/src/errors.ts +192 -0
- package/src/event.ts +1024 -0
- package/src/file-uri.ts +41 -0
- package/src/filters.ts +887 -0
- package/src/functional.ts +43 -0
- package/src/glob.ts +752 -0
- package/src/hash.ts +336 -0
- package/src/iconLabels.ts +149 -0
- package/src/index.ts +40 -0
- package/src/iterator.ts +36 -0
- package/src/lifecycle.ts +48 -0
- package/src/linked-list.ts +144 -0
- package/src/linked-text.ts +55 -0
- package/src/lru-map.ts +133 -0
- package/src/map.ts +934 -0
- package/src/marshalling.ts +62 -0
- package/src/objects.ts +145 -0
- package/src/os.ts +76 -0
- package/src/path.ts +1885 -0
- package/src/platform.ts +194 -0
- package/src/process.ts +43 -0
- package/src/progress.ts +83 -0
- package/src/promises.ts +23 -0
- package/src/sequence.ts +28 -0
- package/src/strings.ts +1002 -0
- package/src/types.ts +214 -0
- package/src/uint.ts +59 -0
- package/src/uri.ts +298 -0
- package/src/uuid.ts +6 -0
package/src/event.ts
ADDED
|
@@ -0,0 +1,1024 @@
|
|
|
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
|
+
// Some code copied and modified from https://github.com/microsoft/vscode/blob/1.44.0/src/vs/base/common/event.ts
|
|
6
|
+
|
|
7
|
+
import { CancellationToken } from './cancellation';
|
|
8
|
+
import { DisposableStore, combinedDisposable, Disposable, IDisposable, toDisposable } from './disposable';
|
|
9
|
+
import { onUnexpectedError } from './errors';
|
|
10
|
+
import { once as onceFn } from './functional';
|
|
11
|
+
import { LinkedList } from './linked-list';
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* 重要备注
|
|
15
|
+
* 由于 vscode 内部的 DisposableStore 有个私有属性导致类型冲突
|
|
16
|
+
* 因此在 event 模块中的 disposables 不再支持传入 DisposableStore 类型
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* To an event a function with one or zero parameters
|
|
21
|
+
* can be subscribed. The event is the subscriber function itself.
|
|
22
|
+
*/
|
|
23
|
+
export type Event<T> = (listener: (e: T) => any, thisArgs?: any, disposables?: IDisposable[]) => IDisposable;
|
|
24
|
+
|
|
25
|
+
export namespace Event {
|
|
26
|
+
const _disposable = { dispose() {} };
|
|
27
|
+
export const None: Event<any> = function () {
|
|
28
|
+
return _disposable;
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Given an event, returns another event which only fires once.
|
|
33
|
+
*/
|
|
34
|
+
export function once<T>(event: Event<T>): Event<T> {
|
|
35
|
+
return (listener, thisArgs = null, disposables?) => {
|
|
36
|
+
// we need this, in case the event fires during the listener call
|
|
37
|
+
let didFire = false;
|
|
38
|
+
const result = event(
|
|
39
|
+
(e) => {
|
|
40
|
+
if (didFire) {
|
|
41
|
+
return;
|
|
42
|
+
} else if (result) {
|
|
43
|
+
result.dispose();
|
|
44
|
+
} else {
|
|
45
|
+
didFire = true;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
return listener.call(thisArgs, e);
|
|
49
|
+
},
|
|
50
|
+
null,
|
|
51
|
+
disposables,
|
|
52
|
+
);
|
|
53
|
+
|
|
54
|
+
if (didFire) {
|
|
55
|
+
result.dispose();
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
return result;
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Given an event and a `map` function, returns another event which maps each element
|
|
64
|
+
* throught the mapping function.
|
|
65
|
+
*/
|
|
66
|
+
export function map<I, O>(event: Event<I>, map: (i: I) => O): Event<O> {
|
|
67
|
+
return snapshot((listener, thisArgs = null, disposables?) =>
|
|
68
|
+
event((i) => listener.call(thisArgs, map(i)), null, disposables),
|
|
69
|
+
);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Given an event and an `each` function, returns another identical event and calls
|
|
74
|
+
* the `each` function per each element.
|
|
75
|
+
*/
|
|
76
|
+
export function forEach<I>(event: Event<I>, each: (i: I) => void): Event<I> {
|
|
77
|
+
return snapshot((listener, thisArgs = null, disposables?) =>
|
|
78
|
+
event(
|
|
79
|
+
(i) => {
|
|
80
|
+
each(i);
|
|
81
|
+
listener.call(thisArgs, i);
|
|
82
|
+
},
|
|
83
|
+
null,
|
|
84
|
+
disposables,
|
|
85
|
+
),
|
|
86
|
+
);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* Given an event and a `filter` function, returns another event which emits those
|
|
91
|
+
* elements for which the `filter` function returns `true`.
|
|
92
|
+
*/
|
|
93
|
+
export function filter<T>(event: Event<T>, filter: (e: T) => boolean): Event<T>;
|
|
94
|
+
export function filter<T, R>(event: Event<T | R>, filter: (e: T | R) => e is R): Event<R>;
|
|
95
|
+
export function filter<T>(event: Event<T>, filter: (e: T) => boolean): Event<T> {
|
|
96
|
+
return snapshot((listener, thisArgs = null, disposables?) =>
|
|
97
|
+
event((e) => filter(e) && listener.call(thisArgs, e), null, disposables),
|
|
98
|
+
);
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* Given an event, returns the same event but typed as `Event<void>`.
|
|
103
|
+
*/
|
|
104
|
+
export function signal<T>(event: Event<T>): Event<void> {
|
|
105
|
+
return event as Event<any> as Event<void>;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* Given a collection of events, returns a single event which emits
|
|
110
|
+
* whenever any of the provided events emit.
|
|
111
|
+
*/
|
|
112
|
+
export function any<T>(...events: Event<T>[]): Event<T> {
|
|
113
|
+
return (listener, thisArgs = null, disposables?) =>
|
|
114
|
+
combinedDisposable(events.map((event) => event((e) => listener.call(thisArgs, e), null, disposables)));
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/**
|
|
118
|
+
* Given an event and a `merge` function, returns another event which maps each element
|
|
119
|
+
* and the cummulative result throught the `merge` function. Similar to `map`, but with memory.
|
|
120
|
+
*/
|
|
121
|
+
export function reduce<I, O>(event: Event<I>, merge: (last: O | undefined, event: I) => O, initial?: O): Event<O> {
|
|
122
|
+
let output: O | undefined = initial;
|
|
123
|
+
|
|
124
|
+
return map<I, O>(event, (e) => {
|
|
125
|
+
output = merge(output, e);
|
|
126
|
+
return output;
|
|
127
|
+
});
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
/**
|
|
131
|
+
* Given a chain of event processing functions (filter, map, etc), each
|
|
132
|
+
* function will be invoked per event & per listener. Snapshotting an event
|
|
133
|
+
* chain allows each function to be invoked just once per event.
|
|
134
|
+
*/
|
|
135
|
+
export function snapshot<T>(event: Event<T>): Event<T> {
|
|
136
|
+
let listener: IDisposable;
|
|
137
|
+
const emitter = new Emitter<T>({
|
|
138
|
+
onFirstListenerAdd() {
|
|
139
|
+
listener = event(emitter.fire, emitter);
|
|
140
|
+
},
|
|
141
|
+
onLastListenerRemove() {
|
|
142
|
+
listener.dispose();
|
|
143
|
+
},
|
|
144
|
+
});
|
|
145
|
+
|
|
146
|
+
return emitter.event;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
/**
|
|
150
|
+
* Debounces the provided event, given a `merge` function.
|
|
151
|
+
*
|
|
152
|
+
* @param event The input event.
|
|
153
|
+
* @param merge The reducing function.
|
|
154
|
+
* @param delay The debouncing delay in millis.
|
|
155
|
+
* @param leading Whether the event should fire in the leading phase of the timeout.
|
|
156
|
+
* @param leakWarningThreshold The leak warning threshold override.
|
|
157
|
+
*/
|
|
158
|
+
export function debounce<T>(
|
|
159
|
+
event: Event<T>,
|
|
160
|
+
merge: (last: T | undefined, event: T) => T,
|
|
161
|
+
delay?: number,
|
|
162
|
+
leading?: boolean,
|
|
163
|
+
leakWarningThreshold?: number,
|
|
164
|
+
): Event<T>;
|
|
165
|
+
export function debounce<I, O>(
|
|
166
|
+
event: Event<I>,
|
|
167
|
+
merge: (last: O | undefined, event: I) => O,
|
|
168
|
+
delay?: number,
|
|
169
|
+
leading?: boolean,
|
|
170
|
+
leakWarningThreshold?: number,
|
|
171
|
+
): Event<O>;
|
|
172
|
+
export function debounce<I, O>(
|
|
173
|
+
event: Event<I>,
|
|
174
|
+
merge: (last: O | undefined, event: I) => O,
|
|
175
|
+
delay = 100,
|
|
176
|
+
leading = false,
|
|
177
|
+
leakWarningThreshold?: number,
|
|
178
|
+
): Event<O> {
|
|
179
|
+
let subscription: IDisposable;
|
|
180
|
+
let output: O | undefined;
|
|
181
|
+
let handle: any;
|
|
182
|
+
let numDebouncedCalls = 0;
|
|
183
|
+
|
|
184
|
+
const emitter = new Emitter<O>({
|
|
185
|
+
leakWarningThreshold,
|
|
186
|
+
onFirstListenerAdd() {
|
|
187
|
+
subscription = event((cur) => {
|
|
188
|
+
numDebouncedCalls++;
|
|
189
|
+
output = merge(output, cur);
|
|
190
|
+
|
|
191
|
+
if (leading && !handle) {
|
|
192
|
+
emitter.fire(output);
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
clearTimeout(handle);
|
|
196
|
+
handle = setTimeout(() => {
|
|
197
|
+
const _output = output;
|
|
198
|
+
output = undefined;
|
|
199
|
+
handle = undefined;
|
|
200
|
+
if (!leading || numDebouncedCalls > 1) {
|
|
201
|
+
emitter.fire(_output!);
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
numDebouncedCalls = 0;
|
|
205
|
+
}, delay);
|
|
206
|
+
});
|
|
207
|
+
},
|
|
208
|
+
onLastListenerRemove() {
|
|
209
|
+
subscription.dispose();
|
|
210
|
+
},
|
|
211
|
+
});
|
|
212
|
+
|
|
213
|
+
return emitter.event;
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
/**
|
|
217
|
+
* Given an event, it returns another event which fires only once and as soon as
|
|
218
|
+
* the input event emits. The event data is the number of millis it took for the
|
|
219
|
+
* event to fire.
|
|
220
|
+
*/
|
|
221
|
+
export function stopwatch<T>(event: Event<T>): Event<number> {
|
|
222
|
+
const start = new Date().getTime();
|
|
223
|
+
return map(once(event), (_) => new Date().getTime() - start);
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
/**
|
|
227
|
+
* Given an event, it returns another event which fires only when the event
|
|
228
|
+
* element changes.
|
|
229
|
+
*/
|
|
230
|
+
export function latch<T>(event: Event<T>): Event<T> {
|
|
231
|
+
let firstCall = true;
|
|
232
|
+
let cache: T;
|
|
233
|
+
|
|
234
|
+
return filter(event, (value) => {
|
|
235
|
+
const shouldEmit = firstCall || value !== cache;
|
|
236
|
+
firstCall = false;
|
|
237
|
+
cache = value;
|
|
238
|
+
return shouldEmit;
|
|
239
|
+
});
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
/**
|
|
243
|
+
* Buffers the provided event until a first listener comes
|
|
244
|
+
* along, at which point fire all the events at once and
|
|
245
|
+
* pipe the event from then on.
|
|
246
|
+
*
|
|
247
|
+
* ```typescript
|
|
248
|
+
* const emitter = new Emitter<number>();
|
|
249
|
+
* const event = emitter.event;
|
|
250
|
+
* const bufferedEvent = buffer(event);
|
|
251
|
+
*
|
|
252
|
+
* emitter.fire(1);
|
|
253
|
+
* emitter.fire(2);
|
|
254
|
+
* emitter.fire(3);
|
|
255
|
+
* // nothing...
|
|
256
|
+
*
|
|
257
|
+
* const listener = bufferedEvent(num => console.log(num));
|
|
258
|
+
* // 1, 2, 3
|
|
259
|
+
*
|
|
260
|
+
* emitter.fire(4);
|
|
261
|
+
* // 4
|
|
262
|
+
* ```
|
|
263
|
+
*/
|
|
264
|
+
export function buffer<T>(event: Event<T>, nextTick = false, _buffer: T[] = []): Event<T> {
|
|
265
|
+
let buffer: T[] | null = _buffer.slice();
|
|
266
|
+
|
|
267
|
+
let listener: IDisposable | null = event((e) => {
|
|
268
|
+
if (buffer) {
|
|
269
|
+
buffer.push(e);
|
|
270
|
+
} else {
|
|
271
|
+
emitter.fire(e);
|
|
272
|
+
}
|
|
273
|
+
});
|
|
274
|
+
|
|
275
|
+
const flush = () => {
|
|
276
|
+
if (buffer) {
|
|
277
|
+
buffer.forEach((e) => emitter.fire(e));
|
|
278
|
+
}
|
|
279
|
+
buffer = null;
|
|
280
|
+
};
|
|
281
|
+
|
|
282
|
+
const emitter = new Emitter<T>({
|
|
283
|
+
onFirstListenerAdd() {
|
|
284
|
+
if (!listener) {
|
|
285
|
+
listener = event((e) => emitter.fire(e));
|
|
286
|
+
}
|
|
287
|
+
},
|
|
288
|
+
|
|
289
|
+
onFirstListenerDidAdd() {
|
|
290
|
+
if (buffer) {
|
|
291
|
+
if (nextTick) {
|
|
292
|
+
setTimeout(flush);
|
|
293
|
+
} else {
|
|
294
|
+
flush();
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
},
|
|
298
|
+
|
|
299
|
+
onLastListenerRemove() {
|
|
300
|
+
if (listener) {
|
|
301
|
+
listener.dispose();
|
|
302
|
+
}
|
|
303
|
+
listener = null;
|
|
304
|
+
},
|
|
305
|
+
});
|
|
306
|
+
|
|
307
|
+
return emitter.event;
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
export interface IChainableEvent<T> {
|
|
311
|
+
event: Event<T>;
|
|
312
|
+
map<O>(fn: (i: T) => O): IChainableEvent<O>;
|
|
313
|
+
forEach(fn: (i: T) => void): IChainableEvent<T>;
|
|
314
|
+
filter(fn: (e: T) => boolean): IChainableEvent<T>;
|
|
315
|
+
reduce<R>(merge: (last: R | undefined, event: T) => R, initial?: R): IChainableEvent<R>;
|
|
316
|
+
latch(): IChainableEvent<T>;
|
|
317
|
+
on(listener: (e: T) => any, thisArgs?: any, disposables?: IDisposable[]): IDisposable;
|
|
318
|
+
once(listener: (e: T) => any, thisArgs?: any, disposables?: IDisposable[]): IDisposable;
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
class ChainableEvent<T> implements IChainableEvent<T> {
|
|
322
|
+
constructor(readonly event: Event<T>) {}
|
|
323
|
+
|
|
324
|
+
map<O>(fn: (i: T) => O): IChainableEvent<O> {
|
|
325
|
+
return new ChainableEvent(map(this.event, fn));
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
forEach(fn: (i: T) => void): IChainableEvent<T> {
|
|
329
|
+
return new ChainableEvent(forEach(this.event, fn));
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
filter(fn: (e: T) => boolean): IChainableEvent<T> {
|
|
333
|
+
return new ChainableEvent(filter(this.event, fn));
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
reduce<R>(merge: (last: R | undefined, event: T) => R, initial?: R): IChainableEvent<R> {
|
|
337
|
+
return new ChainableEvent(reduce(this.event, merge, initial));
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
latch(): IChainableEvent<T> {
|
|
341
|
+
return new ChainableEvent(latch(this.event));
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
on(listener: (e: T) => any, thisArgs: any, disposables: IDisposable[]) {
|
|
345
|
+
return this.event(listener, thisArgs, disposables);
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
once(listener: (e: T) => any, thisArgs: any, disposables: IDisposable[]) {
|
|
349
|
+
return once(this.event)(listener, thisArgs, disposables);
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
export function chain<T>(event: Event<T>): IChainableEvent<T> {
|
|
354
|
+
return new ChainableEvent(event);
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
export interface NodeEventEmitter {
|
|
358
|
+
on(event: string | symbol, listener: Function): this;
|
|
359
|
+
removeListener(event: string | symbol, listener: Function): this;
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
export function fromNodeEventEmitter<T>(
|
|
363
|
+
emitter: NodeEventEmitter,
|
|
364
|
+
eventName: string,
|
|
365
|
+
map: (...args: any[]) => T = (id) => id,
|
|
366
|
+
): Event<T> {
|
|
367
|
+
const fn = (...args: any[]) => result.fire(map(...args));
|
|
368
|
+
const onFirstListenerAdd = () => emitter.on(eventName, fn);
|
|
369
|
+
const onLastListenerRemove = () => emitter.removeListener(eventName, fn);
|
|
370
|
+
const result = new Emitter<T>({ onFirstListenerAdd, onLastListenerRemove });
|
|
371
|
+
|
|
372
|
+
return result.event;
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
export function fromPromise<T = any>(promise: Promise<T>): Event<undefined> {
|
|
376
|
+
const emitter = new Emitter<undefined>();
|
|
377
|
+
let shouldEmit = false;
|
|
378
|
+
|
|
379
|
+
promise
|
|
380
|
+
.then(undefined, () => null)
|
|
381
|
+
.then(() => {
|
|
382
|
+
if (!shouldEmit) {
|
|
383
|
+
setTimeout(() => emitter.fire(undefined), 0);
|
|
384
|
+
} else {
|
|
385
|
+
emitter.fire(undefined);
|
|
386
|
+
}
|
|
387
|
+
});
|
|
388
|
+
|
|
389
|
+
shouldEmit = true;
|
|
390
|
+
return emitter.event;
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
export function toPromise<T>(event: Event<T>): Promise<T> {
|
|
394
|
+
return new Promise((c) => once(event)(c));
|
|
395
|
+
}
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
type Listener<T> = [(e: T) => void, any] | ((e: T) => void);
|
|
399
|
+
|
|
400
|
+
export interface EmitterOptions {
|
|
401
|
+
onFirstListenerAdd?: Function;
|
|
402
|
+
onFirstListenerDidAdd?: Function;
|
|
403
|
+
onListenerDidAdd?: Function;
|
|
404
|
+
onLastListenerRemove?: Function;
|
|
405
|
+
leakWarningThreshold?: number;
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
let _globalLeakWarningThreshold = -1;
|
|
409
|
+
export function setGlobalLeakWarningThreshold(n: number): IDisposable {
|
|
410
|
+
const oldValue = _globalLeakWarningThreshold;
|
|
411
|
+
_globalLeakWarningThreshold = n;
|
|
412
|
+
return {
|
|
413
|
+
dispose() {
|
|
414
|
+
_globalLeakWarningThreshold = oldValue;
|
|
415
|
+
},
|
|
416
|
+
};
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
class LeakageMonitor {
|
|
420
|
+
private _stacks: Map<string, number> | undefined;
|
|
421
|
+
private _warnCountdown = 0;
|
|
422
|
+
|
|
423
|
+
constructor(readonly customThreshold?: number, readonly name: string = Math.random().toString(18).slice(2, 5)) {}
|
|
424
|
+
|
|
425
|
+
dispose(): void {
|
|
426
|
+
if (this._stacks) {
|
|
427
|
+
this._stacks.clear();
|
|
428
|
+
}
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
check(listenerCount: number): undefined | (() => void) {
|
|
432
|
+
let threshold = _globalLeakWarningThreshold;
|
|
433
|
+
if (typeof this.customThreshold === 'number') {
|
|
434
|
+
threshold = this.customThreshold;
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
if (threshold <= 0 || listenerCount < threshold) {
|
|
438
|
+
return undefined;
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
if (!this._stacks) {
|
|
442
|
+
this._stacks = new Map();
|
|
443
|
+
}
|
|
444
|
+
const stack = new Error().stack!.split('\n').slice(3).join('\n');
|
|
445
|
+
const count = this._stacks.get(stack) || 0;
|
|
446
|
+
this._stacks.set(stack, count + 1);
|
|
447
|
+
this._warnCountdown -= 1;
|
|
448
|
+
|
|
449
|
+
if (this._warnCountdown <= 0) {
|
|
450
|
+
// only warn on first exceed and then every time the limit
|
|
451
|
+
// is exceeded by 50% again
|
|
452
|
+
this._warnCountdown = threshold * 0.5;
|
|
453
|
+
|
|
454
|
+
// find most frequent listener and print warning
|
|
455
|
+
let topStack = '';
|
|
456
|
+
let topCount = 0;
|
|
457
|
+
this._stacks.forEach((count, stack) => {
|
|
458
|
+
if (!topStack || topCount < count) {
|
|
459
|
+
topStack = stack;
|
|
460
|
+
topCount = count;
|
|
461
|
+
}
|
|
462
|
+
});
|
|
463
|
+
|
|
464
|
+
// eslint-disable-next-line no-console
|
|
465
|
+
console.warn(
|
|
466
|
+
`[${this.name}] potential listener LEAK detected, having ${listenerCount} listeners already. MOST frequent listener (${topCount}):`,
|
|
467
|
+
);
|
|
468
|
+
// eslint-disable-next-line no-console
|
|
469
|
+
console.warn(topStack);
|
|
470
|
+
}
|
|
471
|
+
|
|
472
|
+
return () => {
|
|
473
|
+
const count = this._stacks!.get(stack) || 0;
|
|
474
|
+
this._stacks!.set(stack, count - 1);
|
|
475
|
+
};
|
|
476
|
+
}
|
|
477
|
+
}
|
|
478
|
+
|
|
479
|
+
/**
|
|
480
|
+
* The Emitter can be used to expose an Event to the public
|
|
481
|
+
* to fire it from the insides.
|
|
482
|
+
* Sample:
|
|
483
|
+
class Document {
|
|
484
|
+
|
|
485
|
+
private _onDidChange = new Emitter<(value:string)=>any>();
|
|
486
|
+
|
|
487
|
+
public onDidChange = this._onDidChange.event;
|
|
488
|
+
|
|
489
|
+
// getter-style
|
|
490
|
+
// get onDidChange(): Event<(value:string)=>any> {
|
|
491
|
+
// return this._onDidChange.event;
|
|
492
|
+
// }
|
|
493
|
+
|
|
494
|
+
private _doIt() {
|
|
495
|
+
//...
|
|
496
|
+
this._onDidChange.fire(value);
|
|
497
|
+
}
|
|
498
|
+
}
|
|
499
|
+
*/
|
|
500
|
+
|
|
501
|
+
export interface IAsyncResult<T> {
|
|
502
|
+
err?: Error;
|
|
503
|
+
result?: T;
|
|
504
|
+
}
|
|
505
|
+
export class Emitter<T> {
|
|
506
|
+
private static readonly _noop = function () {};
|
|
507
|
+
|
|
508
|
+
private readonly _options?: EmitterOptions;
|
|
509
|
+
private readonly _leakageMon?: LeakageMonitor;
|
|
510
|
+
private _disposed = false;
|
|
511
|
+
private _event?: Event<T>;
|
|
512
|
+
private _deliveryQueue?: LinkedList<[Listener<T>, T]>;
|
|
513
|
+
protected _listeners?: LinkedList<Listener<T>>;
|
|
514
|
+
|
|
515
|
+
constructor(options?: EmitterOptions) {
|
|
516
|
+
this._options = options;
|
|
517
|
+
this._leakageMon =
|
|
518
|
+
_globalLeakWarningThreshold > 0
|
|
519
|
+
? new LeakageMonitor(this._options && this._options.leakWarningThreshold)
|
|
520
|
+
: undefined;
|
|
521
|
+
}
|
|
522
|
+
|
|
523
|
+
/**
|
|
524
|
+
* For the public to allow to subscribe
|
|
525
|
+
* to events from this Emitter
|
|
526
|
+
*/
|
|
527
|
+
get event(): Event<T> {
|
|
528
|
+
if (!this._event) {
|
|
529
|
+
this._event = (listener: (e: T) => any, thisArgs?: any, disposables?: IDisposable[]) => {
|
|
530
|
+
if (!this._listeners) {
|
|
531
|
+
this._listeners = new LinkedList();
|
|
532
|
+
}
|
|
533
|
+
|
|
534
|
+
const firstListener = this._listeners.isEmpty();
|
|
535
|
+
|
|
536
|
+
if (firstListener && this._options && this._options.onFirstListenerAdd) {
|
|
537
|
+
this._options.onFirstListenerAdd(this);
|
|
538
|
+
}
|
|
539
|
+
|
|
540
|
+
const remove = this._listeners.push(!thisArgs ? listener : [listener, thisArgs]);
|
|
541
|
+
|
|
542
|
+
if (firstListener && this._options && this._options.onFirstListenerDidAdd) {
|
|
543
|
+
this._options.onFirstListenerDidAdd(this);
|
|
544
|
+
}
|
|
545
|
+
|
|
546
|
+
if (this._options && this._options.onListenerDidAdd) {
|
|
547
|
+
this._options.onListenerDidAdd(this, listener, thisArgs);
|
|
548
|
+
}
|
|
549
|
+
|
|
550
|
+
// check and record this emitter for potential leakage
|
|
551
|
+
let removeMonitor: (() => void) | undefined;
|
|
552
|
+
if (this._leakageMon) {
|
|
553
|
+
removeMonitor = this._leakageMon.check(this._listeners.size);
|
|
554
|
+
}
|
|
555
|
+
|
|
556
|
+
let result: IDisposable;
|
|
557
|
+
result = {
|
|
558
|
+
dispose: () => {
|
|
559
|
+
if (removeMonitor) {
|
|
560
|
+
removeMonitor();
|
|
561
|
+
}
|
|
562
|
+
result.dispose = Emitter._noop;
|
|
563
|
+
if (!this._disposed) {
|
|
564
|
+
remove();
|
|
565
|
+
if (this._options && this._options.onLastListenerRemove) {
|
|
566
|
+
const hasListeners = this._listeners && !this._listeners.isEmpty();
|
|
567
|
+
if (!hasListeners) {
|
|
568
|
+
this._options.onLastListenerRemove(this);
|
|
569
|
+
}
|
|
570
|
+
}
|
|
571
|
+
}
|
|
572
|
+
},
|
|
573
|
+
};
|
|
574
|
+
if (disposables instanceof DisposableStore) {
|
|
575
|
+
disposables.add(result);
|
|
576
|
+
} else if (Array.isArray(disposables)) {
|
|
577
|
+
disposables.push(result);
|
|
578
|
+
}
|
|
579
|
+
|
|
580
|
+
return result;
|
|
581
|
+
};
|
|
582
|
+
}
|
|
583
|
+
return this._event;
|
|
584
|
+
}
|
|
585
|
+
|
|
586
|
+
/**
|
|
587
|
+
* To be kept private to fire an event to
|
|
588
|
+
* subscribers
|
|
589
|
+
*/
|
|
590
|
+
fire(event: T): void {
|
|
591
|
+
if (this._listeners) {
|
|
592
|
+
// put all [listener,event]-pairs into delivery queue
|
|
593
|
+
// then emit all event. an inner/nested event might be
|
|
594
|
+
// the driver of this
|
|
595
|
+
|
|
596
|
+
if (!this._deliveryQueue) {
|
|
597
|
+
this._deliveryQueue = new LinkedList();
|
|
598
|
+
}
|
|
599
|
+
|
|
600
|
+
for (let iter = this._listeners.iterator(), e = iter.next(); !e.done; e = iter.next()) {
|
|
601
|
+
this._deliveryQueue.push([e.value, event]);
|
|
602
|
+
}
|
|
603
|
+
|
|
604
|
+
while (this._deliveryQueue.size > 0) {
|
|
605
|
+
const [listener, event] = this._deliveryQueue.shift()!;
|
|
606
|
+
try {
|
|
607
|
+
if (typeof listener === 'function') {
|
|
608
|
+
listener.call(undefined, event);
|
|
609
|
+
} else {
|
|
610
|
+
listener[0].call(listener[1], event);
|
|
611
|
+
}
|
|
612
|
+
} catch (e) {
|
|
613
|
+
onUnexpectedError(e);
|
|
614
|
+
}
|
|
615
|
+
}
|
|
616
|
+
}
|
|
617
|
+
}
|
|
618
|
+
|
|
619
|
+
/**
|
|
620
|
+
* 发送一个异步事件,等待所有监听器返回,并收集返回值
|
|
621
|
+
* @param e
|
|
622
|
+
* @param timeout
|
|
623
|
+
*/
|
|
624
|
+
async fireAndAwait<R = any>(event: T, timeout = 2000): Promise<Array<IAsyncResult<R>>> {
|
|
625
|
+
if (this._listeners) {
|
|
626
|
+
if (!this._deliveryQueue) {
|
|
627
|
+
this._deliveryQueue = new LinkedList();
|
|
628
|
+
}
|
|
629
|
+
|
|
630
|
+
for (let iter = this._listeners.iterator(), e = iter.next(); !e.done; e = iter.next()) {
|
|
631
|
+
this._deliveryQueue.push([e.value, event]);
|
|
632
|
+
}
|
|
633
|
+
|
|
634
|
+
const promises: Promise<IAsyncResult<R>>[] = [];
|
|
635
|
+
|
|
636
|
+
const timeoutPromise = new Promise<IAsyncResult<R>>((resolve) => {
|
|
637
|
+
setTimeout(() => {
|
|
638
|
+
resolve({
|
|
639
|
+
err: new Error('timeout'),
|
|
640
|
+
});
|
|
641
|
+
}, timeout);
|
|
642
|
+
});
|
|
643
|
+
while (this._deliveryQueue.size > 0) {
|
|
644
|
+
const [listener, event] = this._deliveryQueue.shift()!;
|
|
645
|
+
try {
|
|
646
|
+
const promise: Promise<IAsyncResult<R>> = (async () => {
|
|
647
|
+
try {
|
|
648
|
+
if (typeof listener === 'function') {
|
|
649
|
+
return {
|
|
650
|
+
result: (await listener.call(undefined, event)) as any,
|
|
651
|
+
};
|
|
652
|
+
} else {
|
|
653
|
+
return {
|
|
654
|
+
result: (await listener[0].call(listener[1], event)) as any,
|
|
655
|
+
};
|
|
656
|
+
}
|
|
657
|
+
} catch (e) {
|
|
658
|
+
return {
|
|
659
|
+
err: e,
|
|
660
|
+
};
|
|
661
|
+
}
|
|
662
|
+
})();
|
|
663
|
+
promises.push(Promise.race([timeoutPromise, promise]));
|
|
664
|
+
} catch (e) {
|
|
665
|
+
onUnexpectedError(e);
|
|
666
|
+
}
|
|
667
|
+
}
|
|
668
|
+
return Promise.all(promises);
|
|
669
|
+
} else {
|
|
670
|
+
return [];
|
|
671
|
+
}
|
|
672
|
+
}
|
|
673
|
+
|
|
674
|
+
get listenerSize() {
|
|
675
|
+
return this._listeners ? this._listeners.size : 0;
|
|
676
|
+
}
|
|
677
|
+
|
|
678
|
+
dispose() {
|
|
679
|
+
if (this._listeners) {
|
|
680
|
+
this._listeners.clear();
|
|
681
|
+
}
|
|
682
|
+
if (this._deliveryQueue) {
|
|
683
|
+
this._deliveryQueue.clear();
|
|
684
|
+
}
|
|
685
|
+
if (this._leakageMon) {
|
|
686
|
+
this._leakageMon.dispose();
|
|
687
|
+
}
|
|
688
|
+
this._disposed = true;
|
|
689
|
+
}
|
|
690
|
+
}
|
|
691
|
+
|
|
692
|
+
export class PauseableEmitter<T> extends Emitter<T> {
|
|
693
|
+
private _isPaused = 0;
|
|
694
|
+
private _eventQueue = new LinkedList<T>();
|
|
695
|
+
private _mergeFn?: (input: T[]) => T;
|
|
696
|
+
|
|
697
|
+
constructor(options?: EmitterOptions & { merge?: (input: T[]) => T }) {
|
|
698
|
+
super(options);
|
|
699
|
+
this._mergeFn = options && options.merge;
|
|
700
|
+
}
|
|
701
|
+
|
|
702
|
+
pause(): void {
|
|
703
|
+
this._isPaused++;
|
|
704
|
+
}
|
|
705
|
+
|
|
706
|
+
resume(): void {
|
|
707
|
+
if (this._isPaused !== 0 && --this._isPaused === 0) {
|
|
708
|
+
if (this._mergeFn) {
|
|
709
|
+
// use the merge function to create a single composite
|
|
710
|
+
// event. make a copy in case firing pauses this emitter
|
|
711
|
+
const events = this._eventQueue.toArray();
|
|
712
|
+
this._eventQueue.clear();
|
|
713
|
+
super.fire(this._mergeFn(events));
|
|
714
|
+
} else {
|
|
715
|
+
// no merging, fire each event individually and test
|
|
716
|
+
// that this emitter isn't paused halfway through
|
|
717
|
+
while (!this._isPaused && this._eventQueue.size !== 0) {
|
|
718
|
+
super.fire(this._eventQueue.shift()!);
|
|
719
|
+
}
|
|
720
|
+
}
|
|
721
|
+
}
|
|
722
|
+
}
|
|
723
|
+
|
|
724
|
+
fire(event: T): void {
|
|
725
|
+
if (this._listeners) {
|
|
726
|
+
if (this._isPaused !== 0) {
|
|
727
|
+
this._eventQueue.push(event);
|
|
728
|
+
} else {
|
|
729
|
+
super.fire(event);
|
|
730
|
+
}
|
|
731
|
+
}
|
|
732
|
+
}
|
|
733
|
+
}
|
|
734
|
+
|
|
735
|
+
export interface WaitUntilEvent {
|
|
736
|
+
waitUntil?(thenable: Promise<any>): void;
|
|
737
|
+
}
|
|
738
|
+
export namespace WaitUntilEvent {
|
|
739
|
+
export async function fire<T extends WaitUntilEvent>(
|
|
740
|
+
emitter: Emitter<T>,
|
|
741
|
+
event: Pick<T, Exclude<keyof T, 'waitUntil'>>,
|
|
742
|
+
timeout: number | undefined = undefined,
|
|
743
|
+
): Promise<void> {
|
|
744
|
+
const waitables: Promise<void>[] = [];
|
|
745
|
+
const asyncEvent = Object.assign(event, {
|
|
746
|
+
waitUntil: (thenable: Promise<any>) => {
|
|
747
|
+
if (Object.isFrozen(waitables)) {
|
|
748
|
+
throw new Error('waitUntil cannot be called asynchronously.');
|
|
749
|
+
}
|
|
750
|
+
waitables.push(thenable);
|
|
751
|
+
},
|
|
752
|
+
}) as T;
|
|
753
|
+
emitter.fire(asyncEvent);
|
|
754
|
+
// Asynchronous calls to `waitUntil` should fail.
|
|
755
|
+
Object.freeze(waitables);
|
|
756
|
+
// ts 要求 delete 的属性是 optional
|
|
757
|
+
delete asyncEvent.waitUntil;
|
|
758
|
+
if (!waitables.length) {
|
|
759
|
+
return;
|
|
760
|
+
}
|
|
761
|
+
if (timeout !== undefined) {
|
|
762
|
+
await Promise.race([Promise.all(waitables), new Promise((resolve) => setTimeout(resolve, timeout))]);
|
|
763
|
+
} else {
|
|
764
|
+
await Promise.all(waitables);
|
|
765
|
+
}
|
|
766
|
+
}
|
|
767
|
+
}
|
|
768
|
+
|
|
769
|
+
export class AsyncEmitter<T extends WaitUntilEvent> extends Emitter<T> {
|
|
770
|
+
private _asyncDeliveryQueue?: LinkedList<[Listener<T>, Omit<T, 'waitUntil'>]>;
|
|
771
|
+
|
|
772
|
+
async fireAsync(
|
|
773
|
+
data: Omit<T, 'waitUntil'>,
|
|
774
|
+
token: CancellationToken,
|
|
775
|
+
promiseJoin?: (p: Promise<any>, listener: Function) => Promise<any>,
|
|
776
|
+
): Promise<void> {
|
|
777
|
+
if (!this._listeners) {
|
|
778
|
+
return;
|
|
779
|
+
}
|
|
780
|
+
|
|
781
|
+
if (!this._asyncDeliveryQueue) {
|
|
782
|
+
this._asyncDeliveryQueue = new LinkedList();
|
|
783
|
+
}
|
|
784
|
+
|
|
785
|
+
for (let iter = this._listeners.iterator(), e = iter.next(); !e.done; e = iter.next()) {
|
|
786
|
+
this._asyncDeliveryQueue.push([e.value, data]);
|
|
787
|
+
}
|
|
788
|
+
|
|
789
|
+
while (this._asyncDeliveryQueue.size > 0 && !token.isCancellationRequested) {
|
|
790
|
+
const [listener, data] = this._asyncDeliveryQueue.shift()!;
|
|
791
|
+
const thenables: Promise<any>[] = [];
|
|
792
|
+
|
|
793
|
+
const event = {
|
|
794
|
+
...data,
|
|
795
|
+
waitUntil: (p: Promise<any>): void => {
|
|
796
|
+
if (Object.isFrozen(thenables)) {
|
|
797
|
+
throw new Error('waitUntil can NOT be called asynchronous');
|
|
798
|
+
}
|
|
799
|
+
if (promiseJoin) {
|
|
800
|
+
p = promiseJoin(p, typeof listener === 'function' ? listener : listener[0]);
|
|
801
|
+
}
|
|
802
|
+
thenables.push(p);
|
|
803
|
+
},
|
|
804
|
+
} as T;
|
|
805
|
+
|
|
806
|
+
try {
|
|
807
|
+
if (typeof listener === 'function') {
|
|
808
|
+
listener.call(undefined, event);
|
|
809
|
+
} else {
|
|
810
|
+
listener[0].call(listener[1], event);
|
|
811
|
+
}
|
|
812
|
+
} catch (e) {
|
|
813
|
+
onUnexpectedError(e);
|
|
814
|
+
continue;
|
|
815
|
+
}
|
|
816
|
+
|
|
817
|
+
// freeze thenables-collection to enforce sync-calls to
|
|
818
|
+
// wait until and then wait for all thenables to resolve
|
|
819
|
+
Object.freeze(thenables);
|
|
820
|
+
await Promise.all(
|
|
821
|
+
// Promise.allSettled 只有 core-js3 才支持,先手动加 catch 处理下
|
|
822
|
+
thenables.map((thenable) => thenable.catch((e) => e)),
|
|
823
|
+
).catch((e) => onUnexpectedError(e));
|
|
824
|
+
}
|
|
825
|
+
}
|
|
826
|
+
}
|
|
827
|
+
|
|
828
|
+
export class EventMultiplexer<T> implements IDisposable {
|
|
829
|
+
private readonly emitter: Emitter<T>;
|
|
830
|
+
private hasListeners = false;
|
|
831
|
+
private events: { event: Event<T>; listener: IDisposable | null }[] = [];
|
|
832
|
+
|
|
833
|
+
constructor() {
|
|
834
|
+
this.emitter = new Emitter<T>({
|
|
835
|
+
onFirstListenerAdd: () => this.onFirstListenerAdd(),
|
|
836
|
+
onLastListenerRemove: () => this.onLastListenerRemove(),
|
|
837
|
+
});
|
|
838
|
+
}
|
|
839
|
+
|
|
840
|
+
get event(): Event<T> {
|
|
841
|
+
return this.emitter.event;
|
|
842
|
+
}
|
|
843
|
+
|
|
844
|
+
add(event: Event<T>): IDisposable {
|
|
845
|
+
const e = { event, listener: null };
|
|
846
|
+
this.events.push(e);
|
|
847
|
+
|
|
848
|
+
if (this.hasListeners) {
|
|
849
|
+
this.hook(e);
|
|
850
|
+
}
|
|
851
|
+
|
|
852
|
+
const dispose = () => {
|
|
853
|
+
if (this.hasListeners) {
|
|
854
|
+
this.unhook(e);
|
|
855
|
+
}
|
|
856
|
+
|
|
857
|
+
const idx = this.events.indexOf(e);
|
|
858
|
+
this.events.splice(idx, 1);
|
|
859
|
+
};
|
|
860
|
+
|
|
861
|
+
return toDisposable(onceFn(dispose));
|
|
862
|
+
}
|
|
863
|
+
|
|
864
|
+
private onFirstListenerAdd(): void {
|
|
865
|
+
this.hasListeners = true;
|
|
866
|
+
this.events.forEach((e) => this.hook(e));
|
|
867
|
+
}
|
|
868
|
+
|
|
869
|
+
private onLastListenerRemove(): void {
|
|
870
|
+
this.hasListeners = false;
|
|
871
|
+
this.events.forEach((e) => this.unhook(e));
|
|
872
|
+
}
|
|
873
|
+
|
|
874
|
+
private hook(e: { event: Event<T>; listener: IDisposable | null }): void {
|
|
875
|
+
e.listener = e.event((r) => this.emitter.fire(r));
|
|
876
|
+
}
|
|
877
|
+
|
|
878
|
+
private unhook(e: { event: Event<T>; listener: IDisposable | null }): void {
|
|
879
|
+
if (e.listener) {
|
|
880
|
+
e.listener.dispose();
|
|
881
|
+
}
|
|
882
|
+
e.listener = null;
|
|
883
|
+
}
|
|
884
|
+
|
|
885
|
+
dispose(): void {
|
|
886
|
+
this.emitter.dispose();
|
|
887
|
+
}
|
|
888
|
+
}
|
|
889
|
+
|
|
890
|
+
/**
|
|
891
|
+
* The EventBufferer is useful in situations in which you want
|
|
892
|
+
* to delay firing your events during some code.
|
|
893
|
+
* You can wrap that code and be sure that the event will not
|
|
894
|
+
* be fired during that wrap.
|
|
895
|
+
*
|
|
896
|
+
* ```
|
|
897
|
+
* const emitter: Emitter;
|
|
898
|
+
* const delayer = new EventDelayer();
|
|
899
|
+
* const delayedEvent = delayer.wrapEvent(emitter.event);
|
|
900
|
+
*
|
|
901
|
+
* delayedEvent(console.log);
|
|
902
|
+
*
|
|
903
|
+
* delayer.bufferEvents(() => {
|
|
904
|
+
* emitter.fire(); // event will not be fired yet
|
|
905
|
+
* });
|
|
906
|
+
*
|
|
907
|
+
* // event will only be fired at this point
|
|
908
|
+
* ```
|
|
909
|
+
*/
|
|
910
|
+
export class EventBufferer {
|
|
911
|
+
private buffers: Function[][] = [];
|
|
912
|
+
|
|
913
|
+
wrapEvent<T>(event: Event<T>): Event<T> {
|
|
914
|
+
return (listener, thisArgs?, disposables?) =>
|
|
915
|
+
event(
|
|
916
|
+
(i) => {
|
|
917
|
+
const buffer = this.buffers[this.buffers.length - 1];
|
|
918
|
+
|
|
919
|
+
if (buffer) {
|
|
920
|
+
buffer.push(() => listener.call(thisArgs, i));
|
|
921
|
+
} else {
|
|
922
|
+
listener.call(thisArgs, i);
|
|
923
|
+
}
|
|
924
|
+
},
|
|
925
|
+
undefined,
|
|
926
|
+
disposables,
|
|
927
|
+
);
|
|
928
|
+
}
|
|
929
|
+
|
|
930
|
+
bufferEvents<R = void>(fn: () => R): R {
|
|
931
|
+
const buffer: Array<() => R> = [];
|
|
932
|
+
this.buffers.push(buffer);
|
|
933
|
+
const r = fn();
|
|
934
|
+
this.buffers.pop();
|
|
935
|
+
buffer.forEach((flush) => flush());
|
|
936
|
+
return r;
|
|
937
|
+
}
|
|
938
|
+
}
|
|
939
|
+
|
|
940
|
+
/**
|
|
941
|
+
* A Relay is an event forwarder which functions as a replugabble event pipe.
|
|
942
|
+
* Once created, you can connect an input event to it and it will simply forward
|
|
943
|
+
* events from that input event through its own `event` property. The `input`
|
|
944
|
+
* can be changed at any point in time.
|
|
945
|
+
*/
|
|
946
|
+
export class Relay<T> implements IDisposable {
|
|
947
|
+
private listening = false;
|
|
948
|
+
private inputEvent: Event<T> = Event.None;
|
|
949
|
+
private inputEventListener: IDisposable = Disposable.None;
|
|
950
|
+
|
|
951
|
+
private emitter = new Emitter<T>({
|
|
952
|
+
onFirstListenerDidAdd: () => {
|
|
953
|
+
this.listening = true;
|
|
954
|
+
this.inputEventListener = this.inputEvent(this.emitter.fire, this.emitter);
|
|
955
|
+
},
|
|
956
|
+
onLastListenerRemove: () => {
|
|
957
|
+
this.listening = false;
|
|
958
|
+
this.inputEventListener.dispose();
|
|
959
|
+
},
|
|
960
|
+
});
|
|
961
|
+
|
|
962
|
+
readonly event: Event<T> = this.emitter.event;
|
|
963
|
+
|
|
964
|
+
set input(event: Event<T>) {
|
|
965
|
+
this.inputEvent = event;
|
|
966
|
+
|
|
967
|
+
if (this.listening) {
|
|
968
|
+
this.inputEventListener.dispose();
|
|
969
|
+
this.inputEventListener = event(this.emitter.fire, this.emitter);
|
|
970
|
+
}
|
|
971
|
+
}
|
|
972
|
+
|
|
973
|
+
dispose() {
|
|
974
|
+
this.inputEventListener.dispose();
|
|
975
|
+
this.emitter.dispose();
|
|
976
|
+
}
|
|
977
|
+
}
|
|
978
|
+
|
|
979
|
+
/**
|
|
980
|
+
* 同步执行的 Ready, 对 ready 的实时响应比 promise 快,多用在需要快速响应初始化回调的场景
|
|
981
|
+
*/
|
|
982
|
+
export class ReadyEvent<T = void> implements IDisposable {
|
|
983
|
+
private _isReady = false;
|
|
984
|
+
|
|
985
|
+
private _param: T | undefined = undefined;
|
|
986
|
+
|
|
987
|
+
private _emitter = new Emitter<T>();
|
|
988
|
+
|
|
989
|
+
onceReady(cb: (param: T) => any): Promise<any> {
|
|
990
|
+
if (this._isReady) {
|
|
991
|
+
try {
|
|
992
|
+
return Promise.resolve(cb(this._param!));
|
|
993
|
+
} catch (e) {
|
|
994
|
+
return Promise.reject(e);
|
|
995
|
+
}
|
|
996
|
+
} else {
|
|
997
|
+
return new Promise<any>((resolve, reject) => {
|
|
998
|
+
this._emitter.event((param) => {
|
|
999
|
+
try {
|
|
1000
|
+
resolve(cb(param));
|
|
1001
|
+
} catch (e) {
|
|
1002
|
+
reject(e);
|
|
1003
|
+
}
|
|
1004
|
+
});
|
|
1005
|
+
});
|
|
1006
|
+
}
|
|
1007
|
+
}
|
|
1008
|
+
|
|
1009
|
+
ready(param: T) {
|
|
1010
|
+
if (!this._isReady) {
|
|
1011
|
+
this._isReady = true;
|
|
1012
|
+
this._param = param;
|
|
1013
|
+
}
|
|
1014
|
+
this._emitter.fire(param);
|
|
1015
|
+
this._emitter.dispose();
|
|
1016
|
+
this._emitter = null as any;
|
|
1017
|
+
}
|
|
1018
|
+
|
|
1019
|
+
dispose() {
|
|
1020
|
+
if (this._emitter) {
|
|
1021
|
+
this._emitter.dispose();
|
|
1022
|
+
}
|
|
1023
|
+
}
|
|
1024
|
+
}
|