src/vs/base/common/observableInternal/utils/utils.ts

352 LOC · 161 covered · 191 uncovered · 41 ranges · 6771 concepts · 12 introducers · 3467 tests

File neighbourhood

The centred file is linked to every concept that introduces one of its ranges, every test that runs code from the file, and the gray connector concepts standing between those tests and the file's own introducer concepts. Undirected links join concepts to every file where they introduce source and concepts to the tests they introduce; arrows show specialization between the displayed concepts and bridge only concepts omitted from this view. Concept colors match the source ranges below; connector concepts have no source color and are shown in gray.

Focused file, its introducer and connector concepts, their introduced files, and tests that run code from the file

In the embedded map, ordinary wheel input scrolls the page; use the visible controls to zoom and drag to pan. Open the full-screen map for canvas navigation: wheel pans, Ctrl/Command plus wheel zooms, and arrow keys pan when this region is focused. On touch screens, open the full-screen map to pan or pinch. If JavaScript or WebGL is unavailable, use the related-file, concept, and source links on this page.

Graph controls are ready.

Interactive rendering requires JavaScript and WebGL. Use the related-file, concept, and source links on this page while the interactive map is unavailable.

1 > /*--------------------------------------------------------------------------------------------- devToolsLogger.ts ×24
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 { autorun } from '../reactions/autorun.js';
7 > import { IObservable, IObservableWithChange, IObserver, IReader, ITransaction } from '../base.js';
8 > import { observableValue } from '../observables/observableValue.js';
9 > import { DebugOwner } from '../debugName.js';
10 > import { DisposableStore, Event, IDisposable, toDisposable } from '../commonFacade/deps.js';
11 > import { derived, derivedOpts } from '../observables/derived.js';
12 > import { observableFromEvent } from '../observables/observableFromEvent.js';
13 > import { observableSignal } from '../observables/observableSignal.js';
14 > import { _setKeepObserved, _setRecomputeInitiallyAndOnChange } from '../observables/baseObservable.js';
15 > import { DebugLocation } from '../debugLocation.js';
16 >
17 > export function observableFromPromise<T>(promise: Promise<T>): IObservable<{ value?: T }> {
18 const observable = observableValue<{ value?: T }>('promiseValue', {});
19 promise.then((value) => {
20 observable.set({ value }, undefined);
21 });
22 return observable;
23 }
25 > export function signalFromObservable<T>(owner: DebugOwner | undefined, observable: IObservable<T>): IObservable<void> {
26 return derivedOpts({
27 owner,
28 equalsFn: () => false,
29 }, reader => {
30 observable.read(reader);
31 });
32 }
34 > /**
35 > * Creates an observable that debounces the input observable.
36 > */
37 > export function debouncedObservable<T>(observable: IObservable<T>, debounceMs: number | ((lastValue: T | undefined, newValue: T) => number), debugLocation = DebugLocation.ofCaller()): IObservable<T> {
38 let hasValue = false;
39 let lastValue: T | undefined;
40
41 let timeout: Timeout | undefined = undefined;
42
43 return observableFromEvent<T, void>(undefined, cb => {
44 const d = autorun(reader => {
45 const value = observable.read(reader);
46
47 if (!hasValue) {
48 hasValue = true;
49 lastValue = value;
50 } else {
51 if (timeout) {
52 clearTimeout(timeout);
53 }
54 const debounceDuration = typeof debounceMs === 'number' ? debounceMs : debounceMs(lastValue, value);
55 if (debounceDuration === 0) {
56 lastValue = value;
57 cb();
58 return;
59 }
60 timeout = setTimeout(() => {
61 lastValue = value;
62 cb();
63 }, debounceDuration);
64 }
65 });
66 return {
67 dispose() {
68 d.dispose();
69 hasValue = false;
70 lastValue = undefined;
71 },
72 };
73 }, () => {
74 if (hasValue) {
75 return lastValue!;
76 } else {
77 return observable.get();
78 }
79 }, debugLocation);
80 }
82 > /**
83 > * Creates an observable that throttles the input observable.
84 > * Unlike {@link debouncedObservable}, the timer starts on the first change
85 > * and is not reset by subsequent changes, preventing starvation.
86 > */
87 > export function throttledObservable<T>(observable: IObservable<T>, throttleMs: number, debugLocation = DebugLocation.ofCaller()): IObservable<T> {
88 > let hasValue = false; utils.ts ×4
89 > let lastValue: T | undefined;
90 >
91 > let timeout: Timeout | undefined = undefined;
92 >
93 > return observableFromEvent<T, void>(undefined, cb => {
94 > const d = autorun(reader => {
95 > const value = observable.read(reader);
96 >
97 > if (!hasValue) {
98 > hasValue = true;
99 > lastValue = value;
100 > } else if (!timeout) {
101 > // Only start a timer if one isn't already running utils.ts ×2
102 > timeout = setTimeout(() => {
103 > timeout = undefined; utils.ts ×1
104 > lastValue = observable.read(undefined);
105 > cb();
106 > }, throttleMs); utils.ts ×2
107 > }
108 > }); utils.ts ×4
109 > return {
110 > dispose() {
111 > d.dispose();
112 > if (timeout) {
113 > clearTimeout(timeout); autorunImpl.ts ×1
114 > timeout = undefined;
115 > }
116 > hasValue = false; utils.ts ×4
117 > lastValue = undefined;
118 > },
119 > };
120 > }, () => {
121 > if (hasValue) {
122 > return lastValue!;
123 > } else {
124 return observable.get();
125 }
126 > }, debugLocation); utils.ts ×4
127 > }
129 > /**
130 > * Creates an observable that debounces the input observable.
131 > */
132 > export function debouncedObservable2<T>(observable: IObservable<T>, debounceMs: number | ((currentValue: T | undefined, newValue: T) => number), debugLocation = DebugLocation.ofCaller()): IObservable<T> {
133 const s = observableSignal('handleTimeout');
134
135 let currentValue: T | undefined = undefined;
136 let timeout: Timeout | undefined = undefined;
137
138 const d = derivedOpts({
139 owner: undefined,
140 onLastObserverRemoved: () => {
141 currentValue = undefined;
142 }
143 }, reader => {
144 const val = observable.read(reader);
145 s.read(reader);
146
147 if (val !== currentValue) {
148 const debounceDuration = typeof debounceMs === 'number' ? debounceMs : debounceMs(currentValue, val);
149
150 if (debounceDuration === 0) {
151 currentValue = val;
152 return val;
153 }
154
155 if (timeout) {
156 clearTimeout(timeout);
157 }
158 timeout = setTimeout(() => {
159 currentValue = val;
160 s.trigger(undefined);
161 }, debounceDuration);
162 }
163
164 return currentValue!;
165 }, debugLocation);
166
167 return d;
168 }
170 > export function wasEventTriggeredRecently(event: Event<any>, timeoutMs: number, disposableStore: DisposableStore): IObservable<boolean> {
171 const observable = observableValue('triggeredRecently', false);
172
173 let timeout: Timeout | undefined = undefined;
174
175 disposableStore.add(event(() => {
176 observable.set(true, undefined);
177
178 if (timeout) {
179 clearTimeout(timeout);
180 }
181 timeout = setTimeout(() => {
182 observable.set(false, undefined);
183 }, timeoutMs);
184 }));
185
186 return observable;
187 }
189 > /**
190 > * This makes sure the observable is being observed and keeps its cache alive.
191 > */
192 > export function keepObserved<T>(observable: IObservable<T>): IDisposable {
193 > const o = new KeepAliveObserver(false, undefined); utils.ts ×1
194 > observable.addObserver(o);
195 > return toDisposable(() => {
196 > observable.removeObserver(o);
197 > });
198 > }
200 > _setKeepObserved(keepObserved);
201 >
202 > /**
203 > * This converts the given observable into an autorun.
204 > */
205 > export function recomputeInitiallyAndOnChange<T>(observable: IObservable<T>, handleValue?: (value: T) => void): IDisposable {
206 > const o = new KeepAliveObserver(true, handleValue); utils.ts ×4
207 > observable.addObserver(o);
208 > try {
209 > o.beginUpdate(observable);
210 > } finally {
211 > o.endUpdate(observable);
212 > }
213 >
214 > return toDisposable(() => {
215 > observable.removeObserver(o);
216 > });
217 > }
219 > _setRecomputeInitiallyAndOnChange(recomputeInitiallyAndOnChange);
220 >
221 > export class KeepAliveObserver implements IObserver {
222 > private _counter = 0;
223 >
224 > constructor(
225 > private readonly _forceRecompute: boolean, utils.ts ×1
226 > private readonly _handleValue: ((value: any) => void) | undefined,
227 > ) { }
229 > beginUpdate<T>(observable: IObservable<T>): void {
230 > this._counter++; utils.ts ×3
231 > }
233 > endUpdate<T>(observable: IObservable<T>): void {
234 > if (this._counter === 1 && this._forceRecompute) { utils.ts ×3
235 > if (this._handleValue) { utils.ts ×4
236 > this._handleValue(observable.get()); utils.ts ×1
237 > } else { utils.ts ×4
238 > observable.reportChanges(); utils.ts ×1
239 > }
240 > } utils.ts ×4
241 > this._counter--; utils.ts ×3
242 > }
244 > handlePossibleChange<T>(observable: IObservable<T>): void {
245 // NO OP
246 }
248 > handleChange<T, TChange>(observable: IObservableWithChange<T, TChange>, change: TChange): void {
249 > // NO OP utils.ts ×1
250 > }
252 >
253 > export function derivedObservableWithCache<T>(owner: DebugOwner, computeFn: (reader: IReader, lastValue: T | undefined) => T): IObservable<T> {
254 let lastValue: T | undefined = undefined;
255 const observable = derivedOpts({ owner, debugReferenceFn: computeFn }, reader => {
256 lastValue = computeFn(reader, lastValue);
257 return lastValue;
258 });
259 return observable;
260 }
262 > export function derivedObservableWithWritableCache<T>(owner: object, computeFn: (reader: IReader, lastValue: T | undefined) => T): IObservable<T>
263 & { clearCache(transaction: ITransaction): void; setCache(newValue: T | undefined, tx: ITransaction | undefined): void } {
264 let lastValue: T | undefined = undefined;
265 const onChange = observableSignal('derivedObservableWithWritableCache');
266 const observable = derived(owner, reader => {
267 onChange.read(reader);
268 lastValue = computeFn(reader, lastValue);
269 return lastValue;
270 });
271 return Object.assign(observable, {
272 clearCache: (tx: ITransaction) => {
273 lastValue = undefined;
274 onChange.trigger(tx);
275 },
276 setCache: (newValue: T | undefined, tx: ITransaction | undefined) => {
277 lastValue = newValue;
278 onChange.trigger(tx);
279 }
280 });
281 }
283 > /**
284 > * When the items array changes, referential equal items are not mapped again.
285 > */
286 > export function mapObservableArrayCached<TIn, TOut, TKey = TIn>(owner: DebugOwner, items: IObservable<readonly TIn[]>, map: (input: TIn, store: DisposableStore) => TOut, keySelector?: (input: TIn) => TKey): IObservable<readonly TOut[]> {
287 let m = new ArrayMap(map, keySelector);
288 const self = derivedOpts({
289 debugReferenceFn: map,
290 owner,
291 onLastObserverRemoved: () => {
292 m.dispose();
293 m = new ArrayMap(map);
294 }
295 }, (reader) => {
296 const i = items.read(reader);
297 m.setItems(i);
298 return m.getItems();
299 });
300 return self;
301 }
303 > class ArrayMap<TIn, TOut, TKey> implements IDisposable {
304 > private readonly _cache = new Map<TKey, { out: TOut; store: DisposableStore }>();
305 > private _items: TOut[] = [];
306 > constructor(
307 private readonly _map: (input: TIn, store: DisposableStore) => TOut,
308 private readonly _keySelector?: (input: TIn) => TKey,
309 ) {
310 }
312 > public dispose(): void {
313 this._cache.forEach(entry => entry.store.dispose());
314 this._cache.clear();
315 }
317 > public setItems(items: readonly TIn[]): void {
318 const newItems: TOut[] = [];
319 const itemsToRemove = new Set(this._cache.keys());
320
321 for (const item of items) {
322 const key = this._keySelector ? this._keySelector(item) : item as unknown as TKey;
323
324 let entry = this._cache.get(key);
325 if (!entry) {
326 const store = new DisposableStore();
327 const out = this._map(item, store);
328 entry = { out, store };
329 this._cache.set(key, entry);
330 } else {
331 itemsToRemove.delete(key);
332 }
333 newItems.push(entry.out);
334 }
335
336 for (const item of itemsToRemove) {
337 const entry = this._cache.get(item)!;
338 entry.store.dispose();
339 this._cache.delete(item);
340 }
341
342 this._items = newItems;
343 }
345 > public getItems(): TOut[] {
346 return this._items;
347 }
349 >
350 > export function isObservable<T>(obj: unknown): obj is IObservable<T> {
351 return !!obj && (<IObservable<T>>obj).read !== undefined && (<IObservable<T>>obj).reportChanges !== undefined;
352 }