UNPKG

monaco-editor-core

Version:

A browser based code editor

193 lines (192 loc) 7.65 kB
/*--------------------------------------------------------------------------------------------- * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ import { assertFn } from '../assert.js'; import { DisposableStore, markAsDisposed, toDisposable, trackDisposable } from '../lifecycle.js'; import { DebugNameData } from './debugName.js'; import { getLogger } from './logging.js'; /** * Runs immediately and whenever a transaction ends and an observed observable changed. * {@link fn} should start with a JS Doc using `@description` to name the autorun. */ export function autorun(fn) { return new AutorunObserver(new DebugNameData(undefined, undefined, fn), fn, undefined, undefined); } /** * Runs immediately and whenever a transaction ends and an observed observable changed. * {@link fn} should start with a JS Doc using `@description` to name the autorun. */ export function autorunOpts(options, fn) { return new AutorunObserver(new DebugNameData(options.owner, options.debugName, options.debugReferenceFn ?? fn), fn, undefined, undefined); } /** * Runs immediately and whenever a transaction ends and an observed observable changed. * {@link fn} should start with a JS Doc using `@description` to name the autorun. * * Use `createEmptyChangeSummary` to create a "change summary" that can collect the changes. * Use `handleChange` to add a reported change to the change summary. * The run function is given the last change summary. * The change summary is discarded after the run function was called. * * @see autorun */ export function autorunHandleChanges(options, fn) { return new AutorunObserver(new DebugNameData(options.owner, options.debugName, options.debugReferenceFn ?? fn), fn, options.createEmptyChangeSummary, options.handleChange); } /** * @see autorunHandleChanges (but with a disposable store that is cleared before the next run or on dispose) */ export function autorunWithStoreHandleChanges(options, fn) { const store = new DisposableStore(); const disposable = autorunHandleChanges({ owner: options.owner, debugName: options.debugName, debugReferenceFn: options.debugReferenceFn ?? fn, createEmptyChangeSummary: options.createEmptyChangeSummary, handleChange: options.handleChange, }, (reader, changeSummary) => { store.clear(); fn(reader, changeSummary, store); }); return toDisposable(() => { disposable.dispose(); store.dispose(); }); } /** * @see autorun (but with a disposable store that is cleared before the next run or on dispose) */ export function autorunWithStore(fn) { const store = new DisposableStore(); const disposable = autorunOpts({ owner: undefined, debugName: undefined, debugReferenceFn: fn, }, reader => { store.clear(); fn(reader, store); }); return toDisposable(() => { disposable.dispose(); store.dispose(); }); } export class AutorunObserver { get debugName() { return this._debugNameData.getDebugName(this) ?? '(anonymous)'; } constructor(_debugNameData, _runFn, createChangeSummary, _handleChange) { this._debugNameData = _debugNameData; this._runFn = _runFn; this.createChangeSummary = createChangeSummary; this._handleChange = _handleChange; this.state = 2 /* AutorunState.stale */; this.updateCount = 0; this.disposed = false; this.dependencies = new Set(); this.dependenciesToBeRemoved = new Set(); this.changeSummary = this.createChangeSummary?.(); getLogger()?.handleAutorunCreated(this); this._runIfNeeded(); trackDisposable(this); } dispose() { this.disposed = true; for (const o of this.dependencies) { o.removeObserver(this); } this.dependencies.clear(); markAsDisposed(this); } _runIfNeeded() { if (this.state === 3 /* AutorunState.upToDate */) { return; } const emptySet = this.dependenciesToBeRemoved; this.dependenciesToBeRemoved = this.dependencies; this.dependencies = emptySet; this.state = 3 /* AutorunState.upToDate */; const isDisposed = this.disposed; try { if (!isDisposed) { getLogger()?.handleAutorunTriggered(this); const changeSummary = this.changeSummary; this.changeSummary = this.createChangeSummary?.(); this._runFn(this, changeSummary); } } finally { if (!isDisposed) { getLogger()?.handleAutorunFinished(this); } // We don't want our observed observables to think that they are (not even temporarily) not being observed. // Thus, we only unsubscribe from observables that are definitely not read anymore. for (const o of this.dependenciesToBeRemoved) { o.removeObserver(this); } this.dependenciesToBeRemoved.clear(); } } toString() { return `Autorun<${this.debugName}>`; } // IObserver implementation beginUpdate() { if (this.state === 3 /* AutorunState.upToDate */) { this.state = 1 /* AutorunState.dependenciesMightHaveChanged */; } this.updateCount++; } endUpdate() { if (this.updateCount === 1) { do { if (this.state === 1 /* AutorunState.dependenciesMightHaveChanged */) { this.state = 3 /* AutorunState.upToDate */; for (const d of this.dependencies) { d.reportChanges(); if (this.state === 2 /* AutorunState.stale */) { // The other dependencies will refresh on demand break; } } } this._runIfNeeded(); } while (this.state !== 3 /* AutorunState.upToDate */); } this.updateCount--; assertFn(() => this.updateCount >= 0); } handlePossibleChange(observable) { if (this.state === 3 /* AutorunState.upToDate */ && this.dependencies.has(observable) && !this.dependenciesToBeRemoved.has(observable)) { this.state = 1 /* AutorunState.dependenciesMightHaveChanged */; } } handleChange(observable, change) { if (this.dependencies.has(observable) && !this.dependenciesToBeRemoved.has(observable)) { const shouldReact = this._handleChange ? this._handleChange({ changedObservable: observable, change, didChange: (o) => o === observable, }, this.changeSummary) : true; if (shouldReact) { this.state = 2 /* AutorunState.stale */; } } } // IReader implementation readObservable(observable) { // In case the run action disposes the autorun if (this.disposed) { return observable.get(); } observable.addObserver(this); const value = observable.get(); this.dependencies.add(observable); this.dependenciesToBeRemoved.delete(observable); return value; } } (function (autorun) { autorun.Observer = AutorunObserver; })(autorun || (autorun = {}));