11 Commits

Author SHA1 Message Date
Henning Dieterichs
9fb1e8127a v2.3.0 2022-03-09 13:48:08 +01:00
Henning Dieterichs
a2a5d1b873 Update changelog for 2.2.4 2022-03-09 13:46:05 +01:00
Henning Dieterichs
e7e76ccfb9 Update dependencies. 2022-03-09 13:45:59 +01:00
Henning Dieterichs
e01c09eac5 Merge pull request #122 from fehrlich/master
python support
2021-08-19 11:06:32 +02:00
fehrlich
86e71ae534 remove replaceA
ll
2021-08-18 13:06:08 +02:00
fehrlich
e46cdef402 minor 2021-08-18 12:52:47 +02:00
fehrlich
2af8ef9a53 some cleanup 2021-08-18 12:20:11 +02:00
fehrlich
aa3412e6ac init python support 2021-08-18 11:32:21 +02:00
Henning Dieterichs
6543180591 Version 2.2.4. 2021-06-22 10:05:03 +02:00
Henning Dieterichs
cd88168211 Fixes issues with global. 2021-06-22 09:59:50 +02:00
Henning Dieterichs
683f1e1f76 Updates readme. 2021-06-18 21:12:30 +02:00
27 changed files with 681 additions and 293 deletions

7
.gitignore vendored
View File

@@ -80,4 +80,9 @@ typings/
.dynamodb/ .dynamodb/
dist/ dist/
out/ out/
# Python byte-compiled / optimized / DLL files
__pycache__/
*.py[cod]
*$py.class

View File

