@theia/core 1.64.0-next.28 → 1.64.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.
Files changed (95) hide show
  1. package/README.md +6 -6
  2. package/i18n/nls.cs.json +59 -9
  3. package/i18n/nls.de.json +59 -9
  4. package/i18n/nls.es.json +59 -9
  5. package/i18n/nls.fr.json +59 -9
  6. package/i18n/nls.hu.json +59 -9
  7. package/i18n/nls.it.json +59 -9
  8. package/i18n/nls.ja.json +59 -9
  9. package/i18n/nls.json +63 -13
  10. package/i18n/nls.ko.json +59 -9
  11. package/i18n/nls.pl.json +59 -9
  12. package/i18n/nls.pt-br.json +59 -9
  13. package/i18n/nls.ru.json +59 -9
  14. package/i18n/nls.tr.json +59 -9
  15. package/i18n/nls.zh-cn.json +59 -9
  16. package/i18n/nls.zh-tw.json +59 -9
  17. package/lib/browser/catalog.json +149 -15
  18. package/lib/browser/context-key-service.d.ts +4 -0
  19. package/lib/browser/context-key-service.d.ts.map +1 -1
  20. package/lib/browser/context-key-service.js.map +1 -1
  21. package/lib/browser/frontend-application-module.js +1 -1
  22. package/lib/browser/frontend-application-module.js.map +1 -1
  23. package/lib/browser/menu/browser-menu-plugin.d.ts +1 -1
  24. package/lib/browser/menu/browser-menu-plugin.d.ts.map +1 -1
  25. package/lib/browser/menu/browser-menu-plugin.js +2 -2
  26. package/lib/browser/menu/browser-menu-plugin.js.map +1 -1
  27. package/lib/browser/preload/i18n-preload-contribution.js +1 -1
  28. package/lib/browser/preload/i18n-preload-contribution.js.map +1 -1
  29. package/lib/browser/shell/application-shell.d.ts +1 -1
  30. package/lib/browser/shell/application-shell.d.ts.map +1 -1
  31. package/lib/browser/shell/application-shell.js.map +1 -1
  32. package/lib/common/array-utils.d.ts +4 -0
  33. package/lib/common/array-utils.d.ts.map +1 -1
  34. package/lib/common/array-utils.js +47 -0
  35. package/lib/common/array-utils.js.map +1 -1
  36. package/lib/common/diff.d.ts +33 -0
  37. package/lib/common/diff.d.ts.map +1 -0
  38. package/lib/common/diff.js +20 -0
  39. package/lib/common/diff.js.map +1 -0
  40. package/lib/common/message-rpc/msg-pack-extension-manager.js +2 -2
  41. package/lib/common/message-rpc/msg-pack-extension-manager.js.map +1 -1
  42. package/lib/common/observable/autorun.d.ts +81 -0
  43. package/lib/common/observable/autorun.d.ts.map +1 -0
  44. package/lib/common/observable/autorun.js +194 -0
  45. package/lib/common/observable/autorun.js.map +1 -0
  46. package/lib/common/observable/derived-observable.d.ts +71 -0
  47. package/lib/common/observable/derived-observable.d.ts.map +1 -0
  48. package/lib/common/observable/derived-observable.js +258 -0
  49. package/lib/common/observable/derived-observable.js.map +1 -0
  50. package/lib/common/observable/index.d.ts +8 -0
  51. package/lib/common/observable/index.d.ts.map +1 -0
  52. package/lib/common/observable/index.js +26 -0
  53. package/lib/common/observable/index.js.map +1 -0
  54. package/lib/common/observable/observable-base.d.ts +181 -0
  55. package/lib/common/observable/observable-base.d.ts.map +1 -0
  56. package/lib/common/observable/observable-base.js +183 -0
  57. package/lib/common/observable/observable-base.js.map +1 -0
  58. package/lib/common/observable/observable-from-event.d.ts +41 -0
  59. package/lib/common/observable/observable-from-event.d.ts.map +1 -0
  60. package/lib/common/observable/observable-from-event.js +115 -0
  61. package/lib/common/observable/observable-from-event.js.map +1 -0
  62. package/lib/common/observable/observable-signal.d.ts +9 -0
  63. package/lib/common/observable/observable-signal.d.ts.map +1 -0
  64. package/lib/common/observable/observable-signal.js +45 -0
  65. package/lib/common/observable/observable-signal.js.map +1 -0
  66. package/lib/common/observable/observable-utils.d.ts +26 -0
  67. package/lib/common/observable/observable-utils.d.ts.map +1 -0
  68. package/lib/common/observable/observable-utils.js +98 -0
  69. package/lib/common/observable/observable-utils.js.map +1 -0
  70. package/lib/common/observable/observable.spec.d.ts +2 -0
  71. package/lib/common/observable/observable.spec.d.ts.map +1 -0
  72. package/lib/common/observable/observable.spec.js +301 -0
  73. package/lib/common/observable/observable.spec.js.map +1 -0
  74. package/lib/common/observable/settable-observable.d.ts +16 -0
  75. package/lib/common/observable/settable-observable.d.ts.map +1 -0
  76. package/lib/common/observable/settable-observable.js +58 -0
  77. package/lib/common/observable/settable-observable.js.map +1 -0
  78. package/package.json +7 -8
  79. package/src/browser/context-key-service.ts +5 -0
  80. package/src/browser/frontend-application-module.ts +1 -1
  81. package/src/browser/menu/browser-menu-plugin.ts +3 -3
  82. package/src/browser/preload/i18n-preload-contribution.ts +1 -1
  83. package/src/browser/shell/application-shell.ts +1 -1
  84. package/src/common/array-utils.ts +53 -0
  85. package/src/common/diff.ts +56 -0
  86. package/src/common/message-rpc/msg-pack-extension-manager.ts +2 -2
  87. package/src/common/observable/autorun.ts +246 -0
  88. package/src/common/observable/derived-observable.ts +321 -0
  89. package/src/common/observable/index.ts +23 -0
  90. package/src/common/observable/observable-base.ts +324 -0
  91. package/src/common/observable/observable-from-event.ts +148 -0
  92. package/src/common/observable/observable-signal.ts +48 -0
  93. package/src/common/observable/observable-utils.ts +119 -0
  94. package/src/common/observable/observable.spec.ts +342 -0
  95. package/src/common/observable/settable-observable.ts +73 -0
