src/vs/base/common/observableInternal/reactions/autorun.ts

294 LOC · 248 covered · 46 uncovered · 24 ranges · 6771 concepts · 11 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 { IReaderWithStore, IReader, IObservable, ISettableObservable } from '../base.js';
7 > import { IChangeTracker } from '../changeTracker.js';
8 > import { DisposableStore, IDisposable, toDisposable } from '../commonFacade/deps.js';
9 > import { DebugNameData, IDebugNameData } from '../debugName.js';
10 > import { AutorunObserver } from './autorunImpl.js';
11 > import { DebugLocation } from '../debugLocation.js';
12 > import { observableValue } from '../observables/observableValue.js';
13 > import { transaction } from '../transaction.js';
14 >
15 > /**
16 > * Runs immediately and whenever a transaction ends and an observed observable changed.
17 > * {@link fn} should start with a JS Doc using `@description` to name the autorun.
18 > */
19 > export function autorun(fn: (reader: IReaderWithStore) => void, debugLocation = DebugLocation.ofCaller()): IDisposable {
20 > return new AutorunObserver( autorun.ts ×1
21 > new DebugNameData(undefined, undefined, fn),
22 > fn,
23 > undefined,
24 > debugLocation
25 > );
26 > }
28 > /**
29 > * Runs immediately and whenever a transaction ends and an observed observable changed.
30 > * {@link fn} should start with a JS Doc using `@description` to name the autorun.
31 > */
32 > export function autorunOpts(options: IDebugNameData & {}, fn: (reader: IReaderWithStore) => void, debugLocation = DebugLocation.ofCaller()): IDisposable {
33 > return new AutorunObserver( autorun.ts ×1
34 > new DebugNameData(options.owner, options.debugName, options.debugReferenceFn ?? fn),
35 > fn,
36 > undefined,
37 > debugLocation
38 > );
39 > }
41 > /**
42 > * Runs immediately and whenever a transaction ends and an observed observable changed.
43 > * {@link fn} should start with a JS Doc using `@description` to name the autorun.
44 > *
45 > * Use `changeTracker.createChangeSummary` to create a "change summary" that can collect the changes.
46 > * Use `changeTracker.handleChange` to add a reported change to the change summary.
47 > * The run function is given the last change summary.
48 > * The change summary is discarded after the run function was called.
49 > *
50 > * @see autorun
51 > */
52 > export function autorunHandleChanges<TChangeSummary>(
53 > options: IDebugNameData & { autorunImpl.ts ×2
54 > changeTracker: IChangeTracker<TChangeSummary>;
55 > },
56 > fn: (reader: IReader, changeSummary: TChangeSummary) => void,
57 > debugLocation = DebugLocation.ofCaller()
58 > ): IDisposable {
59 > return new AutorunObserver(
60 > new DebugNameData(options.owner, options.debugName, options.debugReferenceFn ?? fn),
61 > fn,
62 > options.changeTracker,
63 > debugLocation
64 > );
65 > }
67 > /**
68 > * @see autorunHandleChanges (but with a disposable store that is cleared before the next run or on dispose)
69 > */
70 > export function autorunWithStoreHandleChanges<TChangeSummary>(
71 > options: IDebugNameData & { derivedImpl.ts ×3
72 > changeTracker: IChangeTracker<TChangeSummary>;
73 > },
74 > fn: (reader: IReader, changeSummary: TChangeSummary, store: DisposableStore) => void
75 > ): IDisposable {
76 > const store = new DisposableStore();
77 > const disposable = autorunHandleChanges(
78 > {
79 > owner: options.owner,
80 > debugName: options.debugName,
81 > debugReferenceFn: options.debugReferenceFn ?? fn,
82 > changeTracker: options.changeTracker,
83 > },
84 > (reader, changeSummary) => {
85 > store.clear();
86 > fn(reader, changeSummary, store);
87 > }
88 > );
89 > return toDisposable(() => {
90 > disposable.dispose();
91 > store.dispose();
92 > });
93 > }
95 > /**
96 > * @see autorun (but with a disposable store that is cleared before the next run or on dispose)
97 > *
98 > * @deprecated Use `autorun(reader => { reader.store.add(...) })` instead!
99 > */
100 > export function autorunWithStore(fn: (reader: IReader, store: DisposableStore) => void): IDisposable {
101 const store = new DisposableStore();
102 const disposable = autorunOpts(
103 {
104 owner: undefined,
105 debugName: undefined,
106 debugReferenceFn: fn,
107 },
108 reader => {
109 store.clear();
110 fn(reader, store);
111 }
112 );
113 return toDisposable(() => {
114 disposable.dispose();
115 store.dispose();
116 });
117 }
119 > export function autorunDelta<T>(
120 > observable: IObservable<T>, mcpServer.ts ×23
121 > handler: (args: { lastValue: T | undefined; newValue: T }) => void
122 > ): IDisposable {
123 > let _lastValue: T | undefined;
124 > return autorunOpts({ debugReferenceFn: handler }, (reader) => {
125 > const newValue = observable.read(reader);
126 > const lastValue = _lastValue;
127 > _lastValue = newValue;
128 > handler({ lastValue, newValue });
129 > });
130 > }
132 > export function autorunIterableDelta<T>(
133 getValue: (reader: IReader) => Iterable<T>,
134 handler: (args: { addedValues: T[]; removedValues: T[] }) => void,
135 getUniqueIdentifier: (value: T) => unknown = v => v
136 ) {
137 const lastValues = new Map<unknown, T>();
138 return autorunOpts({ debugReferenceFn: getValue }, (reader) => {
139 const newValues = new Map();
140 const removedValues = new Map(lastValues);
141 for (const value of getValue(reader)) {
142 const id = getUniqueIdentifier(value);
143 if (lastValues.has(id)) {
144 removedValues.delete(id);
145 } else {
146 newValues.set(id, value);
147 lastValues.set(id, value);
148 }
149 }
150 for (const id of removedValues.keys()) {
151 lastValues.delete(id);
152 }
153
154 if (newValues.size || removedValues.size) {
155 handler({ addedValues: [...newValues.values()], removedValues: [...removedValues.values()] });
156 }
157 });
158 }
160 > /**
161 > * For each key-stable item in {@link items}, runs {@link setup} once when the
162 > * key is first observed and disposes the per-key {@link DisposableStore} when
163 > * the key is no longer present in the array (or when the returned disposable
164 > * is disposed).
165 > *
166 > * The {@link IObservable} handed to {@link setup} fires whenever the array
167 > * still contains an item with the same key but the item value itself has
168 > * changed (e.g. because the upstream state is immutable and produced a new
169 > * object with the same id). All per-key value updates triggered by a single
170 > * change to {@link items} are batched into one transaction, so dependent
171 > * autoruns observe a consistent snapshot.
172 > *
173 > * Per-key state should be stored in closures or in disposables registered
174 > * against the per-key {@link DisposableStore}. {@link setup} should not call
175 > * `.read()` on the outer {@link items} observable from its body (use the
176 > * provided per-key value observable, or create inner autoruns).
177 > */
178 > export function autorunPerKeyedItem<TIn, TKey>(
179 > items: IObservable<readonly TIn[]>, autorun.ts ×2
180 > keyFn: (input: TIn) => TKey,
181 > setup: (key: TKey, value: IObservable<TIn>, store: DisposableStore) => void,
182 > debugLocation = DebugLocation.ofCaller()
183 > ): IDisposable {
184 > interface ICell {
185 > readonly value: ISettableObservable<TIn>;
186 > readonly store: DisposableStore;
187 > }
188 > const cells = new Map<TKey, ICell>();
189 > const ar = autorunOpts({ debugReferenceFn: setup }, reader => {
190 > const arr = items.read(reader);
191 > const seen = new Set<TKey>();
192 > const additions: { key: TKey; cell: ICell }[] = [];
193 > transaction(tx => {
194 > for (const item of arr) {
195 > const key = keyFn(item);
196 > seen.add(key);
197 > const existing = cells.get(key);
198 > if (existing) {
199 > existing.value.set(item, tx);
200 > } else {
201 > const store = new DisposableStore();
202 > const value = observableValue<TIn>('keyedItem', item);
203 > const cell: ICell = { value, store };
204 > cells.set(key, cell);
205 > additions.push({ key, cell });
206 > }
207 > }
208 > for (const [k, cell] of cells) {
209 > if (!seen.has(k)) {
210 > cell.store.dispose(); autorun.ts ×1
211 > cells.delete(k);
212 > }
213 > } autorun.ts ×2
214 > });
215 > // Setup runs after the transaction so per-key autoruns observe the
216 > // final cell values on their first read.
217 > for (const { key, cell } of additions) {
218 > setup(key, cell.value, cell.store);
219 > }
220 > }, debugLocation);
221 > return toDisposable(() => {
222 > ar.dispose();
223 > for (const cell of cells.values()) {
224 > cell.store.dispose();
225 > }
226 > cells.clear();
227 > });
228 > }
230 > export interface IReaderWithDispose extends IReaderWithStore, IDisposable { }
231 >
232 > /**
233 > * An autorun with a `dispose()` method on its `reader` which cancels the autorun.
234 > * It it safe to call `dispose()` synchronously.
235 > * @deprecated Use autorunSelfDisposable2
236 > */
237 > export function autorunSelfDisposable(fn: (reader: IReaderWithDispose) => void, debugLocation = DebugLocation.ofCaller()): IDisposable {
238 > let ar: IDisposable | undefined; autorun.ts ×1
239 > let disposed = false;
240 >
241 > // eslint-disable-next-line prefer-const
242 > ar = autorun(reader => {
243 > fn({
244 > delayedStore: reader.delayedStore,
245 > store: reader.store,
246 > readObservable: reader.readObservable.bind(reader),
247 > dispose: () => {
248 > ar?.dispose();
249 > disposed = true;
250 > }
251 > });
252 > }, debugLocation);
253 >
254 > if (disposed) {
255 > ar.dispose();
256 > }
257 >
258 > return ar;
259 > }
261 >
262 > /**
263 > * An autorun with a `dispose()` method on its `reader` which cancels the autorun.
264 > * It it safe to call `dispose()` synchronously.
265 > * TODO@hediet/copilot: rename to delete autorunSelfDisposable, and rename autorunSelfDisposable2 to autorunSelfDisposable.
266 > */
267 > export function registerAutorunSelfDisposable(store: DisposableStore, fn: (reader: IReaderWithDispose) => void, debugLocation = DebugLocation.ofCaller()): void {
268 > let ar: IDisposable | undefined; chatModel.ts ×6
269 > let disposeSync = false;
270 >
271 > // eslint-disable-next-line prefer-const
272 > ar = autorun(reader => {
273 > fn({
274 > delayedStore: reader.delayedStore,
275 > store: reader.store,
276 > readObservable: reader.readObservable.bind(reader),
277 > dispose: () => {
278 > if (!ar) { autorun.ts ×2
279 // dispose on first run, ar is not initialized yet.
280 disposeSync = true;
281 > } else { autorun.ts ×2
282 > // dispose on reaction, ar is already registered.
283 > store.delete(ar);
284 > }
285 > }
286 > }); chatModel.ts ×6
287 > }, debugLocation);
288 >
289 > if (disposeSync) {
290 ar.dispose();
291 > } else { chatModel.ts ×6
292 > store.add(ar);
293 > }
294 > }