@@ -228,6 +228,16 @@ export type PlotlyVisualizationData = {
text?: string | string[]; text?: string | string[];
xaxis?: string; xaxis?: string;
yaxis?: string; yaxis?: string;
cells?: {
values?: string[][];
};
header?: {
values?: string[];
};
domain?: {
x?: number[],
y?: number[],
};
x?: (string | number | null)[] | (string | number | null)[][]; x?: (string | number | null)[] | (string | number | null)[][];
y?: (string | number | null)[] | (string | number | null)[][]; y?: (string | number | null)[] | (string | number | null)[][];
z?: (string | number | null)[] | (string | number | null)[][]; z?: (string | number | null)[] | (string | number | null)[][];
@@ -257,7 +267,8 @@ export type PlotlyVisualizationData = {
| "waterfall" | "waterfall"
| "funnel" | "funnel"
| "funnelarea" | "funnelarea"
| "scattermapbox"; | "scattermapbox"
| "table";
mode?: mode?:
| "lines" | "lines"
| "markers" | "markers"

View File

@@ -0,0 +1,11 @@
export function getGlobal(): any {
if (typeof globalThis === "object") {
return globalThis;
} else if (typeof global === "object") {
return global;
} else if (typeof window === "object") {
return window;
}
throw new Error("No global available");
}

View File

@@ -2,6 +2,7 @@ import {
VisualizationData, VisualizationData,
DataExtractionResult, DataExtractionResult,
} from "../../DataExtractionResult"; } from "../../DataExtractionResult";
import { LoadDataExtractorsFn } from "./LoadDataExtractorsFn";
export interface DataExtractorApi { export interface DataExtractorApi {
/** /**
@@ -31,6 +32,9 @@ export interface DataExtractorApi {
* @preferExisting if `true`, existing extractors with the same id are not overwritten. * @preferExisting if `true`, existing extractors with the same id are not overwritten.
*/ */
registerDefaultExtractors(preferExisting?: boolean): void; registerDefaultExtractors(preferExisting?: boolean): void;
registerDataExtractorsSource(id: string, fn: LoadDataExtractorsFn): void;
unregisterDataExtractorsSource(id: string): void;
} }
export type DataResult = export type DataResult =
@@ -70,7 +74,7 @@ export interface DataExtractorContext {
*/ */
evalFn: <TEval>(expression: string) => TEval; evalFn: <TEval>(expression: string) => TEval;
variablesInScope: Record<string, unknown>; variablesInScope: Record<string, () => unknown>;
} }
export interface DataExtraction { export interface DataExtraction {

View File

@@ -5,20 +5,21 @@ import {
DataExtractor, DataExtractor,
DataExtraction, DataExtraction,
ExtractionCollector, ExtractionCollector,
DataExtractorContext,
} from "./DataExtractorApi"; } from "./DataExtractorApi";
import { DataExtractorInfo } from "../../DataExtractionResult"; import { DataExtractorInfo } from "../../DataExtractionResult";
import { registerDefaultExtractors } from "./default-extractors"; import { registerDefaultExtractors } from "./default-extractors";
import { LoadDataExtractorsFn } from "./LoadDataExtractorsFn";
import * as helpers from "../helpers";
/** /**
* @internal * @internal
*/ */
export class DataExtractorApiImpl implements DataExtractorApi { export class DataExtractorApiImpl implements DataExtractorApi {
public static lastEvalFn: (<T>(expression: string) => T) | undefined; public static lastContext: DataExtractorContext | undefined = undefined;
public static lastVariablesInScope:
| Record<string, unknown>
| undefined = undefined;
private readonly extractors = new Map<string, DataExtractor>(); private readonly extractors = new Map<string, DataExtractor>();
private readonly extractorSources = new Map<string, LoadDataExtractorsFn>();
private toJson<TData>(data: TData): JSONString<TData> { private toJson<TData>(data: TData): JSONString<TData> {
return JSON.stringify(data) as any; return JSON.stringify(data) as any;
@@ -38,7 +39,7 @@ export class DataExtractorApiImpl implements DataExtractorApi {
valueFn: () => unknown, valueFn: () => unknown,
evalFn: <T>(expression: string) => T, evalFn: <T>(expression: string) => T,
preferredDataExtractorId: string | undefined, preferredDataExtractorId: string | undefined,
variablesInScope: Record<string, unknown> variablesInScope: Record<string, () => unknown>
): JSONString<DataResult> { ): JSONString<DataResult> {
const extractions = new Array<DataExtraction>(); const extractions = new Array<DataExtraction>();
const extractionCollector: ExtractionCollector = { const extractionCollector: ExtractionCollector = {
@@ -47,19 +48,27 @@ export class DataExtractorApiImpl implements DataExtractorApi {
}, },
}; };
DataExtractorApiImpl.lastVariablesInScope = variablesInScope; const context: DataExtractorContext = {
DataExtractorApiImpl.lastEvalFn = evalFn; evalFn,
variablesInScope,
};
DataExtractorApiImpl.lastContext = context;
const value = valueFn(); const value = valueFn();
for (const e of this.extractors.values()) { const extractors = new Array<DataExtractor>();
e.getExtractions(value, extractionCollector, {
evalFn, for (const fn of this.extractorSources.values()) {
variablesInScope, fn((extractor) => {
}); extractors.push(extractor);
}, helpers);
} }
DataExtractorApiImpl.lastVariablesInScope = undefined; for (const e of [...this.extractors.values(), ...extractors]) {
DataExtractorApiImpl.lastEvalFn = undefined; e.getExtractions(value, extractionCollector, context);
}
DataExtractorApiImpl.lastContext = undefined;
extractions.sort((a, b) => b.priority - a.priority); extractions.sort((a, b) => b.priority - a.priority);
let usedExtraction = extractions[0]; let usedExtraction = extractions[0];
@@ -69,7 +78,7 @@ export class DataExtractorApiImpl implements DataExtractorApi {
if (preferredDataExtractorId) { if (preferredDataExtractorId) {
const preferred = extractions.find( const preferred = extractions.find(
e => e.id === preferredDataExtractorId (e) => e.id === preferredDataExtractorId
); );
if (preferred) { if (preferred) {
usedExtraction = preferred; usedExtraction = preferred;
@@ -99,4 +108,15 @@ export class DataExtractorApiImpl implements DataExtractorApi {
// TODO consider preferExisting // TODO consider preferExisting
registerDefaultExtractors(this); registerDefaultExtractors(this);
} }
public registerDataExtractorsSource(
id: string,
fn: LoadDataExtractorsFn
): void {
this.extractorSources.set(id, fn);
}
public unregisterDataExtractorsSource(id: string): void {
this.extractorSources.delete(id);
}
} }

View File

@@ -1,34 +0,0 @@
import {
DataExtractor,
ExtractionCollector,
DataExtractorContext,
LoadDataExtractorsFn,
} from "..";
import * as helpers from "../../helpers";
export class InjectedExtractor implements DataExtractor {
readonly id = "injected-extractor";
getExtractions(
data: unknown,
extractionCollector: ExtractionCollector,
context: DataExtractorContext
): void {
const key = "@hediet/debug-visualizer/injectedScripts";
const injectedExtractorsFns = Object.values(
(global as any)[key]
) as LoadDataExtractorsFn[];
const extractors = new Array<DataExtractor>();
for (const fn of injectedExtractorsFns) {
fn(extractor => {
extractors.push(extractor);
}, helpers);
}
for (const extractor of extractors) {
extractor.getExtractions(data, extractionCollector, context);
}
}
}

View File

@@ -9,7 +9,6 @@ import { getDataExtractorApi } from "../injection";
import { GridExtractor } from "./GridExtractor"; import { GridExtractor } from "./GridExtractor";
import { TableDataExtractor } from "./TableExtractor"; import { TableDataExtractor } from "./TableExtractor";
import { StringDiffExtractor } from "./StringDiffExtractor"; import { StringDiffExtractor } from "./StringDiffExtractor";
import { InjectedExtractor } from "./InjectedExtractor";
/** /**
* The default data extractors should be registered by VS Code automatically. * The default data extractors should be registered by VS Code automatically.
@@ -28,7 +27,6 @@ export function registerDefaultExtractors(
new GridExtractor(), new GridExtractor(),
new TableDataExtractor(), new TableDataExtractor(),
new StringDiffExtractor(), new StringDiffExtractor(),
new InjectedExtractor(),
]) { ]) {
api.registerExtractor(item); api.registerExtractor(item);
} }

View File

@@ -3,6 +3,7 @@ import { DataExtractorApi } from "./DataExtractorApi";
import { DataExtractorApiImpl } from "./DataExtractorApiImpl"; import { DataExtractorApiImpl } from "./DataExtractorApiImpl";
import * as helpers from "../helpers"; import * as helpers from "../helpers";
import * as globalHelpers from "../global-helpers"; import * as globalHelpers from "../global-helpers";
import { getGlobal } from "../../getGlobal";
/** /**
* Returns standalone JS code representing an expression that initializes the data extraction API. * Returns standalone JS code representing an expression that initializes the data extraction API.
@@ -28,16 +29,11 @@ export function getExpressionForDataExtractorApi(): string {
return `((${selfContainedGetInitializedDataExtractorApi.toString()})())`; return `((${selfContainedGetInitializedDataExtractorApi.toString()})())`;
} }
export function getExpressionToDetectDataExtractorApiPresence(): string { const apiKey = "@hediet/data-extractor-api/v3";
return `((${selfContainedIsDataExtractorApiInitialized.toString()})())`;
}
const apiKey = "@hediet/data-extractor-api/v2";
export function getDataExtractorApi(): DataExtractorApi { export function getDataExtractorApi(): DataExtractorApi {
installHelpers(); installHelpers();
const globalObj = const globalObj = getGlobal();
typeof window === "object" ? (window as any) : (global as any);
if (!globalObj[apiKey]) { if (!globalObj[apiKey]) {
globalObj[apiKey] = new DataExtractorApiImpl(); globalObj[apiKey] = new DataExtractorApiImpl();
} }
@@ -45,33 +41,31 @@ export function getDataExtractorApi(): DataExtractorApi {
} }
/** /**
* This code is used to detect if the API has not been initialized yet.
* @internal * @internal
*/ */
function selfContainedIsDataExtractorApiInitialized(): boolean { export const ApiHasNotBeenInitializedCode = "EgH0cybXij1jYUozyakO" as const;
const globalObj =
typeof window === "object" ? (window as any) : (global as any);
const key: typeof apiKey = "@hediet/data-extractor-api/v2";
let api: DataExtractorApi | undefined = globalObj[key];
return api !== undefined;
}
/** /**
* @internal * @internal
*/ */
function selfContainedGetInitializedDataExtractorApi(): DataExtractorApi { function selfContainedGetInitializedDataExtractorApi(): DataExtractorApi {
const globalObj = const globalObj =
typeof window === "object" ? (window as any) : (global as any); typeof window === "object" ? (window as any) : (global as any);
const key: typeof apiKey = "@hediet/data-extractor-api/v2"; const key: typeof apiKey = "@hediet/data-extractor-api/v3";
let api: DataExtractorApi | undefined = globalObj[key]; let api: DataExtractorApi | undefined = globalObj[key];
if (!api) { if (!api) {
throw new Error(`Data Extractor API has not been initialized.`); const code: typeof ApiHasNotBeenInitializedCode =
} "EgH0cybXij1jYUozyakO";
return api; throw new Error(
} `Data Extractor API has not been initialized. Code: ${code}`
);
}
return api;
}
export function installHelpers(): void { export function installHelpers(): void {
const globalObj = const globalObj = getGlobal();
typeof window === "object" ? (window as any) : (global as any);
// `hediet` as prefix to avoid name collision (I own `hediet.de`). // `hediet` as prefix to avoid name collision (I own `hediet.de`).
globalObj["hedietDbgVis"] = { ...helpers, ...globalHelpers }; globalObj["hedietDbgVis"] = { ...helpers, ...globalHelpers };
} }

View File

@@ -12,8 +12,8 @@ export function cache<T>(
let resultFn: () => any; let resultFn: () => any;
let key: string; let key: string;
if (typeof expression === "string") { if (typeof expression === "string") {
const evalFn = DataExtractorApiImpl.lastEvalFn!; const context = DataExtractorApiImpl.lastContext!;
resultFn = () => evalFn(expression); resultFn = () => context.evalFn(expression);
key = JSON.stringify({ expr: expression, id }); key = JSON.stringify({ expr: expression, id });
} else { } else {
resultFn = () => expression(); resultFn = () => expression();

View File

@@ -2,12 +2,22 @@ import { DataExtractorApiImpl } from "../api";
export function find(predicate: (obj: unknown) => boolean): unknown { export function find(predicate: (obj: unknown) => boolean): unknown {
const processed = new Set(); const processed = new Set();
if (!DataExtractorApiImpl.lastVariablesInScope) { if (!DataExtractorApiImpl.lastContext) {
throw new Error("No variables in scope!"); throw new Error("No data extractor context!");
} }
const values = Object.values(DataExtractorApiImpl.lastVariablesInScope); const values = Object.values(
const queue = [...values]; DataExtractorApiImpl.lastContext.variablesInScope
);
const queue = [
...values.map((v) => {
try {
return v();
} catch (e) {
return undefined;
}
}),
];
let i = 10000; let i = 10000;
while (i > 0) { while (i > 0) {

View File

@@ -30,17 +30,17 @@ export function tryEval(
obj: Record<string, string> | string[] | string obj: Record<string, string> | string[] | string
): Record<string, unknown> | unknown { ): Record<string, unknown> | unknown {
const result: Record<string, unknown> = {}; const result: Record<string, unknown> = {};
const evalFn = DataExtractorApiImpl.lastEvalFn!; const context = DataExtractorApiImpl.lastContext!;
if (Array.isArray(obj)) { if (Array.isArray(obj)) {
for (const val of obj) { for (const val of obj) {
try { try {
result[val] = evalFn(val); result[val] = context.evalFn(val);
} catch (e) {} } catch (e) {}
} }
} else { } else {
for (const [key, val] of Object.entries(obj)) { for (const [key, val] of Object.entries(obj)) {
try { try {
result[key] = evalFn(val); result[key] = context.evalFn(val);
} catch (e) {} } catch (e) {}
} }
} }

7
demos/python/Person.py Normal file
View File

@@ -0,0 +1,7 @@
class Person:
def __init__(self, name, parents=None) -> None:
self.name = name
self.parents = [] if parents is None else parents
def addParent(self, parent: "Person"):
self.parents.append(parent)

View File

@@ -0,0 +1,44 @@
from Person import Person
import json
from vscodedebugvisualizer import globalVisualizationFactory
class PersonVisualizer:
def checkType(self, t):
return isinstance(t, Person)
def visualizePerson(self, person: Person, nodes=[], edges=[]):
if person.name in [n["id"] for n in nodes]:
return nodes, edges
nodes.append(
{
"id": person.name,
"label": person.name,
}
)
for p in person.parents:
nodes, edges = self.visualizePerson(p, nodes, edges)
edges.append(
{
"from": p.name,
"to": person.name,
}
)
return nodes, edges
def visualize(self, person: Person):
jsonDict = {
"kind": {"graph": True},
"nodes": [],
"edges": [],
}
self.visualizePerson(person, jsonDict["nodes"], jsonDict["edges"])
return json.dumps(jsonDict)
globalVisualizationFactory.addVisualizer(PersonVisualizer())

123
demos/python/demo.py Normal file
View File

@@ -0,0 +1,123 @@
import numpy as np
# put "x" in the Debug Visualizer and use step by step
# debugging to see the diffrent types of data visualization
x = 5
x = "asdf"
x = 5.5
x = [1, 2, 3, 4, 5, 6, "a", "b"]
x = ["b", "a", "c", "d", "e"]
x.sort()
x = {
"asdf1": "dasdf",
"asdf2": "dasdf",
"asdf3": {"foo": "bar"},
}
x = [1, 2, 3, 4, 5, 6]
# histogram
x = np.concatenate([np.arange(i) for i in range(9)])
x = x.reshape(2, -1)
# one dimension
x = np.arange(100)
x = np.linspace(-np.pi, np.pi, 100)
x = np.sin(x)
# 2 dimension
x = x.reshape(5, 20)
# 2 dimension transpose
x = x.transpose()
x = x.transpose()
# 3 dimensions
x = x.reshape(2, 5, 10)
# 4 dimensions
x = x.reshape(2, 5, 2, 5)
# big number
x = np.empty(2 ** 24)
x[0:1000000] = 1
x[1000000:10000000] = 5
# pyTorch
try:
import torch
x = np.concatenate([np.arange(i) for i in range(9)])
x = x.reshape(2, -1)
x = torch.Tensor(x)
pass
except ImportError:
pass
# tensorflow
try:
import tensorflow as tf
x = np.concatenate([np.arange(i) for i in range(9)])
x = x.reshape(2, -1)
x = tf.convert_to_tensor(x)
pass
except ImportError:
pass
# pandas
try:
import pandas as pd
import plotly.express as px
x = px.data.gapminder().query("year == 2007")
pass
except ImportError:
pass
# custom visualizer defined in ./debugvisualizer.py (this file is included automatically)
from Person import Person
x = Person("Aria")
parent1 = Person("Eduart")
parent2 = Person("Catelyn")
x.addParent(parent1)
x.addParent(parent2)
parent1.addParent(Person("Benjen"))
# direct debug-visualizer json as dict with property "kind"
x = {
"kind": {"dotGraph": True},
"text": '\ndigraph G {\n subgraph cluster_0 {\n style=filled;\n color=lightgrey;\n node [style=filled,color=white];\n a0 -> a1 -> a2 -> a3;\n label = "process #1";\n }\n \n subgraph cluster_1 {\n node [style=filled];\n b0 -> b1 -> b2 -> b3;\n label = "process #2";\n color=blue\n }\n start -> a0;\n start -> b0;\n a1 -> b3;\n b2 -> a3;\n a3 -> a0;\n a3 -> end;\n b3 -> end;\n \n start [shape=Mdiamond];\n end [shape=Msquare];\n}\n',
}
x = {
"kind": {"plotly": True},
"data": [
{
"mode": "lines",
"type": "scatter",
"x": ["A", "B", "C"],
"xaxis": "x",
"y": [4488916, 3918072, 3892124],
"yaxis": "y",
},
{
"cells": {"values": [["A", "B", "C"], [341319, 281489, 294786], [4488916, 3918072, 3892124]]},
"domain": {"x": [0.0, 1.0], "y": [0.0, 0.60]},
"header": {"align": "left", "font": {"size": 10}, "values": ["Date", "Number", "Output"]},
"type": "table",
},
],
"layout": {"xaxis": {"anchor": "y", "domain": [0.0, 1.0]}, "yaxis": {"anchor": "x", "domain": [0.75, 1.0]}},
}
pass

View File

@@ -23,10 +23,9 @@ for i in range(2,100):
# connects the node to a random edge # connects the node to a random edge
targetId = str(randint(1, i - 1)) targetId = str(randint(1, i - 1))
graph["edges"].append({"from": id, "to": targetId}) graph["edges"].append({"from": id, "to": targetId})
json_graph = dumps(graph)
print("i is " + str(i)) print("i is " + str(i))
# try setting a breakpoint right above # try setting a breakpoint right above
# then put json_graph into the visualization console and press enter # then put graph into the visualization console and press enter
# when you step through the code each time you hit the breakpoint # when you step through the code each time you hit the breakpoint
# the graph should automatically refresh! # the graph should automatically refresh!

View File

@@ -1,5 +1,4 @@
"""Python demo for sorting using VS Code Debug Visualizer.""" """Python demo for sorting using VS Code Debug Visualizer."""
import json
def serialize(arr): def serialize(arr):
@@ -14,7 +13,7 @@ def serialize(arr):
} }
], ],
} }
return json.dumps(formatted) return formatted
arr = [6, 9, 3, 12, 1, 11, 5, 13, 8, 14, 2, 4, 10, 0, 7] arr = [6, 9, 3, 12, 1, 11, 5, 13, 8, 14, 2, 4, 10, 0, 7]

BIN
docs/custom-scripts.gif Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 MiB

View File

@@ -1,5 +1,9 @@
# Change Log # Change Log
## 2.3.0
- [Improves support for python](https://github.com/hediet/vscode-debug-visualizer/pull/122)
## 2.2.0 ## 2.2.0
- Adds setting `debugVisualizer.js.customScriptPaths` to inject custom scripts when debugging JavaScript. Such scripts are reloaded automatically when changed. - Adds setting `debugVisualizer.js.customScriptPaths` to inject custom scripts when debugging JavaScript. Such scripts are reloaded automatically when changed.

View File

@@ -112,68 +112,71 @@ Also, a global object of name `hedietDbgVis` with helper functions is injected.
This extension provides these configuration options: This extension provides these configuration options:
- `debugVisualizer.debugAdapterConfigurations` ### **debugVisualizer.debugAdapterConfigurations**
Allows to set expression templates for specific debug adapter types. Allows to set expression templates for specific debug adapter types.
Example: Example:
```json ```json
"debugVisualizer.debugAdapterConfigurations": { "debugVisualizer.debugAdapterConfigurations": {
"lldb": { "lldb": {
"expressionTemplate": "script to_json(\"${expr}\")", "expressionTemplate": "script to_json(\"${expr}\")",
"context": "repl" "context": "repl"
}
} }
``` }
```
Configurations here overwrite the built-in support for the corresponding debug adapter type. Configurations here overwrite the built-in support for the corresponding debug adapter type.
- `debugVisualizer.useChromeKioskMode` ### **debugVisualizer.useChromeKioskMode**
Specifies whether to pop out Debug Visualization Views with Chrome in Kiosk Mode. Uses the default browser otherwise or if Chrome is not found. Defaults to `true`. Specifies whether to pop out Debug Visualization Views with Chrome in Kiosk Mode. Uses the default browser otherwise or if Chrome is not found. Defaults to `true`.
- `debugVisualizer.js.customScriptPaths` ### **debugVisualizer.js.customScriptPaths**
Specifies a list of JavaScript files that are injected into the debugee when debugging JavaScript. Specifies a list of JavaScript files that are injected into the debugee when debugging JavaScript.
Each script must assign `module.exports` with a function of type `import("@hediet/debug-visualizer-data-extraction").LoadDataExtractorsFn`. Each script must assign `module.exports` with a function of type `import("@hediet/debug-visualizer-data-extraction").LoadDataExtractorsFn`.
Paths must be absolute and can use the variable `${workspaceFolder}`. Paths must be absolute and can use the variable `${workspaceFolder}`.
Scripts are automatically reloaded when they are changed. Scripts are automatically reloaded when they are changed.
Example: Example:
```js ```js
// @ts-check // @ts-check
/** /**
* @type {import("@hediet/debug-visualizer-data-extraction").LoadDataExtractorsFn} * @type {import("@hediet/debug-visualizer-data-extraction").LoadDataExtractorsFn}
*/ */
module.exports = (register, helpers) => { module.exports = (register, helpers) => {
register({ register({
id: "map", id: "map",
getExtractions(data, collector, context) { getExtractions(data, collector, context) {
if (!(data instanceof Map)) { if (!(data instanceof Map)) {
return; return;
} }
collector.addExtraction({ collector.addExtraction({
priority: 1000, priority: 1000,
id: "map", id: "map",
name: "Map", name: "Map",
extractData() { extractData() {
return helpers.asData({ return helpers.asData({
kind: { table: true }, kind: { table: true },
rows: [...data].map(([k, v]) => ({ rows: [...data].map(([k, v]) => ({
key: k, key: k,
value: v, value: v,
})), })),
}); });
}, },
}); });
}, },
}); });
}; };
``` ```
![](../docs/custom-script-map.png) ![](../docs/custom-script-map.png)
Use the `helpers` object to access some magic functions.
`helpers.find(val => ...)` can be used to find an object that is reachable by any variable in scope.
# See Also # See Also

View File

@@ -4,7 +4,7 @@
"displayName": "Debug Visualizer", "displayName": "Debug Visualizer",
"description": "A visual watch window that lets you visualize your data structures while debugging.", "description": "A visual watch window that lets you visualize your data structures while debugging.",
"icon": "docs/logo.drawio.png", "icon": "docs/logo.drawio.png",
"version": "2.2.1", "version": "2.3.0",
"license": "GPL-3.0", "license": "GPL-3.0",
"engines": { "engines": {
"vscode": "^1.46.0" "vscode": "^1.46.0"

View File

@@ -1,31 +1,25 @@
import { import {
getExpressionForDataExtractorApi,
DataResult,
getExpressionToInitializeDataExtractorApi,
DataExtractionResult, DataExtractionResult,
getExpressionToDetectDataExtractorApiPresence, DataResult,
getExpressionForDataExtractorApi,
getExpressionToInitializeDataExtractorApi,
ApiHasNotBeenInitializedCode,
} from "@hediet/debug-visualizer-data-extraction"; } from "@hediet/debug-visualizer-data-extraction";
import { DebugSessionProxy } from "../proxies/DebugSessionProxy"; import { hotClass, registerUpdateReconciler } from "@hediet/node-reload";
import { import { existsSync, readFileSync, watch } from "fs";
DebugSessionVisualizationSupport, import { observable, reaction } from "mobx";
VisualizationBackend, import { window, workspace } from "vscode";
GetVisualizationDataArgs,
VisualizationBackendBase,
} from "./VisualizationBackend";
import { FormattedMessage } from "../webviewContract";
import { registerUpdateReconciler, hotClass } from "@hediet/node-reload";
import { Config } from "../Config"; import { Config } from "../Config";
import { DebuggerViewProxy } from "../proxies/DebuggerViewProxy"; import { DebuggerViewProxy } from "../proxies/DebuggerViewProxy";
import { readFileSync, existsSync } from "fs"; import { DebugSessionProxy } from "../proxies/DebugSessionProxy";
import { reaction, observable } from "mobx"; import { FormattedMessage } from "../webviewContract";
import { import {
workspace, DebugSessionVisualizationSupport,
FileSystemWatcher, GetVisualizationDataArgs,
CancellationTokenSource, VisualizationBackend,
CancellationToken, VisualizationBackendBase,
window, } from "./VisualizationBackend";
} from "vscode"; import { Disposable } from "@hediet/std/disposable";
import { ResettableTimeout } from "@hediet/std/timer";
registerUpdateReconciler(module); registerUpdateReconciler(module);
@@ -64,8 +58,7 @@ export class JsEvaluationEngine implements DebugSessionVisualizationSupport {
class JsVisualizationBackend extends VisualizationBackendBase { class JsVisualizationBackend extends VisualizationBackendBase {
public readonly expressionLanguageId = "javascript"; public readonly expressionLanguageId = "javascript";
private apiInitialized = false; private initializePromise: Promise<void> | undefined = undefined;
private initializedPromise: Promise<void> | undefined = undefined;
constructor( constructor(
debugSession: DebugSessionProxy, debugSession: DebugSessionProxy,
@@ -82,16 +75,41 @@ class JsVisualizationBackend extends VisualizationBackendBase {
return "repl"; return "repl";
} }
public async getVisualizationData({ public async getVisualizationData(
args: GetVisualizationDataArgs
): Promise<
| { kind: "data"; result: DataExtractionResult }
| { kind: "error"; message: FormattedMessage }
> {
const result = await this._getVisualizationData(args);
if (result.kind === "not-initialized") {
await this.initializeApiSafe();
const result2 = await this._getVisualizationData(args);
if (result2.kind !== "not-initialized") {
return result2;
} else {
return {
kind: "error",
message: "Could not initialize API",
};
}
}
return result;
}
private async _getVisualizationData({
expression, expression,
preferredExtractorId, preferredExtractorId,
}: GetVisualizationDataArgs): Promise< }: GetVisualizationDataArgs): Promise<
| { kind: "data"; result: DataExtractionResult } | { kind: "data"; result: DataExtractionResult }
| { kind: "error"; message: FormattedMessage } | { kind: "error"; message: FormattedMessage }
| { kind: "not-initialized" }
> { > {
try { try {
await this.ensureRuntimeIsInjected();
const frameId = this.debuggerView.getActiveStackFrameId( const frameId = this.debuggerView.getActiveStackFrameId(
this.debugSession this.debugSession
); );
@@ -101,8 +119,8 @@ class JsVisualizationBackend extends VisualizationBackendBase {
const scopes = await this.debugSession.getScopes({ frameId }); const scopes = await this.debugSession.getScopes({ frameId });
const scopeVariables = await Promise.all( const scopeVariables = await Promise.all(
scopes scopes
.filter(s => !s.expensive) .filter((s) => !s.expensive && s.name !== "Global")
.map(s => .map((s) =>
this.debugSession.getVariables({ this.debugSession.getVariables({
variablesReference: s.variablesReference, variablesReference: s.variablesReference,
}) })
@@ -111,13 +129,13 @@ class JsVisualizationBackend extends VisualizationBackendBase {
for (const variables of scopeVariables) { for (const variables of scopeVariables) {
variableNames.push( variableNames.push(
...variables ...variables
.filter(v => v.value !== "undefined") .filter((v) => v.value !== "undefined")
.map(v => v.name) .map((v) => v.name)
); );
} }
if (variableNames.length > 100) { if (variableNames.length > 50) {
variableNames.length = 100; variableNames.length = 50;
} }
} }
@@ -129,7 +147,7 @@ class JsVisualizationBackend extends VisualizationBackendBase {
e => (${expression}), e => (${expression}),
expr => eval(expr), expr => eval(expr),
${preferredExtractorExpr}, ${preferredExtractorExpr},
{${variableNames.map(n => `${n}: ${n}`).join(",")}}, {${variableNames.map((n) => `${n}: () => ${n}`).join(",")}},
)`; )`;
const wrappedExpr = ` const wrappedExpr = `
@@ -171,6 +189,15 @@ class JsVisualizationBackend extends VisualizationBackendBase {
throw new Error("Invalid Data"); throw new Error("Invalid Data");
} }
} catch (error) { } catch (error) {
if (
typeof error.message === "string" &&
error.message.indexOf(ApiHasNotBeenInitializedCode) !== -1
) {
return {
kind: "not-initialized",
};
}
return { return {
kind: "error", kind: "error",
message: error.message, message: error.message,
@@ -178,144 +205,148 @@ class JsVisualizationBackend extends VisualizationBackendBase {
} }
} }
private ensureRuntimeIsInjected(): Promise<void> | undefined { private initializeApiSafe(): Promise<void> {
if (this.apiInitialized) { if (!this.initializePromise) {
return; this.initializePromise = this.initializeApi().finally(
} () => (this.initializePromise = undefined)
);
if (this.initializedPromise) {
return this.initializedPromise;
} else {
this.initializedPromise = this._ensureRuntimeIsInjected();
} }
return this.initializePromise;
} }
private async _ensureRuntimeIsInjected(): Promise<void> { private async initializeApi(): Promise<void> {
const body = getExpressionToDetectDataExtractorApiPresence(); // prefer existing is true, so that manually registered (possibly newer) extractors are not overwritten.
const expression = `${getExpressionToInitializeDataExtractorApi()}.registerDefaultExtractors(true);`;
const reply = await this.debugSession.evaluate({ await this.debugSession.evaluate({
expression: body, expression,
frameId: undefined, frameId: undefined,
context: this.getContext(), context: this.getContext(),
}); });
const isApiInitialized = reply.result === "true";
if (!isApiInitialized) {
await this.initializeApi();
}
await this.initializeCustomScripts(); await this.initializeCustomScripts();
} }
private async initializeApi(): Promise<boolean> { private customScripts: undefined | CustomScripts;
try {
// prefer existing is true, so that manually registered (possibly newer) extractors are not overwritten.
const expression = `${getExpressionToInitializeDataExtractorApi()}.registerDefaultExtractors(true);`;
await this.debugSession.evaluate({ private async initializeCustomScripts(): Promise<void> {
expression, this.dispose.untrack(this.customScripts);
frameId: undefined, this.customScripts?.dispose();
context: this.getContext(), this.customScripts = this.dispose.track(new CustomScripts(this.debugSession, this.config, () => this.onChangeEmitter.emit()));
});
return true;
} catch (error) {
return false;
}
} }
}
class CustomScripts {
public readonly dispose = Disposable.fn();
@observable @observable
private filesVersionId: number = 0; private filesVersionId: number = 0;
private readonly fileChangeDebouncedScheduler = new DebouncedScheduler(100); private readonly fileChangeDebouncedScheduler = new DebouncedScheduler(100);
private async initializeCustomScripts(): Promise<void> { constructor(private readonly debugSession: DebugSessionProxy, private readonly config: Config, private readonly changeHandler: () => void) {
await this.debugSession.evaluate({ this.init();
expression: `global["${injectedScriptsKey}"] = {};`, }
frameId: undefined,
context: "repl",
});
private init(): void {
const watchers = new Map<string, WatchedFile>(); const watchers = new Map<string, WatchedFile>();
this.dispose.track({ this.dispose.track({
dispose: () => { dispose: () => {
for (const w of watchers.values()) { for (const w of watchers.values()) {
w.watcher.dispose(); w.dispose();
} }
}, },
}); });
reaction( this.dispose.track({
() => ({ dispose: reaction(
customScriptPaths: [...this.config.customScriptPaths], () => ({
version: this.filesVersionId, customScriptPaths: [...this.config.customScriptPaths],
}), version: this.filesVersionId,
async ({ customScriptPaths }) => { }),
const promises = new Array<Promise<[Result, WatchedFile]>>(); async ({ customScriptPaths }) => {
const promises = new Array<
Promise<[Result, WatchedFile]>
>();
const activePaths = new Set<string>(); const activePaths = new Set<string>();
for (const fsPath of customScriptPaths) { for (const fsPath of customScriptPaths) {
activePaths.add(fsPath); activePaths.add(fsPath);
let entry = watchers.get(fsPath); let entry = watchers.get(fsPath);
if (!entry) { if (!entry) {
entry = new WatchedFile(fsPath, this.debugSession); entry = new WatchedFile(
watchers.set(fsPath, entry); fsPath,
entry.watcher.onDidChange(() => { this.debugSession,
this.fileChangeDebouncedScheduler.run(() => { () => {
this.filesVersionId++; this.fileChangeDebouncedScheduler.run(
}); () => {
}); this.filesVersionId++;
}
);
}
);
watchers.set(fsPath, entry);
}
promises.push(entry.update().then((e) => [e, entry!]));
} }
promises.push(entry.update().then(e => [e, entry!])); for (const [fsPath, entry] of watchers) {
} if (!activePaths.has(fsPath)) {
promises.push(
for (const [fsPath, entry] of watchers) { entry.remove().then((e) => [e, entry])
if (!activePaths.has(fsPath)) { );
promises.push(entry.remove().then(e => [e, entry])); entry.dispose();
entry.watcher.dispose(); watchers.delete(fsPath);
watchers.delete(fsPath); }
} }
}
const results = await Promise.all(promises); const results = await Promise.all(promises);
const errors = results.filter(([r]) => r.error); const errors = results.filter(([r]) => r.error);
for (const [e, watcher] of errors) { for (const [e, watcher] of errors) {
window.showErrorMessage( window.showErrorMessage(
`Error while evaluating "${watcher.fsPath}": ${ `Error while evaluating "${watcher.fsPath}": ${
e.error!.message e.error!.message
}` }`
); );
} }
if (promises.length > 0) { if (promises.length > 0) {
this.onChangeEmitter.emit(); this.changeHandler();
}
},
{
fireImmediately: true,
} }
}, ),
{ });
fireImmediately: true,
}
);
} }
} }
const injectedScriptsKey = `@hediet/debug-visualizer/injectedScripts`;
interface Result { interface Result {
error?: { message: string }; error?: { message: string };
} }
class WatchedFile { class WatchedFile {
public static nextId = 0; public static nextId = 0;
public readonly watcher = workspace.createFileSystemWatcher(this.fsPath); private readonly watcher = watch(
this.fsPath,
{ encoding: "utf-8" },
this.changeHandler
);
public readonly id = WatchedFile.nextId++; public readonly id = WatchedFile.nextId++;
public lastContent: string | undefined = undefined; public lastContent: string | undefined = undefined;
constructor( constructor(
public readonly fsPath: string, public readonly fsPath: string,
private readonly debugSession: DebugSessionProxy private readonly debugSession: DebugSessionProxy,
private readonly changeHandler: () => void
) {} ) {}
public dispose(): void {
this.watcher.close();
}
public async update(): Promise<Result> { public async update(): Promise<Result> {
if (!existsSync(this.fsPath)) { if (!existsSync(this.fsPath)) {
return { return {
@@ -337,7 +368,9 @@ class WatchedFile {
load => { load => {
var module = {}; var module = {};
load(module); load(module);
global["${injectedScriptsKey}"]["${this.id}"] = module.exports; ${getExpressionForDataExtractorApi()}.registerDataExtractorsSource("${
this.id
}", module.exports);
} }
) )
( (
@@ -357,7 +390,9 @@ class WatchedFile {
} }
public async remove(): Promise<Result> { public async remove(): Promise<Result> {
const expression = `(() => { delete global["${injectedScriptsKey}"]["${this.id}"]; })()`; const expression = `${getExpressionForDataExtractorApi()}.unregisterDataExtractorsSource("${
this.id
}")`;
try { try {
await this.debugSession.evaluate({ await this.debugSession.evaluate({
expression, expression,

View File

@@ -0,0 +1,149 @@
import {
DataExtractionResult,
DataExtractorId,
GraphNode,
GraphVisualizationData,
} from "@hediet/debug-visualizer-data-extraction";
import { DebugSessionProxy } from "../proxies/DebugSessionProxy";
import {
DebugSessionVisualizationSupport,
VisualizationBackend,
GetVisualizationDataArgs,
VisualizationBackendBase,
} from "./VisualizationBackend";
import { Config } from "../Config";
import { parseEvaluationResultFromGenericDebugAdapter } from "./parseEvaluationResultFromGenericDebugAdapter";
import { FormattedMessage } from "../webviewContract";
import { hotClass, registerUpdateReconciler } from "@hediet/node-reload";
import { DebuggerViewProxy } from "../proxies/DebuggerViewProxy";
registerUpdateReconciler(module);
@hotClass(module)
export class PyEvaluationEngine
implements DebugSessionVisualizationSupport {
constructor(
private readonly debuggerView: DebuggerViewProxy,
private readonly config: Config
) { }
createBackend(
session: DebugSessionProxy
): VisualizationBackend | undefined {
const supportedDebugAdapters = [
"python",
];
if (supportedDebugAdapters.indexOf(session.session.type) !== -1) {
return new PyVisualizationBackend(
session,
this.debuggerView,
this.config
);
}
return undefined;
}
}
export class PyVisualizationBackend extends VisualizationBackendBase {
public readonly expressionLanguageId = "python";
constructor(
debugSession: DebugSessionProxy,
debuggerView: DebuggerViewProxy,
private readonly config: Config
) {
super(debugSession, debuggerView);
}
protected getContext(): "watch" | "repl" {
// we will use "repl" as default so that results are not truncated.
return "repl";
}
public async getVisualizationData({
expression,
preferredExtractorId,
}: GetVisualizationDataArgs): Promise<
| { kind: "data"; result: DataExtractionResult }
| { kind: "error"; message: FormattedMessage }
> {
const frameId = this.debuggerView.getActiveStackFrameId(
this.debugSession
);
const finalExpression = this.getFinalExpression({
expression,
preferredExtractorId,
});
let reply: { result: string; variablesReference: number };
try {
// inject vscodedebugvisualizer for python
await this.debugSession.evaluate({
expression: 'from vscodedebugvisualizer import visualize\ntry:\n import debugvisualizer\nexcept ImportError:\n pass',
frameId,
context: this.getContext(),
});
reply = await this.debugSession.evaluate({
expression: finalExpression,
frameId,
context: this.getContext(),
});
let result = reply.result;
// remove the initial escape by the the debug session e.g. `''{"kind": {"text": true}, "text": "{"asdf1\'"}''`
result = result.replace(/\\'/g, "'");
result = result.replace(/\\\\/g, "\\");
return parseEvaluationResultFromGenericDebugAdapter(
result,
{
debugAdapterType: this.debugSession.session
.configuration.type,
}
);
} catch (error) {
let errorTyped = error as Error;
if (errorTyped.message.includes("ModuleNotFoundError: No module named 'vscodedebugvisualizer'")) {
return {
kind: "error",
message: {
kind: "list",
items: ["Pleas make sure vscodedebugvisualizer is installed: `pip install vscodedebugvisualizer`"],
},
};
}
return {
kind: "error",
message: {
kind: "list",
items: [
"An error occurred while evaluating the expression:",
errorTyped.message,
`Used debug adapter: ${this.debugSession.session.configuration.type}`,
{
kind: "inlineList",
items: [
"Evaluated expression is",
{ kind: "code", content: finalExpression },
],
},
],
},
};
}
}
protected getFinalExpression(args: {
expression: string;
preferredExtractorId: DataExtractorId | undefined;
}): string {
// wrap expression with visualize function
let pythonInject = "";
pythonInject += "visualize(" + args.expression + ")";
return pythonInject;
}
}

View File

@@ -2,5 +2,6 @@ export * from "./VisualizationBackend";
export { ComposedVisualizationSupport } from "./ComposedVisualizationSupport"; export { ComposedVisualizationSupport } from "./ComposedVisualizationSupport";
export { JsEvaluationEngine } from "./JsVisualizationSupport"; export { JsEvaluationEngine } from "./JsVisualizationSupport";
export { PyEvaluationEngine } from "./PyVisualizationSupport";
export { GenericVisualizationSupport } from "./GenericVisualizationSupport"; export { GenericVisualizationSupport } from "./GenericVisualizationSupport";
export { ConfigurableVisualizationSupport } from "./ConfigurableVisualizationSupport"; export { ConfigurableVisualizationSupport } from "./ConfigurableVisualizationSupport";

View File

@@ -21,6 +21,7 @@ import { VisualizationWatchModelImpl } from "./VisualizationWatchModel";
import { import {
ComposedVisualizationSupport, ComposedVisualizationSupport,
JsEvaluationEngine, JsEvaluationEngine,
PyEvaluationEngine,
GenericVisualizationSupport, GenericVisualizationSupport,
ConfigurableVisualizationSupport, ConfigurableVisualizationSupport,
} from "./VisualizationBackend"; } from "./VisualizationBackend";
@@ -32,7 +33,7 @@ export function activate(context: ExtensionContext) {
); );
} }
export function deactivate() {} export function deactivate() { }
export class Extension { export class Extension {
public readonly dispose = Disposable.fn(); public readonly dispose = Disposable.fn();
@@ -51,6 +52,7 @@ export class Extension {
this.debuggerView this.debuggerView
), ),
new JsEvaluationEngine(this.debuggerView, this.config), new JsEvaluationEngine(this.debuggerView, this.config),
new PyEvaluationEngine(this.debuggerView, this.config),
new GenericVisualizationSupport(this.debuggerView), new GenericVisualizationSupport(this.debuggerView),
]), ]),
this.debuggerView this.debuggerView

View File

@@ -14,13 +14,13 @@
"classnames": "^2.2.6", "classnames": "^2.2.6",
"mobx": "^5.15.4", "mobx": "^5.15.4",
"mobx-react": "^6.1.8", "mobx-react": "^6.1.8",
"monaco-editor": "^0.20.0", "monaco-editor": "^0.25.2",
"popper.js": "^1.16.1", "popper.js": "^1.16.1",
"react": "^16.12.0", "react": "^16.12.0",
"react-dom": "^16.12.0", "react-dom": "^16.12.0",
"react-measure": "^2.3.0", "react-measure": "^2.3.0",
"@hediet/debug-visualizer-data-extraction": "*", "@hediet/debug-visualizer-data-extraction": "*",
"@hediet/visualization-bundle": "^0.2.1", "@hediet/visualization-bundle": "^0.2.3",
"@hediet/visualization-core": "^0.2.0", "@hediet/visualization-core": "^0.2.0",
"@knuddels/mobx-logger": "^1.1.1", "@knuddels/mobx-logger": "^1.1.1",
"less-loader": "^6.2.0" "less-loader": "^6.2.0"
@@ -37,7 +37,7 @@
"file-loader": "^5.1.0", "file-loader": "^5.1.0",
"fork-ts-checker-webpack-plugin": "^4.0.4", "fork-ts-checker-webpack-plugin": "^4.0.4",
"html-webpack-plugin": "^3.2.0", "html-webpack-plugin": "^3.2.0",
"monaco-editor-webpack-plugin": "^1.9.0", "monaco-editor-webpack-plugin": "^4.0.0",
"sass": "^1.25.0", "sass": "^1.25.0",
"raw-loader": "^4.0.0", "raw-loader": "^4.0.0",
"sass-loader": "^8.0.2", "sass-loader": "^8.0.2",

View File

@@ -2,6 +2,9 @@ import * as React from "react";
import * as ReactDOM from "react-dom"; import * as ReactDOM from "react-dom";
import "./style.scss"; import "./style.scss";
import { App } from "./components/App"; import { App } from "./components/App";
import * as monaco from "monaco-editor";
(globalThis as any).monaco = monaco;
const elem = document.createElement("div"); const elem = document.createElement("div");
elem.className = "react-root"; elem.className = "react-root";

View File

@@ -487,10 +487,10 @@
stacktracey "^1.2.112" stacktracey "^1.2.112"
ws "^7.0.0" ws "^7.0.0"
"@hediet/monaco-editor-react@^0.1.1": "@hediet/monaco-editor-react@^0.2.0":
version "0.1.1" version "0.2.0"
resolved "https://registry.yarnpkg.com/@hediet/monaco-editor-react/-/monaco-editor-react-0.1.1.tgz#041f805aeb188403c7876f0f364d2a429e706031" resolved "https://registry.yarnpkg.com/@hediet/monaco-editor-react/-/monaco-editor-react-0.2.0.tgz#4a98102baf1942f210fc19fd406b6c82da3bf583"
integrity sha512-0DZZfLcFNuc3Wrn+2eztnRloCBe6GV8zIrGT3LPdZlWYEigJT+Qh7ANvRHF0tCS5YPAFGQNZJcfzKbBfqRGo4w== integrity sha512-EA71+iv9sV25+YgBQDJ4Ou17sOJCXQat7r5AOD5STZHcUQdSuQufI2t9ue+wJIm4048P9sQ/ruW3pYmwxF9Amw==
"@hediet/node-reload@^0.7.3": "@hediet/node-reload@^0.7.3":
version "0.7.3" version "0.7.3"
@@ -550,22 +550,22 @@
"@hediet/std" "^0.6.0" "@hediet/std" "^0.6.0"
io-ts "^1.8.6" io-ts "^1.8.6"
"@hediet/visualization-bundle@^0.2.1": "@hediet/visualization-bundle@^0.2.3":
version "0.2.1" version "0.2.3"
resolved "https://registry.yarnpkg.com/@hediet/visualization-bundle/-/visualization-bundle-0.2.1.tgz#397a6bff0ea12018a7a1cb1c182ef8c5de87328f" resolved "https://registry.yarnpkg.com/@hediet/visualization-bundle/-/visualization-bundle-0.2.3.tgz#8323706dcaaafc973fdc57da68e6527265e5bfcc"
integrity sha512-Qfyn8WbWGwuE5J1z8U7l8E5VKuG3DpS2JJOtKhlW1QW/rhqBWOLd5avifxqjWxiMIfqh3NTShp9gLHpmI2Bysw== integrity sha512-RpzAST/K5O9FaEaCshHEY7VeGRVlwoTLLcWPkgxzeuZlRr5CNTw8d7Lznh+RaLD1rbz/UEezq2pI3qFYnq/z4w==
dependencies: dependencies:
"@finos/perspective-viewer" "^0.5.2" "@finos/perspective-viewer" "^0.5.2"
"@finos/perspective-viewer-d3fc" "^0.5.2" "@finos/perspective-viewer-d3fc" "^0.5.2"
"@finos/perspective-viewer-datagrid" "^0.5.2" "@finos/perspective-viewer-datagrid" "^0.5.2"
"@hediet/monaco-editor-react" "^0.1.1" "@hediet/monaco-editor-react" "^0.2.0"
"@hediet/semantic-json" "^0.3.15" "@hediet/semantic-json" "^0.3.15"
"@hediet/std" "^0.6.0" "@hediet/std" "^0.6.0"
classnames "^2.2.6" classnames "^2.2.6"
line-column "^1.0.2" line-column "^1.0.2"
mobx "^5.15.4" mobx "^5.15.4"
mobx-react "^6.1.8" mobx-react "^6.1.8"
monaco-editor "^0.20.0" monaco-editor "^0.25.2"
plotly.js "^1.54.7" plotly.js "^1.54.7"
react "^16.12.0" react "^16.12.0"
react-dom "^16.12.0" react-dom "^16.12.0"
@@ -6861,17 +6861,17 @@ moment@^2.19.1:
resolved "https://registry.yarnpkg.com/moment/-/moment-2.27.0.tgz#8bff4e3e26a236220dfe3e36de756b6ebaa0105d" resolved "https://registry.yarnpkg.com/moment/-/moment-2.27.0.tgz#8bff4e3e26a236220dfe3e36de756b6ebaa0105d"
integrity sha512-al0MUK7cpIcglMv3YF13qSgdAIqxHTO7brRtaz3DlSULbqfazqkc5kEjNrLDOM7fsjshoFIihnU8snrP7zUvhQ== integrity sha512-al0MUK7cpIcglMv3YF13qSgdAIqxHTO7brRtaz3DlSULbqfazqkc5kEjNrLDOM7fsjshoFIihnU8snrP7zUvhQ==
monaco-editor-webpack-plugin@^1.9.0: monaco-editor-webpack-plugin@^4.0.0:
version "1.9.0" version "4.0.0"
resolved "https://registry.yarnpkg.com/monaco-editor-webpack-plugin/-/monaco-editor-webpack-plugin-1.9.0.tgz#5b547281b9f404057dc5d8c5722390df9ac90be6" resolved "https://registry.yarnpkg.com/monaco-editor-webpack-plugin/-/monaco-editor-webpack-plugin-4.0.0.tgz#95be3f48f4220999b909266a9997727f0deab947"
integrity sha512-tOiiToc94E1sb50BgZ8q8WK/bxus77SRrwCqIpAB5er3cpX78SULbEBY4YPOB8kDolOzKRt30WIHG/D6gz69Ww== integrity sha512-4BT9XDRQXraMQjxEUjR+uuubRe3RIPkvVoGw8zwWG++s7wq6TAiXaSOMdkdS9TrjCREgSnygCOlVzY6MS8RPuA==
dependencies: dependencies:
loader-utils "^1.2.3" loader-utils "^2.0.0"
monaco-editor@^0.20.0: monaco-editor@^0.25.2:
version "0.20.0" version "0.25.2"
resolved "https://registry.yarnpkg.com/monaco-editor/-/monaco-editor-0.20.0.tgz#5d5009343a550124426cb4d965a4d27a348b4dea" resolved "https://registry.yarnpkg.com/monaco-editor/-/monaco-editor-0.25.2.tgz#119e2b15bbd968a1a99c03cac9c329316d7c37e9"
integrity sha512-hkvf4EtPJRMQlPC3UbMoRs0vTAFAYdzFQ+gpMb8A+9znae1c43q8Mab9iVsgTcg/4PNiLGGn3SlDIa8uvK1FIQ== integrity sha512-5iylzSJevCnzJn9UVsW8yOZ3yHjmAs4TfvH3zsbftKiFKmHG0xirGN6DK9Kk04VSWxYCZZAIafYJoNJJMAU1KA==
monotone-convex-hull-2d@^1.0.1: monotone-convex-hull-2d@^1.0.1:
version "1.0.1" version "1.0.1"