@@ -0,0 +1,321 @@
1
+ // *****************************************************************************
2
+ // Copyright (C) 2025 1C-Soft LLC and others.
3
+ //
4
+ // This program and the accompanying materials are made available under the
5
+ // terms of the Eclipse Public License v. 2.0 which is available at
6
+ // http://www.eclipse.org/legal/epl-2.0.
7
+ //
8
+ // This Source Code may also be made available under the following Secondary
9
+ // Licenses when the conditions for such availability set forth in the Eclipse
10
+ // Public License v. 2.0 are satisfied: GNU General Public License, version 2
11
+ // with the GNU Classpath Exception which is available at
12
+ // https://www.gnu.org/software/classpath/license.html.
13
+ //
14
+ // SPDX-License-Identifier: EPL-2.0 OR GPL-2.0-only WITH Classpath-exception-2.0
15
+ // *****************************************************************************
16
+ /*---------------------------------------------------------------------------------------------
17
+ * Copyright (c) Microsoft Corporation. All rights reserved.
18
+ * Licensed under the MIT License. See License.txt in the project root for license information.
19
+ *--------------------------------------------------------------------------------------------*/
20
+ // copied and modified from https://github.com/microsoft/vscode/blob/1.96.3/src/vs/base/common/observableInternal/derived.ts
21
+
22
+ import { BaseObservable, Observable } from './observable-base';
23
+
24
+ /**
25
+ * An observable that is derived from other observables.
26
+ * Its value is only (re-)computed when absolutely needed.
27
+ */
28
+ export class DerivedObservable<T, TChangeSummary = unknown> extends BaseObservable<T> {
29
+
30
+ protected state = DerivedObservable.State.Initial;
31
+ protected value?: T;
32
+ protected updateCount = 0;
33
+ protected isComputing = false;
34
+ protected dependencies = new Set<Observable<unknown>>();
35
+ protected dependenciesToBeRemoved?: Set<Observable<unknown>>;
36
+ protected removedObserversToCallEndUpdateOn?: Set<Observable.Observer>;
37
+ protected readonly dependencyObserver = this.createDependencyObserver();
38
+ protected readonly isEqual: (a: T, b: T) => boolean;
39
+ protected readonly createChangeSummary?: () => TChangeSummary;
40
+ protected readonly willHandleChange?: <U, UChange>(context: Observable.ChangeContext<U, UChange>, changeSummary: TChangeSummary | undefined) => boolean;
41
+ protected changeSummary?: TChangeSummary;
42
+
43
+ constructor(
44
+ protected readonly compute: (args: DerivedObservable.Args<TChangeSummary>) => T,
45
+ options?: DerivedObservable.Options<T, TChangeSummary>
46
+ ) {
47
+ super();
48
+ this.isEqual = options?.isEqual ?? ((a, b) => a === b);
49
+ this.createChangeSummary = options?.createChangeSummary;
50
+ this.willHandleChange = options?.willHandleChange;
51
+ this.changeSummary = this.createChangeSummary?.();
52
+ }
53
+
54
+ protected override onLastObserverRemoved(): void {
55
+ // We are not tracking changes anymore, thus we have to invalidate the cached value.
56
+ this.state = DerivedObservable.State.Initial;
57
+ this.value = undefined;
58
+ for (const dependency of this.dependencies) {
59
+ dependency.removeObserver(this.dependencyObserver);
60
+ }
61
+ this.dependencies.clear();
62
+ }
63
+
64
+ protected override getValue(): T {
65
+ if (this.isComputing) {
66
+ throw new Error('Cyclic dependencies are not allowed');
67
+ }
68
+
69
+ if (this.observers.size === 0) {
70
+ // Without observers, we don't know when to clean up stuff.
71
+ // Thus, we don't cache anything to prevent memory leaks.
72
+ let result;
73
+ try {
74
+ this.isComputing = true;
75
+ result = Observable.Accessor.runWithAccessor(() => this.compute({ changeSummary: this.createChangeSummary?.() }), dependency => this.watchDependency(dependency));
76
+ } finally {
77
+ this.isComputing = false;
78
+ // Clear new dependencies.
79
+ this.onLastObserverRemoved();
80
+ }
81
+ return result;
82
+ } else {
83
+ do {
84
+ if (this.state === DerivedObservable.State.DependenciesMightHaveChanged) {
85
+ // Need to ask our depedencies if at least one of them has actually changed.
86
+ for (const dependency of this.dependencies) {
87
+ dependency.update(); // might call handleChange indirectly, which could make us stale
88
+ if (this.state as DerivedObservable.State === DerivedObservable.State.Stale) {
89
+ // The other dependencies will refresh on demand, so early break
90
+ break;
91
+ }
92
+ }
93
+ }
94
+
95
+ // If we are still not stale, we can assume to be up to date again.
96
+ if (this.state === DerivedObservable.State.DependenciesMightHaveChanged) {
97
+ this.state = DerivedObservable.State.UpToDate;
98
+ }
99
+
100
+ if (this.state !== DerivedObservable.State.UpToDate) {
101
+ this.recompute();
102
+ }
103
+ // In case recomputation changed one of our dependencies, we need to recompute again.
104
+ } while (this.state !== DerivedObservable.State.UpToDate);
105
+ return this.value!;
106
+ }
107
+ }
108
+
109
+ protected recompute(): void {
110
+ this.dependenciesToBeRemoved = this.dependencies;
111
+ this.dependencies = new Set<Observable<unknown>>();
112
+
113
+ const hadValue = this.state !== DerivedObservable.State.Initial;
114
+ const oldValue = this.value;
115
+ this.state = DerivedObservable.State.UpToDate;
116
+
117
+ try {
118
+ const { changeSummary } = this;
119
+ this.changeSummary = this.createChangeSummary?.();
120
+ this.isComputing = true;
121
+ this.value = Observable.Accessor.runWithAccessor(() => this.compute({ changeSummary }), dependency => this.watchDependency(dependency));
122
+ } finally {
123
+ this.isComputing = false;
124
+ // We don't want our watched dependencies to think that they are no longer observed, even temporarily.
125
+ // Thus, we only unsubscribe from dependencies that are definitely not watched anymore.
126
+ for (const dependency of this.dependenciesToBeRemoved) {
127
+ dependency.removeObserver(this.dependencyObserver);
128
+ }
129
+ this.dependenciesToBeRemoved = undefined;
130
+ }
131
+
132
+ const didChange = hadValue && !this.isEqual(oldValue!, this.value);
133
+
134
+ if (didChange) {
135
+ for (const observer of this.observers) {
136
+ observer.handleChange(this);
137
+ }
138
+ }
139
+ }
140
+
141
+ protected watchDependency<U>(dependency: Observable<U>): U {
142
+ if (!this.isComputing) {
143
+ throw new Error('The accessor may only be called while the compute function is running');
144
+ }
145
+
146
+ // Subscribe before getting the value to enable caching.
147
+ dependency.addObserver(this.dependencyObserver);
148
+ // This might call handleChange indirectly, which could invalidate us.
149
+ const value = dependency.getUntracked();
150
+ // Which is why we only add the observable to the dependencies now.
151
+ this.dependencies.add(dependency);
152
+ this.dependenciesToBeRemoved?.delete(dependency);
153
+ return value;
154
+ }
155
+
156
+ protected createDependencyObserver(): Observable.Observer {
157
+ let inBeginUpdate = false;
158
+ return {
159
+ beginUpdate: () => {
160
+ if (inBeginUpdate) {
161
+ throw new Error('Cyclic dependencies are not allowed');
162
+ }
163
+
164
+ inBeginUpdate = true;
165
+ try {
166
+ this.updateCount++;
167
+ const propagateBeginUpdate = this.updateCount === 1;
168
+ if (this.state === DerivedObservable.State.UpToDate) {
169
+ this.state = DerivedObservable.State.DependenciesMightHaveChanged;
170
+ // If we propagate begin update, that will already signal a possible change.
171
+ if (!propagateBeginUpdate) {
172
+ for (const observer of this.observers) {
173
+ observer.handlePossibleChange(this);
174
+ }
175
+ }
176
+ }
177
+ if (propagateBeginUpdate) {
178
+ for (const observer of this.observers) {
179
+ observer.beginUpdate(this); // signals a possible change
180
+ }
181
+ }
182
+ } finally {
183
+ inBeginUpdate = false;
184
+ }
185
+ },
186
+
187
+ endUpdate: () => {
188
+ this.updateCount--;
189
+ if (this.updateCount === 0) {
190
+ // Calls to endUpdate can potentially change the observers list.
191
+ let observers = [...this.observers];
192
+ for (const observer of observers) {
193
+ observer.endUpdate(this);
194
+ }
195
+ if (this.removedObserversToCallEndUpdateOn) {
196
+ observers = [...this.removedObserversToCallEndUpdateOn];
197
+ this.removedObserversToCallEndUpdateOn = undefined;
198
+ for (const observer of observers) {
199
+ observer.endUpdate(this);
200
+ }
201
+ }
202
+ }
203
+ if (this.updateCount < 0) {
204
+ throw new Error('Unexpected update count: ' + this.updateCount);
205
+ }
206
+ },
207
+
208
+ handlePossibleChange: <U>(observable: Observable<U>) => {
209
+ // In all other states, observers already know that we might have changed.
210
+ if (this.state === DerivedObservable.State.UpToDate && this.dependencies.has(observable) && !this.dependenciesToBeRemoved?.has(observable)) {
211
+ this.state = DerivedObservable.State.DependenciesMightHaveChanged;
212
+ for (const observer of this.observers) {
213
+ observer.handlePossibleChange(this);
214
+ }
215
+ }
216
+ },
217
+
218
+ handleChange: <U, UChange>(observable: Observable<U, UChange>, change: UChange) => {
219
+ if (this.dependencies.has(observable) && !this.dependenciesToBeRemoved?.has(observable)) {
220
+ let shouldReact = true;
221
+ if (this.willHandleChange) {
222
+ try {
223
+ shouldReact = this.willHandleChange({
224
+ observable,
225
+ change,
226
+ isChangeOf: <V, VChange>(o: Observable<V, VChange>): this is { change: VChange } => o as unknown === observable
227
+ }, this.changeSummary);
228
+ } catch (e) {
229
+ console.error(e);
230
+ }
231
+ }
232
+ const wasUpToDate = this.state === DerivedObservable.State.UpToDate;
233
+ if (shouldReact && (this.state === DerivedObservable.State.DependenciesMightHaveChanged || wasUpToDate)) {
234
+ this.state = DerivedObservable.State.Stale;
235
+ if (wasUpToDate) {
236
+ for (const observer of this.observers) {
237
+ observer.handlePossibleChange(this);
238
+ }
239
+ }
240
+ }
241
+ }
242
+ }
243
+ };
244
+ }
245
+
246
+ override addObserver(observer: Observable.Observer): void {
247
+ const shouldCallBeginUpdate = !this.observers.has(observer) && this.updateCount > 0;
248
+ super.addObserver(observer);
249
+ if (shouldCallBeginUpdate) {
250
+ if (this.removedObserversToCallEndUpdateOn?.has(observer)) {
251
+ this.removedObserversToCallEndUpdateOn.delete(observer);
252
+ } else {
253
+ observer.beginUpdate(this);
254
+ }
255
+ }
256
+ }
257
+
258
+ override removeObserver(observer: Observable.Observer): void {
259
+ if (this.observers.has(observer) && this.updateCount > 0) {
260
+ if (!this.removedObserversToCallEndUpdateOn) {
261
+ this.removedObserversToCallEndUpdateOn = new Set();
262
+ }
263
+ this.removedObserversToCallEndUpdateOn.add(observer);
264
+ }
265
+ super.removeObserver(observer);
266
+ }
267
+ }
268
+
269
+ export namespace DerivedObservable {
270
+
271
+ export function create<T, TChangeSummary>(compute: (args: Args<TChangeSummary>) => T, options?: Options<T, TChangeSummary>): Observable<T, void> {
272
+ return new DerivedObservable(compute, options);
273
+ }
274
+
275
+ export interface Args<TChangeSummary> {
276
+ /**
277
+ * The change summary with the changes collected from the start of the previous run of the compute function until the start of this run.
278
+ *
279
+ * The change summary is created by {@link Options.createChangeSummary} and
280
+ * the changes are collected by {@link Options.willHandleChange}.
281
+ */
282
+ readonly changeSummary: TChangeSummary | undefined;
283
+ }
284
+
285
+ export interface Options<T, TChangeSummary> {
286
+ isEqual?: (a: T, b: T) => boolean;
287
+
288
+ /**
289
+ * Creates a change summary that can collect the changes reported by the observed dependencies to {@link willHandleChange}.
290
+ */
291
+ createChangeSummary?: () => TChangeSummary;
292
+
293
+ /**
294
+ * Handles a change reported by an observed dependency, e.g. by adding it to the {@link changeSummary}.
295
+ * Returns `true` if the reported change should be reacted to, and `false` if it should be ignored.
296
+ */
297
+ willHandleChange?: <U, UChange>(context: Observable.ChangeContext<U, UChange>, changeSummary: TChangeSummary | undefined) => boolean;
298
+ }
299
+
300
+ export const enum State {
301
+ /**
302
+ * Initial state. No cached value.
303
+ */
304
+ Initial,
305
+
306
+ /**
307
+ * Dependencies might have changed. Need to check if at least one dependency has actually changed.
308
+ */
309
+ DependenciesMightHaveChanged,
310
+
311
+ /**
312
+ * A dependency has changed. Need to recompute the cached value.
313
+ */
314
+ Stale,
315
+
316
+ /**
317
+ * The cached value is up to date.
318
+ */
319
+ UpToDate
320
+ }
321
+ }
@@ -0,0 +1,23 @@
1
+ // *****************************************************************************
2
+ // Copyright (C) 2025 1C-Soft LLC and others.
3
+ //
4
+ // This program and the accompanying materials are made available under the
5
+ // terms of the Eclipse Public License v. 2.0 which is available at
6
+ // http://www.eclipse.org/legal/epl-2.0.
7
+ //
8
+ // This Source Code may also be made available under the following Secondary
9
+ // Licenses when the conditions for such availability set forth in the Eclipse
10
+ // Public License v. 2.0 are satisfied: GNU General Public License, version 2
11
+ // with the GNU Classpath Exception which is available at
12
+ // https://www.gnu.org/software/classpath/license.html.
13
+ //
14
+ // SPDX-License-Identifier: EPL-2.0 OR GPL-2.0-only WITH Classpath-exception-2.0
15
+ // *****************************************************************************
16
+
17
+ export * from './observable-base';
18
+ export * from './settable-observable';
19
+ export * from './derived-observable';
20
+ export * from './observable-signal';
21
+ export * from './observable-from-event';
22
+ export * from './autorun';
23
+ export * from './observable-utils';
@@ -0,0 +1,324 @@
1
+ // *****************************************************************************
2
+ // Copyright (C) 2025 1C-Soft LLC and others.
3
+ //
4
+ // This program and the accompanying materials are made available under the
5
+ // terms of the Eclipse Public License v. 2.0 which is available at
6
+ // http://www.eclipse.org/legal/epl-2.0.
7
+ //
8
+ // This Source Code may also be made available under the following Secondary
9
+ // Licenses when the conditions for such availability set forth in the Eclipse
10
+ // Public License v. 2.0 are satisfied: GNU General Public License, version 2
11
+ // with the GNU Classpath Exception which is available at
12
+ // https://www.gnu.org/software/classpath/license.html.
13
+ //
14
+ // SPDX-License-Identifier: EPL-2.0 OR GPL-2.0-only WITH Classpath-exception-2.0
15
+ // *****************************************************************************
16
+ /*---------------------------------------------------------------------------------------------
17
+ * Copyright (c) Microsoft Corporation. All rights reserved.
18
+ * Licensed under the MIT License. See License.txt in the project root for license information.
19
+ *--------------------------------------------------------------------------------------------*/
20
+ // copied and modified from https://github.com/microsoft/vscode/blob/1.96.3/src/vs/base/common/observableInternal/base.ts
21
+
22
+ import { Disposable } from '../disposable';
23
+
24
+ /**
25
+ * Represents an observable value.
26
+ *
27
+ * @template T The type of values the observable can hold.
28
+ * @template TChange Describes how or why the observable changed.
29
+ * While observers might miss intermediate values of an observable,
30
+ * they will receive all change notifications as long as they are subscribed.
31
+ */
32
+ export interface Observable<T, TChange = unknown> {
33
+ /**
34
+ * - If an accessor is given, has the same effect as calling `accessor(this)`.
35
+ * - If no accessor is given, uses the {@link Observable.Accessor.getCurrent current accessor} if it is set.
36
+ * - If no accessor is given and there is no current accessor, has the same effect as calling {@link getUntracked}.
37
+ */
38
+ get(accessor?: Observable.Accessor): T;
39
+
40
+ /**
41
+ * This method is called by the framework and should not typically be called by ordinary clients.
42
+ *
43
+ * Returns the current value of this observable without tracking the access.
44
+ *
45
+ * Calls {@link Observable.Observer.handleChange} if the observable notices that its value has changed.
46
+ */
47
+ getUntracked(): T;
48
+
49
+ /**
50
+ * This method is called by the framework and should not typically be called by ordinary clients.
51
+ *
52
+ * Forces the observable to detect and report a change if any.
53
+ *
54
+ * Has the same effect as calling {@link getUntracked}, but does not force the observable
55
+ * to actually construct the value, e.g. if change deltas are used.
56
+ *
57
+ * Calls {@link Observable.Observer.handleChange} if the observable notices that its value has changed.
58
+ */
59
+ update(): void;
60
+
61
+ /**
62
+ * This method is called by the framework and should not typically be called by ordinary clients.
63
+ *
64
+ * Adds the observer to the set of subscribed observers.
65
+ * This method is idempotent.
66
+ */
67
+ addObserver(observer: Observable.Observer): void;
68
+
69
+ /**
70
+ * This method is called by the framework and should not typically be called by ordinary clients.
71
+ *
72
+ * Removes the observer from the set of subscribed observers.
73
+ * This method is idempotent.
74
+ */
75
+ removeObserver(observer: Observable.Observer): void;
76
+
77
+ /**
78
+ * This property captures the type of change information. It should not be used at runtime.
79
+ */
80
+ readonly TChange?: TChange;
81
+ }
82
+
83
+ export namespace Observable {
84
+
85
+ export type Accessor = <T>(observable: Observable<T>) => T;
86
+
87
+ export namespace Accessor {
88
+ let current: Accessor | undefined;
89
+
90
+ export function getCurrent(): Accessor | undefined {
91
+ return current;
92
+ }
93
+
94
+ export function runWithAccessor<T>(run: () => T, accessor: Accessor | undefined): T {
95
+ const previous = current;
96
+ current = accessor;
97
+ try {
98
+ return run();
99
+ } finally {
100
+ current = previous;
101
+ }
102
+ }
103
+ }
104
+
105
+ /**
106
+ * Runs the given function within an invocation context where calling {@link Observable.get} without passing the `accessor` argument
107
+ * will have the same effect as calling {@link Observable.getUntracked}.
108
+ */
109
+ export function noAutoTracking<T>(run: (accessor: Accessor | undefined) => T): T {
110
+ const accessor = Accessor.getCurrent();
111
+ return Accessor.runWithAccessor(() => run(accessor), undefined);
112
+ }
113
+
114
+ /**
115
+ * Represents an observer that can be subscribed to an observable.
116
+ *
117
+ * If an observer is subscribed to an observable and that observable didn't signal
118
+ * a change through one of the observer methods, the observer can assume that the
119
+ * observable didn't change.
120
+ *
121
+ * If an observable reported a possible change, {@link Observable.update} forces
122
+ * the observable to report the actual change if any.
123
+ */
124
+ export interface Observer {
125
+ /**
126
+ * Signals that the given observable might have changed and an update potentially modifying that observable has started.
127
+ *
128
+ * The method {@link Observable.update} can be used to force the observable to report the change.
129
+ *
130
+ * Every call of this method must eventually be accompanied with the corresponding {@link endUpdate} call.
131
+ */
132
+ beginUpdate<T>(observable: Observable<T>): void;
133
+
134
+ /**
135
+ * Signals that an update that potentially modified the given observable has ended.
136
+ * This is a good place to react to changes.
137
+ */
138
+ endUpdate<T>(observable: Observable<T>): void;
139
+
140
+ /**
141
+ * Signals that the given observable might have changed.
142
+ *
143
+ * The method {@link Observable.update} can be used to force the observable to report the change.
144
+ *
145
+ * This method should not attempt to get the value of the given observable or any other observables.
146
+ */
147
+ handlePossibleChange<T>(observable: Observable<T>): void;
148
+
149
+ /**
150
+ * Signals that the given observable has changed.
151
+ *
152
+ * This method should not attempt to get the value of the given observable or any other observables.
153
+ */
154
+ handleChange<T, TChange>(observable: Observable<T, TChange>, change?: TChange): void;
155
+ }
156
+
157
+ export interface ChangeContext<T, TChange> {
158
+
159
+ readonly observable: Observable<T, TChange>;
160
+ readonly change: TChange;
161
+
162
+ isChangeOf<U, UChange>(observable: Observable<U, UChange>): this is { change: UChange };
163
+ }
164
+
165
+ /**
166
+ * A settable observable.
167
+ */
168
+ export interface Settable<T, TChange> extends Observable<T, TChange> {
169
+ /**
170
+ * Sets the value of this observable.
171
+ * - If no update scope is given, uses the {@link Observable.UpdateScope.getCurrent current update scope} if it is set.
172
+ * - If no update scope is given and there is no current update scope, a local update scope will be created, used, and disposed.
173
+ */
174
+ set(value: T, change?: TChange, updateScope?: UpdateScope): void;
175
+ }
176
+
177
+ /**
178
+ * An observable signal can be triggered to invalidate observers.
179
+ * Signals don't have a value - when they are triggered they indicate a change.
180
+ */
181
+ export interface Signal<TChange> extends Observable<void, TChange> {
182
+ /**
183
+ * Triggers this observable signal.
184
+ * - If no update scope is given, uses the {@link Observable.UpdateScope.getCurrent current update scope} if it is set.
185
+ * - If no update scope is given and there is no current update scope, a local update scope will be created, used, and disposed.
186
+ */
187
+ trigger(change?: TChange, updateScope?: UpdateScope): void;
188
+ }
189
+
190
+ /**
191
+ * Represents an update scope in which multiple observables can be updated in a batch.
192
+ */
193
+ export class UpdateScope implements Disposable {
194
+
195
+ private list: { observer: Observer; observable: Observable<unknown> }[] | undefined = [];
196
+
197
+ /**
198
+ * This method is called by the framework and should not typically be called by ordinary clients.
199
+ *
200
+ * Calls {@link Observer.beginUpdate} immediately, and {@link Observer.endUpdate} when this update scope gets disposed.
201
+ *
202
+ * Note that this method may be called while the update scope is being disposed.
203
+ */
204
+ push<T>(observer: Observer, observable: Observable<T>): void {
205
+ if (this.list) {
206
+ this.list.push({ observer, observable });
207
+ observer.beginUpdate(observable);
208
+ } else {
209
+ throw new Error('Update scope has been disposed');
210
+ }
211
+ }
212
+
213
+ dispose(): void {
214
+ const list = this.list;
215
+ if (list) {
216
+ // Note: `this.push` may be called from `observer.endUpdate` directly or indirectly. This code supports it.
217
+ UpdateScope.runWithUpdateScope(() => {
218
+ for (let i = 0; i < list.length; i++) {
219
+ const { observer, observable } = list[i];
220
+ observer.endUpdate(observable);
221
+ }
222
+ }, this);
223
+ }
224
+ this.list = undefined;
225
+ }
226
+ }
227
+
228
+ export namespace UpdateScope {
229
+ let current: UpdateScope | undefined;
230
+
231
+ export function getCurrent(): UpdateScope | undefined {
232
+ return current;
233
+ }
234
+
235
+ export function runWithUpdateScope<T>(run: () => T, updateScope: UpdateScope | undefined): T {
236
+ const previous = current;
237
+ current = updateScope;
238
+ try {
239
+ return run();
240
+ } finally {
241
+ current = previous;
242
+ }
243
+ }
244
+ }
245
+
246
+ /**
247
+ * Runs the given function within an update scope in which multiple observables can be updated in a batch.
248
+ */
249
+ export function update<T>(run: (scope: UpdateScope) => T, updateScope = UpdateScope.getCurrent()): T {
250
+ const ownsUpdateScope = !updateScope;
251
+ if (!updateScope) {
252
+ updateScope = new UpdateScope();
253
+ }
254
+ try {
255
+ return UpdateScope.runWithUpdateScope(() => run(updateScope), updateScope);
256
+ } finally {
257
+ if (ownsUpdateScope) {
258
+ updateScope.dispose();
259
+ }
260
+ }
261
+ }
262
+
263
+ /**
264
+ * Makes sure that the given observable is being observed until the returned disposable is disposed,
265
+ * after which there is no longer a guarantee that the observable is being observed by at least one observer.
266
+ *
267
+ * This function can help keep the cache of a derived observable alive even when there might be no other observers.
268
+ */
269
+ export function keepObserved<T>(observable: Observable<T>): Disposable {
270
+ const observer: Observer = {
271
+ beginUpdate: () => { },
272
+ endUpdate: () => { },
273
+ handlePossibleChange: () => { },
274
+ handleChange: () => { }
275
+ };
276
+ observable.addObserver(observer);
277
+ return Disposable.create(() => {
278
+ observable.removeObserver(observer);
279
+ });
280
+ }
281
+ }
282
+
283
+ export abstract class AbstractObservable<T, TChange> implements Observable<T, TChange> {
284
+
285
+ get(accessor = Observable.Accessor.getCurrent()): T {
286
+ return accessor ? accessor(this) : this.getValue();
287
+ }
288
+
289
+ getUntracked(): T {
290
+ return this.getValue();
291
+ }
292
+
293
+ update(): void {
294
+ this.getValue();
295
+ }
296
+
297
+ abstract addObserver(observer: Observable.Observer): void;
298
+ abstract removeObserver(observer: Observable.Observer): void;
299
+
300
+ protected abstract getValue(): T;
301
+ }
302
+
303
+ export abstract class BaseObservable<T, TChange = void> extends AbstractObservable<T, TChange> {
304
+
305
+ protected readonly observers = new Set<Observable.Observer>();
306
+
307
+ override addObserver(observer: Observable.Observer): void {
308
+ const isFirst = this.observers.size === 0;
309
+ this.observers.add(observer);
310
+ if (isFirst) {
311
+ this.onFirstObserverAdded();
312
+ }
313
+ }
314
+
315
+ override removeObserver(observer: Observable.Observer): void {
316
+ const deleted = this.observers.delete(observer);
317
+ if (deleted && this.observers.size === 0) {
318
+ this.onLastObserverRemoved();
319
+ }
320
+ }
321
+
322
+ protected onFirstObserverAdded(): void { }
323
+ protected onLastObserverRemoved(): void { }
324
+ }