@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.
- package/README.md +6 -6
- package/i18n/nls.cs.json +59 -9
- package/i18n/nls.de.json +59 -9
- package/i18n/nls.es.json +59 -9
- package/i18n/nls.fr.json +59 -9
- package/i18n/nls.hu.json +59 -9
- package/i18n/nls.it.json +59 -9
- package/i18n/nls.ja.json +59 -9
- package/i18n/nls.json +63 -13
- package/i18n/nls.ko.json +59 -9
- package/i18n/nls.pl.json +59 -9
- package/i18n/nls.pt-br.json +59 -9
- package/i18n/nls.ru.json +59 -9
- package/i18n/nls.tr.json +59 -9
- package/i18n/nls.zh-cn.json +59 -9
- package/i18n/nls.zh-tw.json +59 -9
- package/lib/browser/catalog.json +149 -15
- package/lib/browser/context-key-service.d.ts +4 -0
- package/lib/browser/context-key-service.d.ts.map +1 -1
- package/lib/browser/context-key-service.js.map +1 -1
- package/lib/browser/frontend-application-module.js +1 -1
- package/lib/browser/frontend-application-module.js.map +1 -1
- package/lib/browser/menu/browser-menu-plugin.d.ts +1 -1
- package/lib/browser/menu/browser-menu-plugin.d.ts.map +1 -1
- package/lib/browser/menu/browser-menu-plugin.js +2 -2
- package/lib/browser/menu/browser-menu-plugin.js.map +1 -1
- package/lib/browser/preload/i18n-preload-contribution.js +1 -1
- package/lib/browser/preload/i18n-preload-contribution.js.map +1 -1
- package/lib/browser/shell/application-shell.d.ts +1 -1
- package/lib/browser/shell/application-shell.d.ts.map +1 -1
- package/lib/browser/shell/application-shell.js.map +1 -1
- package/lib/common/array-utils.d.ts +4 -0
- package/lib/common/array-utils.d.ts.map +1 -1
- package/lib/common/array-utils.js +47 -0
- package/lib/common/array-utils.js.map +1 -1
- package/lib/common/diff.d.ts +33 -0
- package/lib/common/diff.d.ts.map +1 -0
- package/lib/common/diff.js +20 -0
- package/lib/common/diff.js.map +1 -0
- package/lib/common/message-rpc/msg-pack-extension-manager.js +2 -2
- package/lib/common/message-rpc/msg-pack-extension-manager.js.map +1 -1
- package/lib/common/observable/autorun.d.ts +81 -0
- package/lib/common/observable/autorun.d.ts.map +1 -0
- package/lib/common/observable/autorun.js +194 -0
- package/lib/common/observable/autorun.js.map +1 -0
- package/lib/common/observable/derived-observable.d.ts +71 -0
- package/lib/common/observable/derived-observable.d.ts.map +1 -0
- package/lib/common/observable/derived-observable.js +258 -0
- package/lib/common/observable/derived-observable.js.map +1 -0
- package/lib/common/observable/index.d.ts +8 -0
- package/lib/common/observable/index.d.ts.map +1 -0
- package/lib/common/observable/index.js +26 -0
- package/lib/common/observable/index.js.map +1 -0
- package/lib/common/observable/observable-base.d.ts +181 -0
- package/lib/common/observable/observable-base.d.ts.map +1 -0
- package/lib/common/observable/observable-base.js +183 -0
- package/lib/common/observable/observable-base.js.map +1 -0
- package/lib/common/observable/observable-from-event.d.ts +41 -0
- package/lib/common/observable/observable-from-event.d.ts.map +1 -0
- package/lib/common/observable/observable-from-event.js +115 -0
- package/lib/common/observable/observable-from-event.js.map +1 -0
- package/lib/common/observable/observable-signal.d.ts +9 -0
- package/lib/common/observable/observable-signal.d.ts.map +1 -0
- package/lib/common/observable/observable-signal.js +45 -0
- package/lib/common/observable/observable-signal.js.map +1 -0
- package/lib/common/observable/observable-utils.d.ts +26 -0
- package/lib/common/observable/observable-utils.d.ts.map +1 -0
- package/lib/common/observable/observable-utils.js +98 -0
- package/lib/common/observable/observable-utils.js.map +1 -0
- package/lib/common/observable/observable.spec.d.ts +2 -0
- package/lib/common/observable/observable.spec.d.ts.map +1 -0
- package/lib/common/observable/observable.spec.js +301 -0
- package/lib/common/observable/observable.spec.js.map +1 -0
- package/lib/common/observable/settable-observable.d.ts +16 -0
- package/lib/common/observable/settable-observable.d.ts.map +1 -0
- package/lib/common/observable/settable-observable.js +58 -0
- package/lib/common/observable/settable-observable.js.map +1 -0
- package/package.json +7 -8
- package/src/browser/context-key-service.ts +5 -0
- package/src/browser/frontend-application-module.ts +1 -1
- package/src/browser/menu/browser-menu-plugin.ts +3 -3
- package/src/browser/preload/i18n-preload-contribution.ts +1 -1
- package/src/browser/shell/application-shell.ts +1 -1
- package/src/common/array-utils.ts +53 -0
- package/src/common/diff.ts +56 -0
- package/src/common/message-rpc/msg-pack-extension-manager.ts +2 -2
- package/src/common/observable/autorun.ts +246 -0
- package/src/common/observable/derived-observable.ts +321 -0
- package/src/common/observable/index.ts +23 -0
- package/src/common/observable/observable-base.ts +324 -0
- package/src/common/observable/observable-from-event.ts +148 -0
- package/src/common/observable/observable-signal.ts +48 -0
- package/src/common/observable/observable-utils.ts +119 -0
- package/src/common/observable/observable.spec.ts +342 -0
- package/src/common/observable/settable-observable.ts +73 -0
|
@@ -0,0 +1,148 @@
|
|
|
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/utils.ts
|
|
21
|
+
|
|
22
|
+
import { Disposable } from '../disposable';
|
|
23
|
+
import { Event } from '../event';
|
|
24
|
+
import { BaseObservable, Observable } from './observable-base';
|
|
25
|
+
|
|
26
|
+
export class ObservableFromEvent<T, E> extends BaseObservable<T> {
|
|
27
|
+
|
|
28
|
+
protected value?: T;
|
|
29
|
+
protected subscription?: Disposable;
|
|
30
|
+
protected readonly isEqual: (a: T, b: T) => boolean;
|
|
31
|
+
protected readonly getUpdateScope: () => Observable.UpdateScope | undefined;
|
|
32
|
+
|
|
33
|
+
constructor(
|
|
34
|
+
protected readonly event: Event<E>,
|
|
35
|
+
protected readonly compute: (e: E | undefined) => T,
|
|
36
|
+
options?: ObservableFromEvent.Options<T>
|
|
37
|
+
) {
|
|
38
|
+
super();
|
|
39
|
+
this.isEqual = options?.isEqual ?? ((a, b) => a === b);
|
|
40
|
+
this.getUpdateScope = options?.getUpdateScope ?? Observable.UpdateScope.getCurrent;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
protected handleEvent(e: E | undefined): void {
|
|
44
|
+
const hadValue = this.hasValue();
|
|
45
|
+
const oldValue = this.value;
|
|
46
|
+
|
|
47
|
+
this.value = this.compute(e);
|
|
48
|
+
|
|
49
|
+
const didChange = hadValue && !this.isEqual(oldValue!, this.value);
|
|
50
|
+
|
|
51
|
+
if (didChange) {
|
|
52
|
+
Observable.update(scope => {
|
|
53
|
+
|
|
54
|
+
for (const observer of this.observers) {
|
|
55
|
+
scope.push(observer, this);
|
|
56
|
+
observer.handleChange(this);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
}, this.getUpdateScope());
|
|
60
|
+
}
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
protected override onFirstObserverAdded(): void {
|
|
64
|
+
this.subscription = this.event(this.handleEvent, this);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
protected override onLastObserverRemoved(): void {
|
|
68
|
+
this.subscription?.dispose();
|
|
69
|
+
this.subscription = undefined;
|
|
70
|
+
delete this.value;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
protected hasValue(): boolean {
|
|
74
|
+
return 'value' in this;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
protected override getValue(): T {
|
|
78
|
+
if (this.subscription) {
|
|
79
|
+
if (!this.hasValue()) {
|
|
80
|
+
this.handleEvent(undefined);
|
|
81
|
+
}
|
|
82
|
+
return this.value!;
|
|
83
|
+
} else {
|
|
84
|
+
return this.compute(undefined);
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
export namespace ObservableFromEvent {
|
|
90
|
+
|
|
91
|
+
export function create<T, E>(event: Event<E>, compute: (e: E | undefined) => T, options?: Options<T>): Observable<T, void> {
|
|
92
|
+
return new ObservableFromEvent(event, compute, options);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
export interface Options<T> {
|
|
96
|
+
isEqual?: (a: T, b: T) => boolean;
|
|
97
|
+
getUpdateScope?: () => Observable.UpdateScope | undefined;
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
export class ObservableSignalFromEvent extends BaseObservable<void> {
|
|
102
|
+
|
|
103
|
+
protected subscription?: Disposable;
|
|
104
|
+
protected readonly getUpdateScope: () => Observable.UpdateScope | undefined;
|
|
105
|
+
|
|
106
|
+
constructor(
|
|
107
|
+
protected readonly event: Event<unknown>,
|
|
108
|
+
options?: ObservableSignalFromEvent.Options
|
|
109
|
+
) {
|
|
110
|
+
super();
|
|
111
|
+
this.getUpdateScope = options?.getUpdateScope ?? Observable.UpdateScope.getCurrent;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
protected handleEvent(): void {
|
|
115
|
+
Observable.update(scope => {
|
|
116
|
+
|
|
117
|
+
for (const observer of this.observers) {
|
|
118
|
+
scope.push(observer, this);
|
|
119
|
+
observer.handleChange(this);
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
}, this.getUpdateScope());
|
|
123
|
+
};
|
|
124
|
+
|
|
125
|
+
protected override onFirstObserverAdded(): void {
|
|
126
|
+
this.subscription = this.event(this.handleEvent, this);
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
protected override onLastObserverRemoved(): void {
|
|
130
|
+
this.subscription?.dispose();
|
|
131
|
+
this.subscription = undefined;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
protected override getValue(): void {
|
|
135
|
+
// NO OP
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
export namespace ObservableSignalFromEvent {
|
|
140
|
+
|
|
141
|
+
export function create(event: Event<unknown>): Observable<void> {
|
|
142
|
+
return new ObservableSignalFromEvent(event);
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
export interface Options {
|
|
146
|
+
getUpdateScope?: () => Observable.UpdateScope | undefined;
|
|
147
|
+
}
|
|
148
|
+
}
|
|
@@ -0,0 +1,48 @@
|
|
|
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/utils.ts
|
|
21
|
+
|
|
22
|
+
import { BaseObservable, Observable } from './observable-base';
|
|
23
|
+
|
|
24
|
+
export class ObservableSignal<TChange> extends BaseObservable<void, TChange> implements Observable.Signal<TChange> {
|
|
25
|
+
|
|
26
|
+
trigger(change?: TChange, updateScope = Observable.UpdateScope.getCurrent()): void {
|
|
27
|
+
|
|
28
|
+
Observable.update(scope => {
|
|
29
|
+
|
|
30
|
+
for (const observer of this.observers) {
|
|
31
|
+
scope.push(observer, this);
|
|
32
|
+
observer.handleChange(this, change);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
}, updateScope);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
protected override getValue(): void {
|
|
39
|
+
// NO OP
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export namespace ObservableSignal {
|
|
44
|
+
|
|
45
|
+
export function create<TChange = void>(): Observable.Signal<TChange> {
|
|
46
|
+
return new ObservableSignal();
|
|
47
|
+
}
|
|
48
|
+
}
|
|
@@ -0,0 +1,119 @@
|
|
|
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/utils.ts,
|
|
21
|
+
// https://github.com/microsoft/vscode/blob/1.96.3/src/vs/base/common/observableInternal/utilsCancellation.ts
|
|
22
|
+
|
|
23
|
+
import { CancellationError, CancellationToken } from '../cancellation';
|
|
24
|
+
import { Disposable, DisposableCollection } from '../disposable';
|
|
25
|
+
import { Observable } from './observable-base';
|
|
26
|
+
import { DerivedObservable } from './derived-observable';
|
|
27
|
+
import { Autorun } from './autorun';
|
|
28
|
+
|
|
29
|
+
export namespace ObservableUtils {
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Creates an {@link Autorun.create autorun} that passes a collector for disposable objects to the {@link run} function.
|
|
33
|
+
* The collected disposables are disposed before the next run or when the autorun is disposed.
|
|
34
|
+
*/
|
|
35
|
+
export function autorunWithDisposables<TChangeSummary = void>(
|
|
36
|
+
run: (args: Autorun.Args<TChangeSummary> & { readonly toDispose: { push(disposable: Disposable): void } }) => void,
|
|
37
|
+
options?: Autorun.Options<TChangeSummary>
|
|
38
|
+
): Disposable {
|
|
39
|
+
let toDispose: DisposableCollection | undefined = undefined;
|
|
40
|
+
return new class extends Autorun<TChangeSummary> {
|
|
41
|
+
override dispose(): void {
|
|
42
|
+
super.dispose();
|
|
43
|
+
toDispose?.dispose();
|
|
44
|
+
}
|
|
45
|
+
}(
|
|
46
|
+
({ autorun, isFirstRun, changeSummary }) => {
|
|
47
|
+
toDispose?.dispose();
|
|
48
|
+
toDispose = new DisposableCollection();
|
|
49
|
+
run({ toDispose, autorun, isFirstRun, changeSummary });
|
|
50
|
+
},
|
|
51
|
+
options
|
|
52
|
+
);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export function derivedObservableWithCache<T, TChangeSummary = void>(
|
|
56
|
+
compute: (args: DerivedObservable.Args<TChangeSummary> & { readonly lastValue: T | undefined }) => T,
|
|
57
|
+
options?: DerivedObservable.Options<T, TChangeSummary>
|
|
58
|
+
): Observable<T, void> {
|
|
59
|
+
let value: T | undefined = undefined;
|
|
60
|
+
return new DerivedObservable(
|
|
61
|
+
({ changeSummary }) => {
|
|
62
|
+
value = compute({ lastValue: value, changeSummary });
|
|
63
|
+
return value;
|
|
64
|
+
},
|
|
65
|
+
options
|
|
66
|
+
);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Resolves the promise when the observable's state matches the predicate.
|
|
71
|
+
*/
|
|
72
|
+
export function waitForState<T>(observable: Observable<T | undefined>): Promise<T>;
|
|
73
|
+
export function waitForState<T, TState extends T>(observable: Observable<T>, predicate: (state: T) => state is TState,
|
|
74
|
+
isError?: (state: T) => boolean | unknown | undefined, cancellationToken?: CancellationToken
|
|
75
|
+
): Promise<TState>;
|
|
76
|
+
export function waitForState<T>(observable: Observable<T>, predicate: (state: T) => boolean,
|
|
77
|
+
isError?: (state: T) => boolean | unknown | undefined, cancellationToken?: CancellationToken
|
|
78
|
+
): Promise<T>;
|
|
79
|
+
export function waitForState<T>(observable: Observable<T>, predicate?: (state: T) => boolean,
|
|
80
|
+
isError?: (state: T) => boolean | unknown | undefined, cancellationToken?: CancellationToken
|
|
81
|
+
): Promise<T> {
|
|
82
|
+
if (!predicate) {
|
|
83
|
+
predicate = state => !!state;
|
|
84
|
+
}
|
|
85
|
+
return new Promise((resolve, reject) => {
|
|
86
|
+
const stateObservable = DerivedObservable.create(() => {
|
|
87
|
+
const state = observable.get();
|
|
88
|
+
return {
|
|
89
|
+
isFinished: predicate(state),
|
|
90
|
+
error: isError ? isError(state) : false,
|
|
91
|
+
state
|
|
92
|
+
};
|
|
93
|
+
});
|
|
94
|
+
const autorun_ = Autorun.create(({ autorun }) => {
|
|
95
|
+
const { isFinished, error, state } = stateObservable.get();
|
|
96
|
+
if (isFinished || error) {
|
|
97
|
+
autorun.dispose();
|
|
98
|
+
if (error) {
|
|
99
|
+
reject(error === true ? state : error);
|
|
100
|
+
} else {
|
|
101
|
+
resolve(state);
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
});
|
|
105
|
+
if (cancellationToken) {
|
|
106
|
+
const subscription = cancellationToken.onCancellationRequested(() => {
|
|
107
|
+
autorun_.dispose();
|
|
108
|
+
subscription.dispose();
|
|
109
|
+
reject(new CancellationError());
|
|
110
|
+
});
|
|
111
|
+
if (cancellationToken.isCancellationRequested) {
|
|
112
|
+
autorun_.dispose();
|
|
113
|
+
subscription.dispose();
|
|
114
|
+
reject(new CancellationError());
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
});
|
|
118
|
+
}
|
|
119
|
+
}
|
|
@@ -0,0 +1,342 @@
|
|
|
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/test/common/observable.test.ts
|
|
21
|
+
|
|
22
|
+
import { expect } from 'chai';
|
|
23
|
+
import { DisposableCollection } from '../disposable';
|
|
24
|
+
import { Observable } from './observable-base';
|
|
25
|
+
import { SettableObservable } from './settable-observable';
|
|
26
|
+
import { DerivedObservable } from './derived-observable';
|
|
27
|
+
import { ObservableSignal } from './observable-signal';
|
|
28
|
+
import { Autorun } from './autorun';
|
|
29
|
+
|
|
30
|
+
describe('Observables', () => {
|
|
31
|
+
let disposables: DisposableCollection;
|
|
32
|
+
beforeEach(() => disposables = new DisposableCollection());
|
|
33
|
+
afterEach(() => disposables.dispose());
|
|
34
|
+
|
|
35
|
+
// Read these tests to understand how to use observables.
|
|
36
|
+
describe('Tutorial', () => {
|
|
37
|
+
it('settable observable & autorun', () => {
|
|
38
|
+
const log = new Log();
|
|
39
|
+
// This creates an observable with an initial value that can later be changed with the `set` method.
|
|
40
|
+
const myObservable = SettableObservable.create(0);
|
|
41
|
+
|
|
42
|
+
// This creates an autorun. The autorun has to be disposed!
|
|
43
|
+
disposables.push(Autorun.create(() => { // This code runs immediately and then whenever any of the autorun's dependencies change.
|
|
44
|
+
|
|
45
|
+
// Observables are automatically added to the tracked dependencies of the autorun as they are accessed with `get`.
|
|
46
|
+
log.log(`myAutorun.run(myObservable: ${myObservable.get()})`);
|
|
47
|
+
|
|
48
|
+
// Now that all dependencies are tracked, the autorun is re-run whenever any of the dependencies change.
|
|
49
|
+
}));
|
|
50
|
+
// The autorun runs immediately.
|
|
51
|
+
expect(log.getAndClearEntries()).to.be.deep.equal(['myAutorun.run(myObservable: 0)']);
|
|
52
|
+
|
|
53
|
+
myObservable.set(1);
|
|
54
|
+
// The autorun runs again, because its dependency changed.
|
|
55
|
+
expect(log.getAndClearEntries()).to.be.deep.equal(['myAutorun.run(myObservable: 1)']);
|
|
56
|
+
|
|
57
|
+
myObservable.set(1);
|
|
58
|
+
// The autorun didn't run, because the observable was set to the same value (no change).
|
|
59
|
+
expect(log.getAndClearEntries()).to.be.deep.equal([]);
|
|
60
|
+
|
|
61
|
+
// An update scope can be used to batch autorun runs.
|
|
62
|
+
Observable.update(() => {
|
|
63
|
+
myObservable.set(2);
|
|
64
|
+
expect(log.getAndClearEntries()).to.be.deep.equal([]); // The autorun didn't run, even though its dependency changed!
|
|
65
|
+
|
|
66
|
+
myObservable.set(3);
|
|
67
|
+
expect(log.getAndClearEntries()).to.be.deep.equal([]);
|
|
68
|
+
});
|
|
69
|
+
// The autorun re-runs only at the end of the update scope.
|
|
70
|
+
// Note that the autorun didn't see the intermediate value `2`!
|
|
71
|
+
expect(log.getAndClearEntries()).to.be.deep.equal(['myAutorun.run(myObservable: 3)']);
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
it('derived observable & autorun', () => {
|
|
75
|
+
const log = new Log();
|
|
76
|
+
const observable1 = SettableObservable.create(0);
|
|
77
|
+
const observable2 = SettableObservable.create(0);
|
|
78
|
+
|
|
79
|
+
// This creates an observable that is derived from other observables.
|
|
80
|
+
const myDerived = DerivedObservable.create(() => {
|
|
81
|
+
// Dependencies are automatically tracked as they are accessed with `get`.
|
|
82
|
+
const value1 = observable1.get();
|
|
83
|
+
const value2 = observable2.get();
|
|
84
|
+
const sum = value1 + value2;
|
|
85
|
+
log.log(`myDerived.recompute: ${value1} + ${value2} = ${sum}`);
|
|
86
|
+
return sum;
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
// This creates an autorun that reacts to changes of the derived observable.
|
|
90
|
+
disposables.push(Autorun.create(() => {
|
|
91
|
+
log.log(`myAutorun(myDerived: ${myDerived.get()})`);
|
|
92
|
+
}));
|
|
93
|
+
// The autorun runs immediately...
|
|
94
|
+
expect(log.getAndClearEntries()).to.be.deep.equal([
|
|
95
|
+
'myDerived.recompute: 0 + 0 = 0',
|
|
96
|
+
'myAutorun(myDerived: 0)',
|
|
97
|
+
]);
|
|
98
|
+
|
|
99
|
+
observable1.set(1);
|
|
100
|
+
// ...and on changes...
|
|
101
|
+
expect(log.getAndClearEntries()).to.be.deep.equal([
|
|
102
|
+
'myDerived.recompute: 1 + 0 = 1',
|
|
103
|
+
'myAutorun(myDerived: 1)',
|
|
104
|
+
]);
|
|
105
|
+
|
|
106
|
+
observable2.set(1);
|
|
107
|
+
// ...of the derived observable.
|
|
108
|
+
expect(log.getAndClearEntries()).to.be.deep.equal([
|
|
109
|
+
'myDerived.recompute: 1 + 1 = 2',
|
|
110
|
+
'myAutorun(myDerived: 2)',
|
|
111
|
+
]);
|
|
112
|
+
|
|
113
|
+
// Multiple observables can be updated in a batch.
|
|
114
|
+
Observable.update(() => {
|
|
115
|
+
observable1.set(5);
|
|
116
|
+
expect(log.getAndClearEntries()).to.be.deep.equal([]);
|
|
117
|
+
|
|
118
|
+
observable2.set(5);
|
|
119
|
+
expect(log.getAndClearEntries()).to.be.deep.equal([]);
|
|
120
|
+
});
|
|
121
|
+
// The autorun re-runs only at the end of the update scope.
|
|
122
|
+
// Derived observables are only recomputed on demand.
|
|
123
|
+
expect(log.getAndClearEntries()).to.be.deep.equal([
|
|
124
|
+
'myDerived.recompute: 5 + 5 = 10',
|
|
125
|
+
'myAutorun(myDerived: 10)',
|
|
126
|
+
]);
|
|
127
|
+
|
|
128
|
+
Observable.update(() => {
|
|
129
|
+
observable1.set(6);
|
|
130
|
+
expect(log.getAndClearEntries()).to.be.deep.equal([]);
|
|
131
|
+
|
|
132
|
+
observable2.set(4);
|
|
133
|
+
expect(log.getAndClearEntries()).to.be.deep.equal([]);
|
|
134
|
+
});
|
|
135
|
+
// The autorun didn't run, because its dependency changed from 10 to 10 (no change).
|
|
136
|
+
expect(log.getAndClearEntries()).to.be.deep.equal(['myDerived.recompute: 6 + 4 = 10']);
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
it('derived observable: get within update scope', () => {
|
|
140
|
+
const log = new Log();
|
|
141
|
+
const observable1 = SettableObservable.create(0);
|
|
142
|
+
const observable2 = SettableObservable.create(0);
|
|
143
|
+
|
|
144
|
+
const myDerived = DerivedObservable.create(() => {
|
|
145
|
+
const value1 = observable1.get();
|
|
146
|
+
const value2 = observable2.get();
|
|
147
|
+
const sum = value1 + value2;
|
|
148
|
+
log.log(`myDerived.recompute: ${value1} + ${value2} = ${sum}`);
|
|
149
|
+
return sum;
|
|
150
|
+
});
|
|
151
|
+
|
|
152
|
+
disposables.push(Autorun.create(() => {
|
|
153
|
+
log.log(`myAutorun(myDerived: ${myDerived.get()})`);
|
|
154
|
+
}));
|
|
155
|
+
// The autorun runs immediately.
|
|
156
|
+
expect(log.getAndClearEntries()).to.be.deep.equal([
|
|
157
|
+
'myDerived.recompute: 0 + 0 = 0',
|
|
158
|
+
'myAutorun(myDerived: 0)',
|
|
159
|
+
]);
|
|
160
|
+
|
|
161
|
+
Observable.update(() => {
|
|
162
|
+
observable1.set(-10);
|
|
163
|
+
expect(log.getAndClearEntries()).to.be.deep.equal([]);
|
|
164
|
+
|
|
165
|
+
myDerived.get(); // This forces a (sync) recomputation of the current value!
|
|
166
|
+
expect(log.getAndClearEntries()).to.be.deep.equal(['myDerived.recompute: -10 + 0 = -10']);
|
|
167
|
+
// This means that, even within an update scope, all observable values you get are up-to-date.
|
|
168
|
+
// It might just cause additional (potentially unneeded) recomputations.
|
|
169
|
+
|
|
170
|
+
observable2.set(10);
|
|
171
|
+
expect(log.getAndClearEntries()).to.be.deep.equal([]);
|
|
172
|
+
});
|
|
173
|
+
// The autorun runs again, because its dependency changed from 0 to -10 and then back to 0.
|
|
174
|
+
expect(log.getAndClearEntries()).to.be.deep.equal([
|
|
175
|
+
'myDerived.recompute: -10 + 10 = 0',
|
|
176
|
+
'myAutorun(myDerived: 0)',
|
|
177
|
+
]);
|
|
178
|
+
});
|
|
179
|
+
|
|
180
|
+
it('derived observable: get without observers', () => {
|
|
181
|
+
const log = new Log();
|
|
182
|
+
const observable1 = SettableObservable.create(0);
|
|
183
|
+
|
|
184
|
+
const computed1 = DerivedObservable.create(() => {
|
|
185
|
+
const value1 = observable1.get();
|
|
186
|
+
const result = value1 % 3;
|
|
187
|
+
log.log(`recompute1: ${value1} % 3 = ${result}`);
|
|
188
|
+
return result;
|
|
189
|
+
});
|
|
190
|
+
const computed2 = DerivedObservable.create(() => {
|
|
191
|
+
const value1 = computed1.get();
|
|
192
|
+
const result = value1 * 2;
|
|
193
|
+
log.log(`recompute2: ${value1} * 2 = ${result}`);
|
|
194
|
+
return result;
|
|
195
|
+
});
|
|
196
|
+
const computed3 = DerivedObservable.create(() => {
|
|
197
|
+
const value1 = computed1.get();
|
|
198
|
+
const result = value1 * 3;
|
|
199
|
+
log.log(`recompute3: ${value1} * 3 = ${result}`);
|
|
200
|
+
return result;
|
|
201
|
+
});
|
|
202
|
+
const computedSum = DerivedObservable.create(() => {
|
|
203
|
+
const value1 = computed2.get();
|
|
204
|
+
const value2 = computed3.get();
|
|
205
|
+
const result = value1 + value2;
|
|
206
|
+
log.log(`recompute4: ${value1} + ${value2} = ${result}`);
|
|
207
|
+
return result;
|
|
208
|
+
});
|
|
209
|
+
expect(log.getAndClearEntries()).to.be.deep.equal([]);
|
|
210
|
+
|
|
211
|
+
observable1.set(1);
|
|
212
|
+
// Derived observables are only recomputed on demand.
|
|
213
|
+
expect(log.getAndClearEntries()).to.be.deep.equal([]);
|
|
214
|
+
|
|
215
|
+
log.log(`value: ${computedSum.get()}`);
|
|
216
|
+
expect(log.getAndClearEntries()).to.be.deep.equal([
|
|
217
|
+
'recompute1: 1 % 3 = 1',
|
|
218
|
+
'recompute2: 1 * 2 = 2',
|
|
219
|
+
'recompute3: 1 * 3 = 3',
|
|
220
|
+
'recompute4: 2 + 3 = 5',
|
|
221
|
+
'value: 5',
|
|
222
|
+
]);
|
|
223
|
+
|
|
224
|
+
log.log(`value: ${computedSum.get()}`);
|
|
225
|
+
// Because there are no observers, the derived observable values are not cached (!) but recomputed from scratch.
|
|
226
|
+
expect(log.getAndClearEntries()).to.be.deep.equal([
|
|
227
|
+
'recompute1: 1 % 3 = 1',
|
|
228
|
+
'recompute2: 1 * 2 = 2',
|
|
229
|
+
'recompute3: 1 * 3 = 3',
|
|
230
|
+
'recompute4: 2 + 3 = 5',
|
|
231
|
+
'value: 5',
|
|
232
|
+
]);
|
|
233
|
+
|
|
234
|
+
// keepObserved can be used to keep the cache alive.
|
|
235
|
+
const disposable = Observable.keepObserved(computedSum);
|
|
236
|
+
log.log(`value: ${computedSum.get()}`);
|
|
237
|
+
expect(log.getAndClearEntries()).to.be.deep.equal([
|
|
238
|
+
'recompute1: 1 % 3 = 1',
|
|
239
|
+
'recompute2: 1 * 2 = 2',
|
|
240
|
+
'recompute3: 1 * 3 = 3',
|
|
241
|
+
'recompute4: 2 + 3 = 5',
|
|
242
|
+
'value: 5',
|
|
243
|
+
]);
|
|
244
|
+
|
|
245
|
+
log.log(`value: ${computedSum.get()}`);
|
|
246
|
+
// Tada, no recomputations!
|
|
247
|
+
expect(log.getAndClearEntries()).to.be.deep.equal([
|
|
248
|
+
'value: 5',
|
|
249
|
+
]);
|
|
250
|
+
|
|
251
|
+
observable1.set(2);
|
|
252
|
+
// keepObserved does not force derived observables to be recomputed.
|
|
253
|
+
expect(log.getAndClearEntries()).to.be.deep.equal([]);
|
|
254
|
+
|
|
255
|
+
log.log(`value: ${computedSum.get()}`);
|
|
256
|
+
// Derived observables are only recomputed on demand...
|
|
257
|
+
expect(log.getAndClearEntries()).to.be.deep.equal([
|
|
258
|
+
'recompute1: 2 % 3 = 2',
|
|
259
|
+
'recompute2: 2 * 2 = 4',
|
|
260
|
+
'recompute3: 2 * 3 = 6',
|
|
261
|
+
'recompute4: 4 + 6 = 10',
|
|
262
|
+
'value: 10',
|
|
263
|
+
]);
|
|
264
|
+
log.log(`value: ${computedSum.get()}`);
|
|
265
|
+
// ...and then cached again.
|
|
266
|
+
expect(log.getAndClearEntries()).to.be.deep.equal(['value: 10']);
|
|
267
|
+
|
|
268
|
+
// Don't forget to dispose the disposable returned by keepObserved!
|
|
269
|
+
disposable.dispose();
|
|
270
|
+
|
|
271
|
+
log.log(`value: ${computedSum.get()}`);
|
|
272
|
+
// The cache is disabled again.
|
|
273
|
+
expect(log.getAndClearEntries()).to.be.deep.equal([
|
|
274
|
+
'recompute1: 2 % 3 = 2',
|
|
275
|
+
'recompute2: 2 * 2 = 4',
|
|
276
|
+
'recompute3: 2 * 3 = 6',
|
|
277
|
+
'recompute4: 4 + 6 = 10',
|
|
278
|
+
'value: 10',
|
|
279
|
+
]);
|
|
280
|
+
|
|
281
|
+
log.log(`value: ${computedSum.get()}`);
|
|
282
|
+
expect(log.getAndClearEntries()).to.be.deep.equal([
|
|
283
|
+
'recompute1: 2 % 3 = 2',
|
|
284
|
+
'recompute2: 2 * 2 = 4',
|
|
285
|
+
'recompute3: 2 * 3 = 6',
|
|
286
|
+
'recompute4: 4 + 6 = 10',
|
|
287
|
+
'value: 10',
|
|
288
|
+
]);
|
|
289
|
+
});
|
|
290
|
+
|
|
291
|
+
it('autorun that receives change information of signals', () => {
|
|
292
|
+
const log = new Log();
|
|
293
|
+
|
|
294
|
+
// A signal is an observable without a value.
|
|
295
|
+
// However, it can ship change information when it is triggered.
|
|
296
|
+
const signal = ObservableSignal.create<{ msg: string }>();
|
|
297
|
+
|
|
298
|
+
disposables.push(Autorun.create(({ changeSummary }) => {
|
|
299
|
+
signal.get(); // This makes sure the signal is tracked as a dependency of the autorun.
|
|
300
|
+
log.log('msgs: ' + changeSummary!.msgs.join(', '));
|
|
301
|
+
}, {
|
|
302
|
+
// A change summary can be used to collect the reported changes.
|
|
303
|
+
createChangeSummary: () => ({ msgs: [] as string[] }),
|
|
304
|
+
willHandleChange: (context, changeSummary) => {
|
|
305
|
+
if (context.isChangeOf(signal)) {
|
|
306
|
+
changeSummary!.msgs.push(context.change.msg);
|
|
307
|
+
}
|
|
308
|
+
return true;
|
|
309
|
+
},
|
|
310
|
+
}));
|
|
311
|
+
|
|
312
|
+
signal.trigger({ msg: 'foobar' });
|
|
313
|
+
|
|
314
|
+
// An update scope can be used to batch triggering signals.
|
|
315
|
+
// No change information is lost!
|
|
316
|
+
Observable.update(() => {
|
|
317
|
+
signal.trigger({ msg: 'hello' });
|
|
318
|
+
signal.trigger({ msg: 'world' });
|
|
319
|
+
});
|
|
320
|
+
|
|
321
|
+
expect(log.getAndClearEntries()).to.be.deep.equal([
|
|
322
|
+
'msgs: ',
|
|
323
|
+
'msgs: foobar',
|
|
324
|
+
'msgs: hello, world'
|
|
325
|
+
]);
|
|
326
|
+
});
|
|
327
|
+
});
|
|
328
|
+
});
|
|
329
|
+
|
|
330
|
+
class Log {
|
|
331
|
+
private readonly entries: string[] = [];
|
|
332
|
+
|
|
333
|
+
log(message: string): void {
|
|
334
|
+
this.entries.push(message);
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
getAndClearEntries(): string[] {
|
|
338
|
+
const entries = [...this.entries];
|
|
339
|
+
this.entries.length = 0;
|
|
340
|
+
return entries;
|
|
341
|
+
}
|
|
342
|
+
}
|