monaco-editor-core
Version:
A browser based code editor
440 lines • 18.2 kB
JavaScript
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { AutorunObserver } from '../../reactions/autorunImpl.js';
import { formatValue } from '../consoleObservableLogger.js';
import { registerDebugChannel } from './debuggerRpc.js';
import { deepAssign, deepAssignDeleteNulls, Throttler } from './utils.js';
import { isDefined } from '../../../types.js';
import { FromEventObservable } from '../../observables/observableFromEvent.js';
import { BugIndicatingError, onUnexpectedError } from '../../../errors.js';
import { Derived } from '../../observables/derivedImpl.js';
import { ObservableValue } from '../../observables/observableValue.js';
import { DebugLocation } from '../../debugLocation.js';
export class DevToolsLogger {
static { this._instance = undefined; }
static getInstance() {
if (DevToolsLogger._instance === undefined) {
DevToolsLogger._instance = new DevToolsLogger();
}
return DevToolsLogger._instance;
}
getTransactionState() {
const affected = [];
const txs = [...this._activeTransactions];
if (txs.length === 0) {
return undefined;
}
const observerQueue = txs.flatMap(t => t.debugGetUpdatingObservers() ?? []).map(o => o.observer);
const processedObservers = new Set();
while (observerQueue.length > 0) {
const observer = observerQueue.shift();
if (processedObservers.has(observer)) {
continue;
}
processedObservers.add(observer);
const state = this._getInfo(observer, d => {
if (!processedObservers.has(d)) {
observerQueue.push(d);
}
});
if (state) {
affected.push(state);
}
}
return { names: txs.map(t => t.getDebugName() ?? 'tx'), affected };
}
_getObservableInfo(observable) {
const info = this._instanceInfos.get(observable);
if (!info) {
onUnexpectedError(new BugIndicatingError('No info found'));
return undefined;
}
return info;
}
_getAutorunInfo(autorun) {
const info = this._instanceInfos.get(autorun);
if (!info) {
onUnexpectedError(new BugIndicatingError('No info found'));
return undefined;
}
return info;
}
_getInfo(observer, queue) {
if (observer instanceof Derived) {
const observersToUpdate = [...observer.debugGetObservers()];
for (const o of observersToUpdate) {
queue(o);
}
const info = this._getObservableInfo(observer);
if (!info) {
return;
}
const observerState = observer.debugGetState();
const base = { name: observer.debugName, instanceId: info.instanceId, updateCount: observerState.updateCount };
const changedDependencies = [...info.changedObservables].map(o => this._instanceInfos.get(o)?.instanceId).filter(isDefined);
if (observerState.isComputing) {
return { ...base, type: 'observable/derived', state: 'updating', changedDependencies, initialComputation: false };
}
switch (observerState.state) {
case 0 /* DerivedState.initial */:
return { ...base, type: 'observable/derived', state: 'noValue' };
case 3 /* DerivedState.upToDate */:
return { ...base, type: 'observable/derived', state: 'upToDate' };
case 2 /* DerivedState.stale */:
return { ...base, type: 'observable/derived', state: 'stale', changedDependencies };
case 1 /* DerivedState.dependenciesMightHaveChanged */:
return { ...base, type: 'observable/derived', state: 'possiblyStale' };
}
}
else if (observer instanceof AutorunObserver) {
const info = this._getAutorunInfo(observer);
if (!info) {
return undefined;
}
const base = { name: observer.debugName, instanceId: info.instanceId, updateCount: info.updateCount };
const changedDependencies = [...info.changedObservables].map(o => this._instanceInfos.get(o).instanceId);
if (observer.debugGetState().isRunning) {
return { ...base, type: 'autorun', state: 'updating', changedDependencies };
}
switch (observer.debugGetState().state) {
case 3 /* AutorunState.upToDate */:
return { ...base, type: 'autorun', state: 'upToDate' };
case 2 /* AutorunState.stale */:
return { ...base, type: 'autorun', state: 'stale', changedDependencies };
case 1 /* AutorunState.dependenciesMightHaveChanged */:
return { ...base, type: 'autorun', state: 'possiblyStale' };
}
}
return undefined;
}
_formatObservable(obs) {
const info = this._getObservableInfo(obs);
if (!info) {
return undefined;
}
return { name: obs.debugName, instanceId: info.instanceId };
}
_formatObserver(obs) {
if (obs instanceof Derived) {
return { name: obs.toString(), instanceId: this._getObservableInfo(obs)?.instanceId };
}
const autorunInfo = this._getAutorunInfo(obs);
if (autorunInfo) {
return { name: obs.toString(), instanceId: autorunInfo.instanceId };
}
return undefined;
}
constructor() {
this._declarationId = 0;
this._instanceId = 0;
this._declarations = new Map();
this._instanceInfos = new WeakMap();
this._aliveInstances = new Map();
this._activeTransactions = new Set();
this._channel = registerDebugChannel('observableDevTools', () => {
return {
notifications: {
setDeclarationIdFilter: declarationIds => {
},
logObservableValue: (observableId) => {
console.log('logObservableValue', observableId);
},
flushUpdates: () => {
this._flushUpdates();
},
resetUpdates: () => {
this._pendingChanges = null;
this._channel.api.notifications.handleChange(this._fullState, true);
},
},
requests: {
getDeclarations: () => {
const result = {};
for (const decl of this._declarations.values()) {
result[decl.id] = decl;
}
return { decls: result };
},
getSummarizedInstances: () => {
return null;
},
getObservableValueInfo: instanceId => {
const obs = this._aliveInstances.get(instanceId);
return {
observers: [...obs.debugGetObservers()].map(d => this._formatObserver(d)).filter(isDefined),
};
},
getDerivedInfo: instanceId => {
const d = this._aliveInstances.get(instanceId);
return {
dependencies: [...d.debugGetState().dependencies].map(d => this._formatObservable(d)).filter(isDefined),
observers: [...d.debugGetObservers()].map(d => this._formatObserver(d)).filter(isDefined),
};
},
getAutorunInfo: instanceId => {
const obs = this._aliveInstances.get(instanceId);
return {
dependencies: [...obs.debugGetState().dependencies].map(d => this._formatObservable(d)).filter(isDefined),
};
},
getTransactionState: () => {
return this.getTransactionState();
},
setValue: (instanceId, jsonValue) => {
const obs = this._aliveInstances.get(instanceId);
if (obs instanceof Derived) {
obs.debugSetValue(jsonValue);
}
else if (obs instanceof ObservableValue) {
obs.debugSetValue(jsonValue);
}
else if (obs instanceof FromEventObservable) {
obs.debugSetValue(jsonValue);
}
else {
throw new BugIndicatingError('Observable is not supported');
}
const observers = [...obs.debugGetObservers()];
for (const d of observers) {
d.beginUpdate(obs);
}
for (const d of observers) {
d.handleChange(obs, undefined);
}
for (const d of observers) {
d.endUpdate(obs);
}
},
getValue: instanceId => {
const obs = this._aliveInstances.get(instanceId);
if (obs instanceof Derived) {
return formatValue(obs.debugGetState().value, 200);
}
else if (obs instanceof ObservableValue) {
return formatValue(obs.debugGetState().value, 200);
}
return undefined;
},
logValue: (instanceId) => {
const obs = this._aliveInstances.get(instanceId);
if (obs && 'get' in obs) {
console.log('Logged Value:', obs.get());
}
else {
throw new BugIndicatingError('Observable is not supported');
}
},
rerun: (instanceId) => {
const obs = this._aliveInstances.get(instanceId);
if (obs instanceof Derived) {
obs.debugRecompute();
}
else if (obs instanceof AutorunObserver) {
obs.debugRerun();
}
else {
throw new BugIndicatingError('Observable is not supported');
}
},
}
};
});
this._pendingChanges = null;
this._changeThrottler = new Throttler();
this._fullState = {};
this._flushUpdates = () => {
if (this._pendingChanges !== null) {
this._channel.api.notifications.handleChange(this._pendingChanges, false);
this._pendingChanges = null;
}
};
DebugLocation.enable();
}
_handleChange(update) {
deepAssignDeleteNulls(this._fullState, update);
if (this._pendingChanges === null) {
this._pendingChanges = update;
}
else {
deepAssign(this._pendingChanges, update);
}
this._changeThrottler.throttle(this._flushUpdates, 10);
}
_getDeclarationId(type, location) {
if (!location) {
return -1;
}
let decInfo = this._declarations.get(location.id);
if (decInfo === undefined) {
decInfo = {
id: this._declarationId++,
type,
url: location.fileName,
line: location.line,
column: location.column,
};
this._declarations.set(location.id, decInfo);
this._handleChange({ decls: { [decInfo.id]: decInfo } });
}
return decInfo.id;
}
handleObservableCreated(observable, location) {
const declarationId = this._getDeclarationId('observable/value', location);
const info = {
declarationId,
instanceId: this._instanceId++,
listenerCount: 0,
lastValue: undefined,
updateCount: 0,
changedObservables: new Set(),
};
this._instanceInfos.set(observable, info);
}
handleOnListenerCountChanged(observable, newCount) {
const info = this._getObservableInfo(observable);
if (!info) {
return;
}
if (info.listenerCount === 0 && newCount > 0) {
const type = observable instanceof Derived ? 'observable/derived' : 'observable/value';
this._aliveInstances.set(info.instanceId, observable);
this._handleChange({
instances: {
[info.instanceId]: {
instanceId: info.instanceId,
declarationId: info.declarationId,
formattedValue: info.lastValue,
type,
name: observable.debugName,
}
}
});
}
else if (info.listenerCount > 0 && newCount === 0) {
this._handleChange({
instances: { [info.instanceId]: null }
});
this._aliveInstances.delete(info.instanceId);
}
info.listenerCount = newCount;
}
handleObservableUpdated(observable, changeInfo) {
if (observable instanceof Derived) {
this._handleDerivedRecomputed(observable, changeInfo);
return;
}
const info = this._getObservableInfo(observable);
if (info) {
if (changeInfo.didChange) {
info.lastValue = formatValue(changeInfo.newValue, 30);
if (info.listenerCount > 0) {
this._handleChange({
instances: { [info.instanceId]: { formattedValue: info.lastValue } }
});
}
}
}
}
handleAutorunCreated(autorun, location) {
const declarationId = this._getDeclarationId('autorun', location);
const info = {
declarationId,
instanceId: this._instanceId++,
updateCount: 0,
changedObservables: new Set(),
};
this._instanceInfos.set(autorun, info);
this._aliveInstances.set(info.instanceId, autorun);
if (info) {
this._handleChange({
instances: {
[info.instanceId]: {
instanceId: info.instanceId,
declarationId: info.declarationId,
runCount: 0,
type: 'autorun',
name: autorun.debugName,
}
}
});
}
}
handleAutorunDisposed(autorun) {
const info = this._getAutorunInfo(autorun);
if (!info) {
return;
}
this._handleChange({
instances: { [info.instanceId]: null }
});
this._instanceInfos.delete(autorun);
this._aliveInstances.delete(info.instanceId);
}
handleAutorunDependencyChanged(autorun, observable, change) {
const info = this._getAutorunInfo(autorun);
if (!info) {
return;
}
info.changedObservables.add(observable);
}
handleAutorunStarted(autorun) {
}
handleAutorunFinished(autorun) {
const info = this._getAutorunInfo(autorun);
if (!info) {
return;
}
info.changedObservables.clear();
info.updateCount++;
this._handleChange({
instances: { [info.instanceId]: { runCount: info.updateCount } }
});
}
handleDerivedDependencyChanged(derived, observable, change) {
const info = this._getObservableInfo(derived);
if (info) {
info.changedObservables.add(observable);
}
}
_handleDerivedRecomputed(observable, changeInfo) {
const info = this._getObservableInfo(observable);
if (!info) {
return;
}
const formattedValue = formatValue(changeInfo.newValue, 30);
info.updateCount++;
info.changedObservables.clear();
info.lastValue = formattedValue;
if (info.listenerCount > 0) {
this._handleChange({
instances: { [info.instanceId]: { formattedValue: formattedValue, recomputationCount: info.updateCount } }
});
}
}
handleDerivedCleared(observable) {
const info = this._getObservableInfo(observable);
if (!info) {
return;
}
info.lastValue = undefined;
info.changedObservables.clear();
if (info.listenerCount > 0) {
this._handleChange({
instances: {
[info.instanceId]: {
formattedValue: undefined,
}
}
});
}
}
handleBeginTransaction(transaction) {
this._activeTransactions.add(transaction);
}
handleEndTransaction(transaction) {
this._activeTransactions.delete(transaction);
}
}
//# sourceMappingURL=devToolsLogger.js.map