UNPKG

monaco-editor

Version:
331 lines (330 loc) • 10.4 kB
/*--------------------------------------------------------------------------------------------- * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ import { getLogger } from './logging.js'; let _recomputeInitiallyAndOnChange; export function _setRecomputeInitiallyAndOnChange(recomputeInitiallyAndOnChange) { _recomputeInitiallyAndOnChange = recomputeInitiallyAndOnChange; } let _keepObserved; export function _setKeepObserved(keepObserved) { _keepObserved = keepObserved; } let _derived; /** * @internal * This is to allow splitting files. */ export function _setDerivedOpts(derived) { _derived = derived; } export class ConvenientObservable { get TChange() { return null; } reportChanges() { this.get(); } /** @sealed */ read(reader) { if (reader) { return reader.readObservable(this); } else { return this.get(); } } map(fnOrOwner, fnOrUndefined) { const owner = fnOrUndefined === undefined ? undefined : fnOrOwner; const fn = fnOrUndefined === undefined ? fnOrOwner : fnOrUndefined; return _derived({ owner, debugName: () => { const name = getFunctionName(fn); if (name !== undefined) { return name; } // regexp to match `x => x.y` or `x => x?.y` where x and y can be arbitrary identifiers (uses backref): const regexp = /^\s*\(?\s*([a-zA-Z_$][a-zA-Z_$0-9]*)\s*\)?\s*=>\s*\1(?:\??)\.([a-zA-Z_$][a-zA-Z_$0-9]*)\s*$/; const match = regexp.exec(fn.toString()); if (match) { return `${this.debugName}.${match[2]}`; } if (!owner) { return `${this.debugName} (mapped)`; } return undefined; }, }, (reader) => fn(this.read(reader), reader)); } recomputeInitiallyAndOnChange(store, handleValue) { store.add(_recomputeInitiallyAndOnChange(this, handleValue)); return this; } } export class BaseObservable extends ConvenientObservable { constructor() { super(...arguments); this.observers = new Set(); } addObserver(observer) { const len = this.observers.size; this.observers.add(observer); if (len === 0) { this.onFirstObserverAdded(); } } removeObserver(observer) { const deleted = this.observers.delete(observer); if (deleted && this.observers.size === 0) { this.onLastObserverRemoved(); } } onFirstObserverAdded() { } onLastObserverRemoved() { } } /** * Starts a transaction in which many observables can be changed at once. * {@link fn} should start with a JS Doc using `@description` to give the transaction a debug name. * Reaction run on demand or when the transaction ends. */ export function transaction(fn, getDebugName) { const tx = new TransactionImpl(fn, getDebugName); try { fn(tx); } finally { tx.finish(); } } let _globalTransaction = undefined; export function globalTransaction(fn) { if (_globalTransaction) { fn(_globalTransaction); } else { const tx = new TransactionImpl(fn, undefined); _globalTransaction = tx; try { fn(tx); } finally { tx.finish(); // During finish, more actions might be added to the transaction. // Which is why we only clear the global transaction after finish. _globalTransaction = undefined; } } } export async function asyncTransaction(fn, getDebugName) { const tx = new TransactionImpl(fn, getDebugName); try { await fn(tx); } finally { tx.finish(); } } /** * Allows to chain transactions. */ export function subtransaction(tx, fn, getDebugName) { if (!tx) { transaction(fn, getDebugName); } else { fn(tx); } } export class TransactionImpl { constructor(_fn, _getDebugName) { var _a; this._fn = _fn; this._getDebugName = _getDebugName; this.updatingObservers = []; (_a = getLogger()) === null || _a === void 0 ? void 0 : _a.handleBeginTransaction(this); } getDebugName() { if (this._getDebugName) { return this._getDebugName(); } return getFunctionName(this._fn); } updateObserver(observer, observable) { // When this gets called while finish is active, they will still get considered this.updatingObservers.push({ observer, observable }); observer.beginUpdate(observable); } finish() { var _a; const updatingObservers = this.updatingObservers; for (let i = 0; i < updatingObservers.length; i++) { const { observer, observable } = updatingObservers[i]; observer.endUpdate(observable); } // Prevent anyone from updating observers from now on. this.updatingObservers = null; (_a = getLogger()) === null || _a === void 0 ? void 0 : _a.handleEndTransaction(); } } const countPerName = new Map(); const cachedDebugName = new WeakMap(); export function getDebugName(self, debugNameFn, fn, owner) { var _a; const cached = cachedDebugName.get(self); if (cached) { return cached; } const dbgName = computeDebugName(self, debugNameFn, fn, owner); if (dbgName) { let count = (_a = countPerName.get(dbgName)) !== null && _a !== void 0 ? _a : 0; count++; countPerName.set(dbgName, count); const result = count === 1 ? dbgName : `${dbgName}#${count}`; cachedDebugName.set(self, result); return result; } return undefined; } function computeDebugName(self, debugNameFn, fn, owner) { const cached = cachedDebugName.get(self); if (cached) { return cached; } const ownerStr = owner ? formatOwner(owner) + `.` : ''; let result; if (debugNameFn !== undefined) { if (typeof debugNameFn === 'function') { result = debugNameFn(); if (result !== undefined) { return ownerStr + result; } } else { return ownerStr + debugNameFn; } } if (fn !== undefined) { result = getFunctionName(fn); if (result !== undefined) { return ownerStr + result; } } if (owner !== undefined) { for (const key in owner) { if (owner[key] === self) { return ownerStr + key; } } } return undefined; } const countPerClassName = new Map(); const ownerId = new WeakMap(); function formatOwner(owner) { var _a; const id = ownerId.get(owner); if (id) { return id; } const className = getClassName(owner); let count = (_a = countPerClassName.get(className)) !== null && _a !== void 0 ? _a : 0; count++; countPerClassName.set(className, count); const result = count === 1 ? className : `${className}#${count}`; ownerId.set(owner, result); return result; } function getClassName(obj) { const ctor = obj.constructor; if (ctor) { return ctor.name; } return 'Object'; } export function getFunctionName(fn) { const fnSrc = fn.toString(); // Pattern: /** @description ... */ const regexp = /\/\*\*\s*@description\s*([^*]*)\*\//; const match = regexp.exec(fnSrc); const result = match ? match[1] : undefined; return result === null || result === void 0 ? void 0 : result.trim(); } export function observableValue(nameOrOwner, initialValue) { if (typeof nameOrOwner === 'string') { return new ObservableValue(undefined, nameOrOwner, initialValue); } else { return new ObservableValue(nameOrOwner, undefined, initialValue); } } export class ObservableValue extends BaseObservable { get debugName() { var _a; return (_a = getDebugName(this, this._debugName, undefined, this._owner)) !== null && _a !== void 0 ? _a : 'ObservableValue'; } constructor(_owner, _debugName, initialValue) { super(); this._owner = _owner; this._debugName = _debugName; this._value = initialValue; } get() { return this._value; } set(value, tx, change) { var _a; if (this._value === value) { return; } let _tx; if (!tx) { tx = _tx = new TransactionImpl(() => { }, () => `Setting ${this.debugName}`); } try { const oldValue = this._value; this._setValue(value); (_a = getLogger()) === null || _a === void 0 ? void 0 : _a.handleObservableChanged(this, { oldValue, newValue: value, change, didChange: true, hadValue: true }); for (const observer of this.observers) { tx.updateObserver(observer, this); observer.handleChange(this, change); } } finally { if (_tx) { _tx.finish(); } } } toString() { return `${this.debugName}: ${this._value}`; } _setValue(newValue) { this._value = newValue; } } /** * A disposable observable. When disposed, its value is also disposed. * When a new value is set, the previous value is disposed. */ export function disposableObservableValue(nameOrOwner, initialValue) { if (typeof nameOrOwner === 'string') { return new DisposableObservableValue(undefined, nameOrOwner, initialValue); } else { return new DisposableObservableValue(nameOrOwner, undefined, initialValue); } } export class DisposableObservableValue extends ObservableValue { _setValue(newValue) { if (this._value === newValue) { return; } if (this._value) { this._value.dispose(); } this._value = newValue; } dispose() { var _a; (_a = this._value) === null || _a === void 0 ? void 0 : _a.dispose(); } }