diff --git a/LICENSE.md b/LICENSE.md new file mode 100644 index 0000000..53da587 --- /dev/null +++ b/LICENSE.md @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2020 Henning Dieterichs + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/data-extraction/README.md b/data-extraction/README.md new file mode 100644 index 0000000..218e407 --- /dev/null +++ b/data-extraction/README.md @@ -0,0 +1,126 @@ +# @hediet/debug-visualizer-data-extraction + +[![](https://img.shields.io/twitter/follow/hediet_dev.svg?style=social)](https://twitter.com/intent/follow?screen_name=hediet_dev) + +A library that helps implementing data extractors for the Debug Visualizer VS Code extension. + +# Installation + +Use the following command to install the library using yarn: + +``` +yarn add @hediet/debug-visualizer-data-extraction +``` + +# Usage + +## `createGraph` / `createGraphFromPointers` Helper + +```ts +import { + registerDefaultDataExtractors, + createGraphFromPointers, +} from "@hediet/debug-visualizer-data-extraction"; + +registerDefaultDataExtractors(); + +setTimeout(() => { + new Main().run(); +}, 0); + +class Main { + run() { + const list = new DoublyLinkedList("1"); + list.setNext(new DoublyLinkedList("2")); + list.next!.setNext(new DoublyLinkedList("3")); + list.next!.next!.setNext(new DoublyLinkedList("4")); + + // Watch `visualize()` with the Debug Visualizer Extension for VS Code! + const visualize = () => + // Returns `CommonDataTypes.Graph` data which can be visualized by + // either the vis.js or the graphviz visualizer. + createGraphFromPointers({ list, last, cur }, i => ({ + id: i.id, + label: i.name, + color: finished.has(i) ? "lime" : undefined, + edges: [ + { to: i.next!, label: "next" }, + { to: i.prev!, label: "prev", color: "lightgray" }, + ].filter(r => !!r.to), + })); + + const finished = new Set(); + var cur: DoublyLinkedList | undefined = list; + // Reverses `list`. Finished nodes have correct pointers, + // their next node is also finished. + var last: DoublyLinkedList | undefined = undefined; + while (cur) { + cur.prev = cur.next; + cur.next = last; + finished.add(cur); + last = cur; + cur = cur.prev; + } + console.log("finished"); + } +} + +let id = 0; +class DoublyLinkedList { + public readonly id = (id++).toString(); + constructor(public name: string) {} + + next: DoublyLinkedList | undefined; + prev: DoublyLinkedList | undefined; + + public setNext(val: DoublyLinkedList): void { + val.prev = this; + this.next = val; + } +} +``` + +![](../docs/doubly-linked-list-reverse-demo.gif) + +## Registering Custom Data Extractors + +```ts +import { + registerDefaultDataExtractors, + getDataExtractorApi, +} from "@hediet/debug-visualizer-data-extraction"; + +// The default data extractors are usually also registered by the VS Code extension. +// Registered them manually ensures that they are up to date. +registerDefaultDataExtractors(); + +getDataExtractorApi().registerExtractor({ + id: "my-foo-extractor", + getExtractions: (data, collector) => { + if (data instanceof Foo) { + collector.addExtraction({ + id: "my-foo-extraction", + name: "My Foo Extraction", + priority: 2000, + extractData: () => ({ kind: { text: true }, text: "Foo" }), + }); + } + }, +}); + +setTimeout(() => { + new Main().run(); +}, 0); + +class Foo {} + +class Main { + run() { + const f = new Foo(); + // if `f` is watched by the Debug Visualizer, + // `my-foo-extractor` will provide the data for the visualizers. + // See `CommonDataTypes` for data types that have built in visualizers. + debugger; + } +} +``` diff --git a/data-extraction/package.json b/data-extraction/package.json index a452653..385a5d8 100644 --- a/data-extraction/package.json +++ b/data-extraction/package.json @@ -25,5 +25,8 @@ "dev": "tsc --watch", "build": "tsc" }, - "dependencies": {} + "dependencies": {}, + "devDependencies": { + "typescript": "^" + } } diff --git a/data-extraction/src/CommonDataTypes.ts b/data-extraction/src/CommonDataTypes.ts index 0ba9c52..de21183 100644 --- a/data-extraction/src/CommonDataTypes.ts +++ b/data-extraction/src/CommonDataTypes.ts @@ -1,5 +1,14 @@ import { ExtractedData } from "./DataExtractionResult"; +export type CommonDataType = + | CommonDataTypes.Text + | CommonDataTypes.Svg + | CommonDataTypes.Html + | CommonDataTypes.DotGraph + | CommonDataTypes.Tree + | CommonDataTypes.Ast + | CommonDataTypes.Graph; + export function isCommonDataType( data: ExtractedData, kind: T @@ -14,15 +23,6 @@ export function isCommonDataType( type Narrow = T extends TKind ? T : never; -export type CommonDataType = - | CommonDataTypes.Text - | CommonDataTypes.Svg - | CommonDataTypes.Html - | CommonDataTypes.DotGraph - | CommonDataTypes.Tree - | CommonDataTypes.Ast - | CommonDataTypes.Graph; - export module CommonDataTypes { export interface Text { kind: { text: true }; diff --git a/data-extraction/src/DataExtractionResult.ts b/data-extraction/src/DataExtractionResult.ts index c9346b4..1493f7e 100644 --- a/data-extraction/src/DataExtractionResult.ts +++ b/data-extraction/src/DataExtractionResult.ts @@ -17,4 +17,4 @@ export type DataExtractorInfo = { export type DataExtractorId = { __brand: "DataExtractorId"; -}; +} & string; diff --git a/data-extraction/src/js/DataExtractor.ts b/data-extraction/src/js/DataExtractor.ts index 0ab6a90..24f2229 100644 --- a/data-extraction/src/js/DataExtractor.ts +++ b/data-extraction/src/js/DataExtractor.ts @@ -1,22 +1,45 @@ import { ExtractedData } from "../DataExtractionResult"; export interface DataExtractor { + /** + * Must be unique among all data extractors. + */ id: string; getExtractions( data: unknown, extractionCollector: ExtractionCollector, - evalFn: (expression: string) => TEval + context: DataExtractorContext ): void; } export interface ExtractionCollector { + /** + * Suggests a possible extraction. + */ addExtraction(extraction: DataExtraction): void; } +export interface DataExtractorContext { + /** + * Evaluates an expression in the context of the active stack frame. + */ + evalFn: (expression: string) => TEval; +} + export interface DataExtraction { - // the higher the better + /** + * Higher priorities are preferred. + */ priority: number; + + /** + * A unique id identifying this extraction among all extractions. + * Required to express extraction preferences. + */ id: string; + /** + * A user friendly name of this extraction. + */ name: string; extractData(): TData; } @@ -24,7 +47,7 @@ export interface DataExtraction { export function getExtractions( extractor: DataExtractor, data: unknown, - evalFn: (expression: string) => T + context: DataExtractorContext ): DataExtraction[] { const extractions = new Array>(); const collector: ExtractionCollector = { @@ -32,6 +55,6 @@ export function getExtractions( extractions.push(extraction); }, }; - extractor.getExtractions(data, collector, evalFn); + extractor.getExtractions(data, collector, context); return extractions; } diff --git a/data-extraction/src/js/DataExtractorApi.ts b/data-extraction/src/js/DataExtractorApi.ts index 60a3054..fc48455 100644 --- a/data-extraction/src/js/DataExtractorApi.ts +++ b/data-extraction/src/js/DataExtractorApi.ts @@ -9,6 +9,35 @@ import { ExtractionCollector, } from "./DataExtractor"; +export interface DataExtractorApi { + /** + * Registers a single extractor. + * @preferExisting if `true`, an existing extractor is not overwritten. + */ + registerExtractor( + extractor: DataExtractor, + preferExisting?: boolean + ): void; + + /** + * Registers multiple extractors. + * @preferExisting if `true`, an existing extractor is not overwritten. + */ + registerExtractors( + extractors: DataExtractor[], + preferExisting?: boolean + ): void; + + /** + * Extracts data from `value`. + */ + getData( + value: unknown, + evalFn: (expression: string) => T, + preferredDataExtractorId: string | undefined + ): JSONString; +} + export type DataResult = | { kind: "Data"; @@ -21,26 +50,18 @@ export interface JSONString extends String { __brand: { json: T }; } -export interface DataExtractorApi { - registerExtractor( - extractor: DataExtractor - ): void; - registerExtractors(extractors: DataExtractor[]): void; - getData( - value: unknown, - evalFn: (expression: string) => T, - preferredDataExtractorId: string | undefined - ): JSONString; -} - declare const window: any; +/** + * @internal + */ export function selfContainedInitDataExtractorApi(): boolean { - const obj = typeof window === "object" ? (window as any) : (global as any); + const globalObj = + typeof window === "object" ? (window as any) : (global as any); const key = "@hediet/data-extractor/v1"; const prefix = key + "::"; - let api: DataExtractorApi | undefined = obj[key]; + let api: DataExtractorApi | undefined = globalObj[key]; if (api) { return false; } @@ -49,29 +70,38 @@ export function selfContainedInitDataExtractorApi(): boolean { return JSON.stringify(data) as any; } function getExtractors(): DataExtractor[] { - return Object.entries(obj) + return Object.entries(globalObj) .filter(([key, value]) => key.startsWith(prefix)) .map(([key, value]) => value as DataExtractor); } - obj["$asData"] = (x: unknown) => x; - obj[key] = api = { - registerExtractor(extractor) { - obj[prefix + extractor.id] = extractor; + globalObj["$asData"] = (x: unknown) => x; + globalObj[key] = api = { + registerExtractor(extractor, preferExisting) { + const key = prefix + extractor.id; + if (preferExisting && key in globalObj) { + // don't overwrite existing data extractor + return; + } + globalObj[key] = extractor; }, - registerExtractors(extractors: DataExtractor[]) { + registerExtractors( + extractors: DataExtractor[], + preferExisting + ) { for (const e of extractors) { - this.registerExtractor(e); + this.registerExtractor(e, preferExisting); } }, getData(value, evalFn, preferredDataExtractorId) { const extractions = new Array>(); - const collector: ExtractionCollector = { + const extractionCollector: ExtractionCollector = { addExtraction(extraction) { extractions.push(extraction); }, }; + for (const e of getExtractors()) { - e.getExtractions(value, collector, evalFn); + e.getExtractions(value, extractionCollector, { evalFn }); } extractions.sort((a, b) => b.priority - a.priority); let usedExtraction = extractions[0]; @@ -111,10 +141,10 @@ export function selfContainedInitDataExtractorApi(): boolean { }; api.registerExtractor({ - id: "toString", - getExtractions(data, collector) { + id: "to-string", + getExtractions: (data, collector) => { collector.addExtraction({ - id: "toString", + id: "to-string", name: "To String", priority: 100, extractData() { @@ -131,8 +161,15 @@ export function selfContainedInitDataExtractorApi(): boolean { return true; } +/** + * This code is used to detect if the API has not been initialized yet. + * @internal + */ export const ApiHasNotBeenInitializedCode = "EgH0cybXij1jYUozyakO" as const; +/** + * @internal + */ export function selfContainedGetInitializedDataExtractorApi(): DataExtractorApi { const obj = typeof window === "object" ? (window as any) : (global as any); const key = "@hediet/data-extractor/v1"; @@ -147,6 +184,9 @@ export function selfContainedGetInitializedDataExtractorApi(): DataExtractorApi return api; } +/** + * Get the data extractor API. + */ export function getDataExtractorApi(): DataExtractorApi { selfContainedInitDataExtractorApi(); return selfContainedGetInitializedDataExtractorApi(); diff --git a/data-extraction/src/js/Extractors/AsIsDataExtractor.ts b/data-extraction/src/js/Extractors/AsIsDataExtractor.ts index eb2b0dd..f71bd43 100644 --- a/data-extraction/src/js/Extractors/AsIsDataExtractor.ts +++ b/data-extraction/src/js/Extractors/AsIsDataExtractor.ts @@ -1,12 +1,12 @@ import { ExtractedData } from "../../DataExtractionResult"; -import { DataExtractor, ExtractionCollector } from ".."; +import { DataExtractor, ExtractionCollector, DataExtractorContext } from ".."; export class AsIsDataExtractor implements DataExtractor { readonly id = "as-is"; getExtractions( data: unknown, extractionCollector: ExtractionCollector, - evalFn: (expression: string) => TEval + context: DataExtractorContext ): void { if (typeof data !== "object" || !data || !("kind" in data)) { return; diff --git a/data-extraction/src/js/Extractors/GetDebugVisualizationDataExtractor.ts b/data-extraction/src/js/Extractors/GetDebugVisualizationDataExtractor.ts index 9f93553..ecc187e 100644 --- a/data-extraction/src/js/Extractors/GetDebugVisualizationDataExtractor.ts +++ b/data-extraction/src/js/Extractors/GetDebugVisualizationDataExtractor.ts @@ -1,13 +1,17 @@ import { ExtractedData } from "../../DataExtractionResult"; -import { DataExtractor, ExtractionCollector } from ".."; +import { + ExtractionCollector, + DataExtractor, + DataExtractorContext, +} from "../DataExtractor"; export class GetDebugVisualizationDataExtractor implements DataExtractor { readonly id = "get-debug-visualization"; getExtractions( data: unknown, - extractionCollector: ExtractionCollector, - evalFn: (expression: string) => TEval + collector: ExtractionCollector, + context: DataExtractorContext ): void { if ( typeof data !== "object" || @@ -17,10 +21,10 @@ export class GetDebugVisualizationDataExtractor return; } - extractionCollector.addExtraction({ + collector.addExtraction({ id: this.id, name: "Use Method 'getDebugVisualization'", - priority: 500, + priority: 600, extractData() { return (data as any).getDebugVisualization(); }, diff --git a/data-extraction/src/js/Extractors/TypeScriptDataExtractors.ts b/data-extraction/src/js/Extractors/TypeScriptDataExtractors.ts index c44bd48..1171daf 100644 --- a/data-extraction/src/js/Extractors/TypeScriptDataExtractors.ts +++ b/data-extraction/src/js/Extractors/TypeScriptDataExtractors.ts @@ -1,16 +1,20 @@ import * as ts from "typescript"; -import { DataExtractor, ExtractionCollector } from "../DataExtractor"; +import { + DataExtractor, + ExtractionCollector, + DataExtractorContext, +} from "../DataExtractor"; import { CommonDataTypes } from "../../CommonDataTypes"; // This class is self contained and can be injected into both nodejs and browser environments. export class TypeScriptAstDataExtractor implements DataExtractor { - readonly id = "TypeScriptAst"; + readonly id = "typescript-ast"; getExtractions( data: unknown, collector: ExtractionCollector, - evalFn: (expression: string) => TEval + { evalFn }: DataExtractorContext ): void { if (!data) { return; diff --git a/data-extraction/src/js/Extractors/index.ts b/data-extraction/src/js/Extractors/index.ts index 962be70..65c64f8 100644 --- a/data-extraction/src/js/Extractors/index.ts +++ b/data-extraction/src/js/Extractors/index.ts @@ -1,21 +1,5 @@ -import { getDataExtractorApi } from "../DataExtractorApi"; -import { TypeScriptAstDataExtractor } from "./TypeScriptDataExtractors"; -import { AsIsDataExtractor } from "./AsIsDataExtractor"; -import { GetDebugVisualizationDataExtractor } from "./GetDebugVisualizationDataExtractor"; +export { TypeScriptAstDataExtractor } from "./TypeScriptDataExtractors"; +export { AsIsDataExtractor } from "./AsIsDataExtractor"; +export { GetDebugVisualizationDataExtractor } from "./GetDebugVisualizationDataExtractor"; -export { - AsIsDataExtractor, - TypeScriptAstDataExtractor, - GetDebugVisualizationDataExtractor, -}; - -export function registerAll() { - const api = getDataExtractorApi(); - for (const item of [ - new TypeScriptAstDataExtractor(), - new AsIsDataExtractor(), - new GetDebugVisualizationDataExtractor(), - ]) { - api.registerExtractor(item); - } -} +export { registerDefaultDataExtractors } from "./registerDefaultDataExtractors"; diff --git a/data-extraction/src/js/Extractors/registerDefaultDataExtractors.ts b/data-extraction/src/js/Extractors/registerDefaultDataExtractors.ts new file mode 100644 index 0000000..40c6e86 --- /dev/null +++ b/data-extraction/src/js/Extractors/registerDefaultDataExtractors.ts @@ -0,0 +1,19 @@ +import { getDataExtractorApi } from "../DataExtractorApi"; +import { TypeScriptAstDataExtractor } from "./TypeScriptDataExtractors"; +import { AsIsDataExtractor } from "./AsIsDataExtractor"; +import { GetDebugVisualizationDataExtractor } from "./GetDebugVisualizationDataExtractor"; + +/** + * The default data extractors should be registered by vs code anyways. + * This however ensures that the most up to date extractors are registered. + */ +export function registerDefaultDataExtractors() { + const api = getDataExtractorApi(); + for (const item of [ + new TypeScriptAstDataExtractor(), + new AsIsDataExtractor(), + new GetDebugVisualizationDataExtractor(), + ]) { + api.registerExtractor(item); + } +} diff --git a/data-extraction/src/js/createGraph.ts b/data-extraction/src/js/createGraph.ts index 8a824fb..153033c 100644 --- a/data-extraction/src/js/createGraph.ts +++ b/data-extraction/src/js/createGraph.ts @@ -4,47 +4,10 @@ import { NodeGraphData, } from "../CommonDataTypes"; -export function createGraphFromPointers( - roots: Record, - infoSelector: ( - item: T - ) => { - id?: string | number; - edges: ({ to: T } & Omit)[]; - } & Omit -): CommonDataTypes.Graph { - const marker = {}; - - interface Pointer { - marker: {}; - name: string; - value: T | null | undefined; - } - - const items = Object.entries(roots).map(([name, value]) => ({ - marker, - name, - value, - })); - - const includeLabelInName = false; - - return createGraph(items, item => { - if ("marker" in item && item["marker"] === marker) { - return { - id: "label____" + item.name, - color: "orange", - label: item.name, - edges: [{ to: item.value!, color: "orange", label: "" }].filter( - t => !!t.to - ), - }; - } else { - return infoSelector(item as T); - } - }); -} - +/** + * Given a list of roots, it creates a graph by following their edges recursively. + * Tracks cycles. + */ export function createGraph( roots: T[], infoSelector: ( @@ -103,3 +66,46 @@ export function createGraph( } return r; } + +/** + * Given a labeled list of roots, it creates a graph by following their edges recursively. + * Tracks cycles. + */ +export function createGraphFromPointers( + roots: Record, + infoSelector: ( + item: T + ) => { + id?: string | number; + edges: ({ to: T } & Omit)[]; + } & Omit +): CommonDataTypes.Graph { + const marker = {}; + + interface Pointer { + marker: {}; + name: string; + value: T | null | undefined; + } + + const items = Object.entries(roots).map(([name, value]) => ({ + marker, + name, + value, + })); + + return createGraph(items, item => { + if ("marker" in item && item["marker"] === marker) { + return { + id: "label____" + item.name, + color: "orange", + label: item.name, + edges: [{ to: item.value!, color: "orange", label: "" }].filter( + t => !!t.to + ), + }; + } else { + return infoSelector(item as T); + } + }); +} diff --git a/example-project/src/demo_custom-data-extractor.ts b/example-project/src/demo_custom-data-extractor.ts new file mode 100644 index 0000000..251f5ae --- /dev/null +++ b/example-project/src/demo_custom-data-extractor.ts @@ -0,0 +1,34 @@ +import { + registerDefaultDataExtractors, + getDataExtractorApi, +} from "@hediet/debug-visualizer-data-extraction"; + +// Registers all existing extractors. +registerDefaultDataExtractors(); + +getDataExtractorApi().registerExtractor({ + id: "my-extractor", + getExtractions: (data, collector) => { + if (data instanceof Foo) { + collector.addExtraction({ + id: "my-extractor", + name: "My Extractor", + priority: 2000, + extractData: () => ({ kind: { text: true }, text: "Foo" }), + }); + } + }, +}); + +setTimeout(() => { + new Main().run(); +}, 0); + +class Foo {} + +class Main { + run() { + const f = new Foo(); + debugger; + } +} diff --git a/example-project/src/demo_doubly-linked-list.ts b/example-project/src/demo_doubly-linked-list.ts index 13145d7..6c41a68 100644 --- a/example-project/src/demo_doubly-linked-list.ts +++ b/example-project/src/demo_doubly-linked-list.ts @@ -1,10 +1,9 @@ import { - registerAll, - CommonDataTypes, + registerDefaultDataExtractors, createGraphFromPointers, } from "@hediet/debug-visualizer-data-extraction"; -registerAll(); +registerDefaultDataExtractors(); setTimeout(() => { new Main().run(); diff --git a/example-project/src/demo_typescript.ts b/example-project/src/demo_typescript.ts index e7c98ea..c848083 100644 --- a/example-project/src/demo_typescript.ts +++ b/example-project/src/demo_typescript.ts @@ -1,8 +1,8 @@ import * as ts from "typescript"; -import { registerAll } from "@hediet/debug-visualizer-data-extraction"; +import { registerDefaultDataExtractors } from "@hediet/debug-visualizer-data-extraction"; import { MockLanguageServiceHost } from "./MockLanguageServiceHost"; -registerAll(); +registerDefaultDataExtractors(); setTimeout(() => { new Main().run(); diff --git a/extension/CHANGELOG.md b/extension/CHANGELOG.md index e9a4642..80e43a2 100644 --- a/extension/CHANGELOG.md +++ b/extension/CHANGELOG.md @@ -3,3 +3,8 @@ ## 0.9.0 - Initial release + +## Current + +- Minor refactoring of `@hediet/debug-visualizer-data-extraction`'s API +- Improved documentation. diff --git a/extension/README.md b/extension/README.md index 5cf6664..d4c63d9 100644 --- a/extension/README.md +++ b/extension/README.md @@ -9,12 +9,15 @@ A VS Code extension for visualizing data structures during debugging. ## Usage After installing this extension, use the command `Open a new Debug Visualizer View` to open a new visualizer view. -In this view you can enter an expression that is visualized when debugging your application, e.g. +In this view you can enter an expression that is evaluated and visualized while stepping through your application, e.g. ```ts { kind: { graph: true }, nodes: [ { id: "1", label: "1" }, { id: "2", label: "2" } ], edges: [{ from: "1", to: "2", label: "edge" }]} ``` +You can implement your own functions to extract this debug data from your custom data structures. +See [here](../data-extraction/README.md) for documentation of the `createGraphFromPointers` helper. + ## Integrated Visualizers Visualizers present data extracted by a _Data Extractor_. @@ -159,6 +162,11 @@ Calls `.getDebugVisualization()` on values and treats the result as direct input Currently, only JavaScript (and thus TypeScript) values can be visualized and only a few visualizations are supported. The architecture is solid enough to support other languages in the future. +# `@hediet/debug-visualizer-data-extraction` + +A library that provides infrastructure to implement and register custom data extractors. +See [README](../data-extraction/README.md) of the library for more info. + # See Also This extension works very well together with my library [`@hediet/node-reload`](https://github.com/hediet/node-reload). diff --git a/extension/src/DataSource/JsDebuggerSource.ts b/extension/src/DataSource/JsDebuggerSource.ts index bcd536e..42ce1e4 100644 --- a/extension/src/DataSource/JsDebuggerSource.ts +++ b/extension/src/DataSource/JsDebuggerSource.ts @@ -3,8 +3,7 @@ import { EvaluationWatcher, EvaluationWatcherOptions, } from "./DataSource"; -import { observable, autorun } from "mobx"; -import * as vscode from "vscode"; +import { observable, autorun, action } from "mobx"; import { Disposable } from "@hediet/std/disposable"; import { selfContainedGetInitializedDataExtractorApi, @@ -156,7 +155,8 @@ export class JsDebuggerSourceImplementation implements JsDataSource { GetDebugVisualizationDataExtractor, ].map(e => `new (${e.toString()})()`); expression += `(${selfContainedGetInitializedDataExtractorApi.toString()})()`; - expression += `.registerExtractors([${es.join(",")}])`; + // prefer existing is true, so that manually registered extractors are not overwritten. + expression += `.registerExtractors([${es.join(",")}], true)`; const reply = await session.evaluate({ expression, @@ -182,12 +182,13 @@ class ObservableEvaluationWatcher implements EvaluationWatcher { } @observable - _preferredDataExtractor: DataExtractorId | undefined = undefined; + private _preferredDataExtractor: DataExtractorId | undefined = undefined; - get preferredDataExtractor(): DataExtractorId | undefined { + public get preferredDataExtractor(): DataExtractorId | undefined { return this._preferredDataExtractor; } + @action public setPreferredDataExtractor(id: DataExtractorId | undefined): void { this._preferredDataExtractor = id; this.refresh();