@theia/core
Version:
Theia is a cloud & desktop IDE framework implemented in TypeScript.
194 lines • 9.35 kB
JavaScript
"use strict";
// *****************************************************************************
// Copyright (C) 2025 1C-Soft LLC and others.
//
// This program and the accompanying materials are made available under the
// terms of the Eclipse Public License v. 2.0 which is available at
// http://www.eclipse.org/legal/epl-2.0.
//
// This Source Code may also be made available under the following Secondary
// Licenses when the conditions for such availability set forth in the Eclipse
// Public License v. 2.0 are satisfied: GNU General Public License, version 2
// with the GNU Classpath Exception which is available at
// https://www.gnu.org/software/classpath/license.html.
//
// SPDX-License-Identifier: EPL-2.0 OR GPL-2.0-only WITH Classpath-exception-2.0
// *****************************************************************************
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
// copied and modified from https://github.com/microsoft/vscode/blob/1.96.3/src/vs/base/common/observableInternal/autorun.ts
Object.defineProperty(exports, "__esModule", { value: true });
exports.Autorun = void 0;
const observable_base_1 = require("./observable-base");
class Autorun {
constructor(doRun, options) {
var _a;
this.doRun = doRun;
this.state = 1 /* Autorun.State.Stale */;
this.updateCount = 0;
this.disposed = false;
this.isRunning = false;
this.dependencies = new Set();
this.dependencyObserver = this.createDependencyObserver();
this.createChangeSummary = options === null || options === void 0 ? void 0 : options.createChangeSummary;
this.willHandleChange = options === null || options === void 0 ? void 0 : options.willHandleChange;
this.changeSummary = (_a = this.createChangeSummary) === null || _a === void 0 ? void 0 : _a.call(this);
try {
this.run(true);
}
catch (e) {
this.dispose();
throw e;
}
}
dispose() {
if (this.disposed) {
return;
}
this.disposed = true;
for (const dependency of this.dependencies) {
dependency.removeObserver(this.dependencyObserver);
}
this.dependencies.clear();
}
run(isFirstRun = false) {
var _a;
if (this.disposed) {
return;
}
this.dependenciesToBeRemoved = this.dependencies;
this.dependencies = new Set();
this.state = 2 /* Autorun.State.UpToDate */;
try {
const { changeSummary } = this;
this.changeSummary = (_a = this.createChangeSummary) === null || _a === void 0 ? void 0 : _a.call(this);
this.isRunning = true;
observable_base_1.Observable.Accessor.runWithAccessor(() => this.doRun({ autorun: this, isFirstRun, changeSummary }), dependency => this.watchDependency(dependency));
}
finally {
this.isRunning = false;
// We don't want our watched dependencies to think that they are no longer observed, even temporarily.
// Thus, we only unsubscribe from dependencies that are definitely not watched anymore.
for (const dependency of this.dependenciesToBeRemoved) {
dependency.removeObserver(this.dependencyObserver);
}
this.dependenciesToBeRemoved = undefined;
}
}
watchDependency(dependency) {
var _a;
if (!this.isRunning) {
throw new Error('The accessor may only be called while the autorun is running');
}
// In case the run action disposed the autorun.
if (this.disposed) {
return dependency.getUntracked();
}
// Subscribe before getting the value to enable caching.
dependency.addObserver(this.dependencyObserver);
// This might call handleChange indirectly, which could invalidate us.
const value = dependency.getUntracked();
// Which is why we only add the observable to the dependencies now.
this.dependencies.add(dependency);
(_a = this.dependenciesToBeRemoved) === null || _a === void 0 ? void 0 : _a.delete(dependency);
return value;
}
createDependencyObserver() {
return {
beginUpdate: () => {
if (this.state === 2 /* Autorun.State.UpToDate */) {
this.state = 0 /* Autorun.State.DependenciesMightHaveChanged */;
}
this.updateCount++;
},
endUpdate: () => {
this.updateCount--;
if (this.updateCount === 0) {
do {
if (this.state === 0 /* Autorun.State.DependenciesMightHaveChanged */) {
this.state = 2 /* Autorun.State.UpToDate */;
for (const dependency of this.dependencies) {
dependency.update(); // might call handleChange indirectly, which could make us stale
if (this.state === 1 /* Autorun.State.Stale */) {
// The other dependencies will refresh on demand
break;
}
}
}
if (this.state !== 2 /* Autorun.State.UpToDate */) {
try {
this.run();
}
catch (e) {
console.error(e);
}
}
// In case the run action changed one of our dependencies, we need to run again.
} while (this.state !== 2 /* Autorun.State.UpToDate */);
}
if (this.updateCount < 0) {
throw new Error('Unexpected update count: ' + this.updateCount);
}
},
handlePossibleChange: (observable) => {
var _a;
if (this.state === 2 /* Autorun.State.UpToDate */ && this.dependencies.has(observable) && !((_a = this.dependenciesToBeRemoved) === null || _a === void 0 ? void 0 : _a.has(observable))) {
this.state = 0 /* Autorun.State.DependenciesMightHaveChanged */;
}
},
handleChange: (observable, change) => {
var _a;
if (this.dependencies.has(observable) && !((_a = this.dependenciesToBeRemoved) === null || _a === void 0 ? void 0 : _a.has(observable))) {
let shouldReact = true;
if (this.willHandleChange) {
try {
shouldReact = this.willHandleChange({
observable,
change,
isChangeOf: (o) => o === observable
}, this.changeSummary);
}
catch (e) {
console.error(e);
}
}
if (shouldReact) {
this.state = 1 /* Autorun.State.Stale */;
}
}
}
};
}
}
exports.Autorun = Autorun;
(function (Autorun) {
/**
* Runs the given {@link run} function immediately, and whenever an update scope ends
* and an observable tracked as a dependency of the autorun has changed.
*
* Note that the run function of the autorun is called within an invocation context where
* the {@link Observable.Accessor.getCurrent current accessor} is set to track the autorun
* dependencies, so that any observables accessed with `get()` will automatically be tracked.
* Occasionally, it might be useful to disable such automatic tracking and track the dependencies
* manually with `get(accessor)`. This can be done using the {@link Observable.noAutoTracking} function,
* e.g.
* ```ts
* this.toDispose.push(Autorun.create(() => Observable.noAutoTracking(accessor => {
* const value1 = this.observable1.get(accessor); // the autorun will depend on this observable...
* const value2 = this.observable2.get(); // ...but not on this observable
* })));
* ```
* In particular, this pattern might be useful when copying existing autorun code from VS Code,
* where observables can only be tracked manually with `read(reader)`, which corresponds to
* `get(accessor)` in Theia; calls to `get()` never cause an observable to be tracked. This directly
* corresponds to disabling automatic tracking in Theia with {@link Observable.noAutoTracking}.
*/
function create(run, options) {
return new Autorun(run, options);
}
Autorun.create = create;
;
})(Autorun || (exports.Autorun = Autorun = {}));
//# sourceMappingURL=autorun.js.map