v0.9.0
5
.vscode/launch.json
vendored
@@ -2,7 +2,7 @@
|
||||
"version": "0.2.0",
|
||||
"configurations": [
|
||||
{
|
||||
"name": "Run Extension",
|
||||
"name": "Run Extension (Build UI)",
|
||||
"type": "extensionHost",
|
||||
"request": "launch",
|
||||
"runtimeExecutable": "${execPath}",
|
||||
@@ -11,7 +11,8 @@
|
||||
"${workspaceFolder}\\example-project"
|
||||
],
|
||||
"env": {
|
||||
"HOT_RELOAD": "true"
|
||||
"HOT_RELOAD": "true",
|
||||
"USE_DEV_UI": ""
|
||||
},
|
||||
"outFiles": ["${workspaceFolder}/extension/dist/**/*.js"],
|
||||
"preLaunchTask": "npm: dev - extension"
|
||||
|
||||
@@ -1,20 +1,29 @@
|
||||
{
|
||||
"name": "@hediet/debug-visualizer-data-extraction",
|
||||
"private": true,
|
||||
"version": "0.2.0",
|
||||
"description": "A library that helps implementing data extractors for the Debug Visualizer VS Code extension.",
|
||||
"version": "0.9.0",
|
||||
"main": "dist/index.js",
|
||||
"types": "dist/index.d.ts",
|
||||
"author": {
|
||||
"name": "Henning Dieterichs",
|
||||
"email": "henning.dieterichs@live.de"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/hediet/vscode-debug-visualizer.git"
|
||||
},
|
||||
"license": "MIT",
|
||||
"files": [
|
||||
"dist",
|
||||
"src"
|
||||
],
|
||||
"publishConfig": {
|
||||
"access": "public",
|
||||
"registry": "https://registry.npmjs.org/"
|
||||
},
|
||||
"scripts": {
|
||||
"dev": "tsc --watch"
|
||||
"dev": "tsc --watch",
|
||||
"build": "tsc"
|
||||
},
|
||||
"dependencies": {}
|
||||
}
|
||||
|
||||
@@ -19,9 +19,9 @@ export type CommonDataType =
|
||||
| CommonDataTypes.Svg
|
||||
| CommonDataTypes.Html
|
||||
| CommonDataTypes.DotGraph
|
||||
| CommonDataTypes.TreeNodeData
|
||||
| CommonDataTypes.AstData
|
||||
| CommonDataTypes.GraphData;
|
||||
| CommonDataTypes.Tree
|
||||
| CommonDataTypes.Ast
|
||||
| CommonDataTypes.Graph;
|
||||
|
||||
export module CommonDataTypes {
|
||||
export interface Text {
|
||||
@@ -31,34 +31,31 @@ export module CommonDataTypes {
|
||||
fileName?: string;
|
||||
}
|
||||
|
||||
export interface GraphData {
|
||||
export interface Graph {
|
||||
kind: { graph: true };
|
||||
nodes: { id: string; label: string }[];
|
||||
edges: { from: string; to: string; label: string }[];
|
||||
nodes: NodeGraphData[];
|
||||
edges: EdgeGraphData[];
|
||||
}
|
||||
|
||||
export interface Svg extends Text {
|
||||
kind: { text: true; svg: true };
|
||||
text: string;
|
||||
}
|
||||
|
||||
export interface Html extends Text {
|
||||
kind: { text: true; html: true };
|
||||
text: string;
|
||||
}
|
||||
|
||||
export interface DotGraph extends Text {
|
||||
kind: { text: true; dotGraph: true };
|
||||
text: string;
|
||||
}
|
||||
|
||||
export interface TreeNodeData<TData = unknown> {
|
||||
export interface Tree<TData = unknown> {
|
||||
kind: { tree: true };
|
||||
root: TreeNode<TData>;
|
||||
}
|
||||
|
||||
export interface AstData
|
||||
extends TreeNodeData<{
|
||||
export interface Ast
|
||||
extends Tree<{
|
||||
position: number;
|
||||
length: number;
|
||||
}>,
|
||||
@@ -68,11 +65,26 @@ export module CommonDataTypes {
|
||||
}
|
||||
|
||||
export interface TreeNode<TExtraData> {
|
||||
id: string | undefined;
|
||||
name: string;
|
||||
value: string | undefined;
|
||||
emphasizedValue: string | undefined;
|
||||
children: TreeNode<TExtraData>[];
|
||||
data: TExtraData;
|
||||
isMarked: boolean;
|
||||
id?: string;
|
||||
value?: string;
|
||||
emphasizedValue?: string;
|
||||
isMarked?: boolean;
|
||||
}
|
||||
|
||||
export interface NodeGraphData {
|
||||
id: string;
|
||||
label: string;
|
||||
color?: string;
|
||||
}
|
||||
|
||||
export interface EdgeGraphData {
|
||||
from: string;
|
||||
to: string;
|
||||
label: string;
|
||||
id?: string;
|
||||
color?: string;
|
||||
weight?: number;
|
||||
}
|
||||
|
||||
@@ -37,7 +37,7 @@ declare const window: any;
|
||||
|
||||
export function selfContainedInitDataExtractorApi(): boolean {
|
||||
const obj = typeof window === "object" ? (window as any) : (global as any);
|
||||
const key = "@hediet/data-extractor";
|
||||
const key = "@hediet/data-extractor/v1";
|
||||
const prefix = key + "::";
|
||||
|
||||
let api: DataExtractorApi | undefined = obj[key];
|
||||
@@ -135,7 +135,7 @@ export const ApiHasNotBeenInitializedCode = "EgH0cybXij1jYUozyakO" as const;
|
||||
|
||||
export function selfContainedGetInitializedDataExtractorApi(): DataExtractorApi {
|
||||
const obj = typeof window === "object" ? (window as any) : (global as any);
|
||||
const key = "@hediet/data-extractor";
|
||||
const key = "@hediet/data-extractor/v1";
|
||||
let api: DataExtractorApi | undefined = obj[key];
|
||||
if (!api) {
|
||||
const code: typeof ApiHasNotBeenInitializedCode =
|
||||
|
||||
@@ -4,12 +4,12 @@ import { CommonDataTypes } from "../../CommonDataTypes";
|
||||
|
||||
// This class is self contained and can be injected into both nodejs and browser environments.
|
||||
export class TypeScriptAstDataExtractor
|
||||
implements DataExtractor<CommonDataTypes.AstData> {
|
||||
implements DataExtractor<CommonDataTypes.Ast> {
|
||||
readonly id = "TypeScriptAst";
|
||||
|
||||
getExtractions(
|
||||
data: unknown,
|
||||
collector: ExtractionCollector<CommonDataTypes.AstData>,
|
||||
collector: ExtractionCollector<CommonDataTypes.Ast>,
|
||||
evalFn: <TEval>(expression: string) => TEval
|
||||
): void {
|
||||
if (!data) {
|
||||
@@ -63,7 +63,7 @@ export class TypeScriptAstDataExtractor
|
||||
memberName: string,
|
||||
marked: Set<ts.Node>,
|
||||
emphasizedValueFn: (node: ts.Node) => string | undefined
|
||||
): CommonDataTypes.AstData["root"] {
|
||||
): CommonDataTypes.Ast["root"] {
|
||||
const name = tsApi.SyntaxKind[node.kind];
|
||||
const children = getChildren(node)
|
||||
.map((childNode, idx) => {
|
||||
|
||||
105
data-extraction/src/js/createGraph.ts
Normal file
@@ -0,0 +1,105 @@
|
||||
import {
|
||||
CommonDataTypes,
|
||||
EdgeGraphData,
|
||||
NodeGraphData,
|
||||
} from "../CommonDataTypes";
|
||||
|
||||
export function createGraphFromPointers<T>(
|
||||
roots: Record<string, T | undefined | null>,
|
||||
infoSelector: (
|
||||
item: T
|
||||
) => {
|
||||
id?: string | number;
|
||||
edges: ({ to: T } & Omit<EdgeGraphData, "from" | "to">)[];
|
||||
} & Omit<NodeGraphData, "id">
|
||||
): CommonDataTypes.Graph {
|
||||
const marker = {};
|
||||
|
||||
interface Pointer {
|
||||
marker: {};
|
||||
name: string;
|
||||
value: T | null | undefined;
|
||||
}
|
||||
|
||||
const items = Object.entries(roots).map<Pointer>(([name, value]) => ({
|
||||
marker,
|
||||
name,
|
||||
value,
|
||||
}));
|
||||
|
||||
const includeLabelInName = false;
|
||||
|
||||
return createGraph<T | Pointer>(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);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
export function createGraph<T>(
|
||||
roots: T[],
|
||||
infoSelector: (
|
||||
item: T
|
||||
) => {
|
||||
id?: string | number;
|
||||
edges: ({ to: T } & Omit<EdgeGraphData, "from" | "to">)[];
|
||||
} & Omit<NodeGraphData, "id">
|
||||
): CommonDataTypes.Graph {
|
||||
const r: CommonDataTypes.Graph = {
|
||||
kind: {
|
||||
graph: true,
|
||||
},
|
||||
nodes: [],
|
||||
edges: [],
|
||||
};
|
||||
let idCounter = 1;
|
||||
const ids = new Map<T, string>();
|
||||
function getId(item: T): string {
|
||||
const _id = infoSelector(item).id;
|
||||
if (_id !== undefined) {
|
||||
return "" + _id;
|
||||
}
|
||||
|
||||
let id = ids.get(item);
|
||||
if (!id) {
|
||||
id = (idCounter++).toString();
|
||||
ids.set(item, id);
|
||||
}
|
||||
return id;
|
||||
}
|
||||
|
||||
const queue = new Array<T>(...roots);
|
||||
const processed = new Set<T>();
|
||||
|
||||
while (queue.length > 0) {
|
||||
const item = queue.shift()!;
|
||||
if (processed.has(item)) {
|
||||
continue;
|
||||
}
|
||||
processed.add(item);
|
||||
const nodeInfo = infoSelector(item);
|
||||
const fromId = getId(item);
|
||||
r.nodes.push({ ...nodeInfo, id: fromId, ["edges" as any]: undefined });
|
||||
for (const e of nodeInfo.edges) {
|
||||
const toId = getId(e.to);
|
||||
r.edges.push({
|
||||
...e,
|
||||
from: fromId,
|
||||
to: toId,
|
||||
});
|
||||
if (!processed.has(e.to)) {
|
||||
queue.push(e.to);
|
||||
}
|
||||
}
|
||||
}
|
||||
return r;
|
||||
}
|
||||
@@ -1,80 +0,0 @@
|
||||
import { CommonDataTypes } from "../CommonDataTypes";
|
||||
|
||||
export function getClosureObj<T>(
|
||||
roots: Record<string, T | undefined | null>,
|
||||
edgeSelector: (item: T) => { item: T; edgeLabel: string }[],
|
||||
labelSelector: (item: T) => string,
|
||||
idSelector?: (item: T) => string | number
|
||||
): CommonDataTypes.GraphData {
|
||||
const items = Object.entries(roots).map(([k, v]) => v);
|
||||
return getClosure<T>(
|
||||
items.filter(i => !!i) as T[],
|
||||
edgeSelector,
|
||||
item => {
|
||||
const names = new Array<string>();
|
||||
for (const [key, val] of Object.entries(roots)) {
|
||||
if (val === item) {
|
||||
names.push(key);
|
||||
}
|
||||
}
|
||||
const base = labelSelector(item);
|
||||
return names.length === 0 ? base : `${names.join(", ")}: ${base}`;
|
||||
},
|
||||
idSelector
|
||||
);
|
||||
}
|
||||
|
||||
export function getClosure<T>(
|
||||
roots: T[],
|
||||
edgeSelector: (item: T) => { item: T; edgeLabel: string }[],
|
||||
labelSelector: (item: T) => string,
|
||||
idSelector?: (item: T) => string | number
|
||||
): CommonDataTypes.GraphData {
|
||||
const r: CommonDataTypes.GraphData = {
|
||||
kind: {
|
||||
graph: true,
|
||||
},
|
||||
nodes: [],
|
||||
edges: [],
|
||||
};
|
||||
let idCounter = 1;
|
||||
const ids = new Map<T, string>();
|
||||
function getId(item: T): string {
|
||||
if (idSelector) {
|
||||
return "" + idSelector(item);
|
||||
}
|
||||
|
||||
let id = ids.get(item);
|
||||
if (!id) {
|
||||
id = (idCounter++).toString();
|
||||
ids.set(item, id);
|
||||
}
|
||||
return id;
|
||||
}
|
||||
|
||||
const queue = new Array<T>(...roots);
|
||||
const processed = new Set<T>();
|
||||
|
||||
while (queue.length > 0) {
|
||||
const item = queue.shift()!;
|
||||
if (processed.has(item)) {
|
||||
continue;
|
||||
}
|
||||
processed.add(item);
|
||||
const edges = edgeSelector(item);
|
||||
const fromId = getId(item);
|
||||
r.nodes.push({ id: fromId, label: labelSelector(item) });
|
||||
for (const e of edges) {
|
||||
const toId = getId(e.item);
|
||||
r.edges.push({
|
||||
from: fromId,
|
||||
to: toId,
|
||||
label: e.edgeLabel,
|
||||
});
|
||||
if (!processed.has(e.item)) {
|
||||
queue.push(e.item);
|
||||
}
|
||||
}
|
||||
}
|
||||
return r;
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
export * from "./Extractors";
|
||||
export * from "./DataExtractor";
|
||||
export * from "./DataExtractorApi";
|
||||
export * from "./getClosure";
|
||||
export * from "./createGraph";
|
||||
|
||||
|
Before Width: | Height: | Size: 140 KiB |
BIN
docs/demo.gif
|
Before Width: | Height: | Size: 182 KiB |
BIN
docs/doubly-linked-list-reverse-demo.gif
Normal file
|
After Width: | Height: | Size: 3.5 MiB |
|
Before Width: | Height: | Size: 18 KiB |
|
Before Width: | Height: | Size: 1.1 MiB |
|
Before Width: | Height: | Size: 4.0 KiB |
BIN
docs/visualization-ast.gif
Normal file
|
After Width: | Height: | Size: 920 KiB |
BIN
docs/visualization-ast.png
Normal file
|
After Width: | Height: | Size: 26 KiB |
BIN
docs/visualization-graphviz.png
Normal file
|
After Width: | Height: | Size: 19 KiB |
BIN
docs/visualization-tree.png
Normal file
|
After Width: | Height: | Size: 31 KiB |
BIN
docs/visualization-visjs.png
Normal file
|
After Width: | Height: | Size: 14 KiB |
19
example-project/.vscode/launch.json
vendored
@@ -3,22 +3,5 @@
|
||||
// Hover to view descriptions of existing attributes.
|
||||
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
|
||||
"version": "0.2.0",
|
||||
"configurations": [
|
||||
{
|
||||
"type": "node",
|
||||
"request": "launch",
|
||||
"name": "Launch Index",
|
||||
"program": "${workspaceFolder}\\dist\\index.js",
|
||||
"outFiles": ["${workspaceFolder}/dist/**/*.js"],
|
||||
"preLaunchTask": "npm: dev"
|
||||
},
|
||||
{
|
||||
"type": "node",
|
||||
"request": "launch",
|
||||
"name": "Launch Double Linked List Demo",
|
||||
"program": "${workspaceFolder}\\dist\\DoubleLinkedList.js",
|
||||
"outFiles": ["${workspaceFolder}/dist/**/*.js"],
|
||||
"preLaunchTask": "npm: dev"
|
||||
}
|
||||
]
|
||||
"configurations": []
|
||||
}
|
||||
|
||||
4
example-project/.vscode/settings.json
vendored
Normal file
@@ -0,0 +1,4 @@
|
||||
{
|
||||
"debugVisualizer.useChromeKioskMode": false,
|
||||
"editor.minimap.enabled": false
|
||||
}
|
||||
@@ -1,72 +0,0 @@
|
||||
import {
|
||||
enableHotReload,
|
||||
registerUpdateReconciler,
|
||||
getReloadCount,
|
||||
hotClass,
|
||||
} from "@hediet/node-reload";
|
||||
enableHotReload();
|
||||
registerUpdateReconciler(module);
|
||||
|
||||
import {
|
||||
registerAll,
|
||||
CommonDataTypes,
|
||||
getClosureObj,
|
||||
} from "@hediet/debug-visualizer-data-extraction";
|
||||
|
||||
registerAll();
|
||||
|
||||
setTimeout(() => {
|
||||
new Main().run();
|
||||
}, 0);
|
||||
|
||||
@hotClass(module)
|
||||
class Main {
|
||||
run() {
|
||||
let myValue: DoubleLinkedList | undefined = new DoubleLinkedList("1");
|
||||
|
||||
const first = myValue;
|
||||
myValue.setNext(new DoubleLinkedList("2"));
|
||||
myValue.next!.setNext(new DoubleLinkedList("3"));
|
||||
myValue.next!.next!.setNext(new DoubleLinkedList("4"));
|
||||
|
||||
const dbgLast = myValue.next!.next!.next!;
|
||||
|
||||
let last: DoubleLinkedList | undefined = undefined;
|
||||
while (myValue) {
|
||||
myValue.prev = myValue.next;
|
||||
myValue.next = last;
|
||||
last = myValue;
|
||||
myValue = myValue.prev;
|
||||
}
|
||||
myValue = first;
|
||||
}
|
||||
|
||||
getDebugVisualization(
|
||||
items: Record<string, DoubleLinkedList | undefined>
|
||||
): CommonDataTypes.GraphData {
|
||||
return getClosureObj(
|
||||
items,
|
||||
r =>
|
||||
[
|
||||
{ item: r.next!, edgeLabel: "next" },
|
||||
{ item: r.prev!, edgeLabel: "prev" },
|
||||
].filter(r => !!r.item),
|
||||
item => item.name,
|
||||
item => item.id
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
let id = 0;
|
||||
class DoubleLinkedList {
|
||||
public readonly id = (id++).toString();
|
||||
constructor(public name: string) {}
|
||||
|
||||
next: DoubleLinkedList | undefined;
|
||||
prev: DoubleLinkedList | undefined;
|
||||
|
||||
public setNext(val: DoubleLinkedList): void {
|
||||
val.prev = this;
|
||||
this.next = val;
|
||||
}
|
||||
}
|
||||
59
example-project/src/demo_doubly-linked-list.ts
Normal file
@@ -0,0 +1,59 @@
|
||||
import {
|
||||
registerAll,
|
||||
CommonDataTypes,
|
||||
createGraphFromPointers,
|
||||
} from "@hediet/debug-visualizer-data-extraction";
|
||||
|
||||
registerAll();
|
||||
|
||||
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"));
|
||||
|
||||
const visualize = () =>
|
||||
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;
|
||||
}
|
||||
}
|
||||
@@ -1,24 +1,13 @@
|
||||
import * as ts from "typescript";
|
||||
import {
|
||||
enableHotReload,
|
||||
registerUpdateReconciler,
|
||||
getReloadCount,
|
||||
hotClass,
|
||||
} from "@hediet/node-reload";
|
||||
enableHotReload();
|
||||
|
||||
import {
|
||||
registerAll,
|
||||
CommonDataTypes,
|
||||
getClosure,
|
||||
} from "@hediet/debug-visualizer-data-extraction";
|
||||
import { registerAll } from "@hediet/debug-visualizer-data-extraction";
|
||||
import { MockLanguageServiceHost } from "./MockLanguageServiceHost";
|
||||
|
||||
registerAll();
|
||||
|
||||
registerUpdateReconciler(module);
|
||||
setTimeout(() => {
|
||||
new Main().run();
|
||||
}, 0);
|
||||
|
||||
@hotClass(module)
|
||||
class Main {
|
||||
run() {
|
||||
const mainFile = {
|
||||
@@ -49,10 +38,12 @@ class Test1 {
|
||||
|
||||
const c = prog.getTypeChecker();
|
||||
let myValue = undefined;
|
||||
const sf = prog.getSourceFiles()[0];
|
||||
myValue = sf.getText();
|
||||
const sourceFileAst = prog.getSourceFiles()[0];
|
||||
myValue = sourceFileAst.getText();
|
||||
console.log("myValue is the source code of the AST");
|
||||
|
||||
myValue = {
|
||||
sf,
|
||||
sf: sourceFileAst,
|
||||
fn: (n: ts.Node) => {
|
||||
try {
|
||||
const t = c.getTypeAtLocation(n);
|
||||
@@ -62,8 +53,10 @@ class Test1 {
|
||||
}
|
||||
},
|
||||
};
|
||||
console.log("myValue is AST, annotated with type information");
|
||||
|
||||
myValue = {
|
||||
sf,
|
||||
sf: sourceFileAst,
|
||||
fn: (n: ts.Node) => {
|
||||
try {
|
||||
const t = c.getSymbolAtLocation(n);
|
||||
@@ -73,12 +66,16 @@ class Test1 {
|
||||
}
|
||||
},
|
||||
};
|
||||
console.log("myValue is AST, annotated with symbol information");
|
||||
|
||||
for (const ident of identifiers) {
|
||||
const s = c.getSymbolAtLocation(ident);
|
||||
myValue = ident;
|
||||
console.log("myValue is an identifier");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
myValue = {
|
||||
kind: { text: true, svg: true },
|
||||
text: `
|
||||
@@ -89,10 +86,4 @@ class Test1 {
|
||||
/>
|
||||
</svg>
|
||||
`,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
if (getReloadCount(module) === 0) {
|
||||
new Main().run();
|
||||
}
|
||||
};*/
|
||||
5
extension/CHANGELOG.md
Normal file
@@ -0,0 +1,5 @@
|
||||
# Change Log
|
||||
|
||||
## 0.9.0
|
||||
|
||||
- Initial release
|
||||
@@ -1,62 +1,171 @@
|
||||
# Debug Visualizer (Preview)
|
||||
# Debug Visualizer
|
||||
|
||||
[](https://twitter.com/intent/follow?screen_name=hediet_dev)
|
||||
|
||||
Provides infrastructure to visualize values while debugging.
|
||||
A VS Code extension for visualizing data structures during debugging.
|
||||
|
||||
This extension is still in development.
|
||||
|
||||

|
||||

|
||||
|
||||
## Usage
|
||||
|
||||
After installing this extension, use the command `Open a new Debug Visualizer View` to open a new visualizer view.
|
||||
There you can enter an expression that is visualized when debugging your application.
|
||||
In this view you can enter an expression that is visualized when debugging your application, e.g.
|
||||
|
||||
```ts
|
||||
{ kind: { graph: true }, nodes: [ { id: "1", label: "1" }, { id: "2", label: "2" } ], edges: [{ from: "1", to: "2", label: "edge" }]}
|
||||
```
|
||||
|
||||
## Integrated Visualizers
|
||||
|
||||
Visualizers present preprocessed data.
|
||||
They are mostly React-Components and live in the webview.
|
||||
Visualizers present data extracted by a _Data Extractor_.
|
||||
Visualizers are (mostly) React components and live in the webview of the extension.
|
||||
|
||||
These visualizers are integrated:
|
||||
### Tree Visualization
|
||||
|
||||
- Tree Viewer
|
||||

|
||||
The tree visualizer renders data that matches the `Tree` interface.
|
||||
|
||||
- Svg Viewer
|
||||
- Dot Graph Viewer
|
||||
- VisJs Graph Viewer
|
||||

|
||||
```ts
|
||||
interface Tree<TData = unknown> {
|
||||
kind: { tree: true };
|
||||
root: TreeNode<TData>;
|
||||
}
|
||||
interface TreeNode<TExtraData> {
|
||||
id: string | undefined;
|
||||
name: string;
|
||||
value: string | undefined;
|
||||
emphasizedValue: string | undefined;
|
||||
children: TreeNode<TExtraData>[];
|
||||
data: TExtraData;
|
||||
isMarked: boolean;
|
||||
}
|
||||
```
|
||||
|
||||
- Graph Viewer
|
||||

|
||||

|
||||
|
||||
- Text Viewer
|
||||
- Plain Text
|
||||
- Monaco
|
||||
### AST Visualization
|
||||
|
||||
The AST visualizer renders data that matches the `Ast` interface.
|
||||
|
||||
```ts
|
||||
interface Ast
|
||||
extends Tree<{
|
||||
position: number;
|
||||
length: number;
|
||||
}>,
|
||||
Text {
|
||||
kind: { text: true; tree: true; ast: true };
|
||||
}
|
||||
```
|
||||
|
||||
In addition to the tree view, the text representation is shown.
|
||||
|
||||

|
||||
|
||||
### Text Visualization
|
||||
|
||||
The text visualizer renders data that matches the `Text` interface.
|
||||
|
||||
```ts
|
||||
interface Text {
|
||||
kind: { text: true };
|
||||
text: string;
|
||||
mimeType?: string;
|
||||
fileName?: string;
|
||||
}
|
||||
```
|
||||
|
||||
The `mimeType` and the file extension of `fileName` are used for syntax highlighting.
|
||||
|
||||
### SVG Visualization
|
||||
|
||||
The SVG visualizer renders data that matches the `Svg` interface.
|
||||
The actual SVG data must be stored in `text`.
|
||||
|
||||
```ts
|
||||
interface Svg extends Text {
|
||||
kind: { text: true; svg: true };
|
||||
}
|
||||
```
|
||||
|
||||
### Dot Graph Visualization
|
||||
|
||||
The Graphviz Dot visualizer renders data that matches the `DotGraph` interface.
|
||||
|
||||
```ts
|
||||
interface DotGraph extends Text {
|
||||
kind: { text: true; dotGraph: true };
|
||||
}
|
||||
```
|
||||
|
||||
`Viz.js` (Graphviz) is used for rendering.
|
||||
|
||||
### Graph Visualization
|
||||
|
||||
The Graphviz and vis.js visualizers render data that matches the `Graph` interface.
|
||||
|
||||
```ts
|
||||
interface Graph {
|
||||
kind: { graph: true };
|
||||
nodes: NodeGraphData[];
|
||||
edges: EdgeGraphData[];
|
||||
}
|
||||
|
||||
interface NodeGraphData {
|
||||
id: string;
|
||||
label: string;
|
||||
color?: string;
|
||||
}
|
||||
|
||||
interface EdgeGraphData {
|
||||
from: string;
|
||||
to: string;
|
||||
label: string;
|
||||
id?: string;
|
||||
color?: string;
|
||||
weight?: number;
|
||||
}
|
||||
```
|
||||
|
||||
The graphviz visualizer uses the SVG viewer to render the SVG created by `viz.js`.
|
||||
|
||||

|
||||

|
||||
|
||||
## Integrated Data Extractors
|
||||
|
||||
Data extractors convert arbitrary values into visualizable data.
|
||||
They live in the debugee.
|
||||
They live in the debugee. The following data extractors are injected automatically by this extension.
|
||||
Custom data extractors can be registered too.
|
||||
|
||||
These visualizers are integrated:
|
||||
### ToString
|
||||
|
||||
- ToString Text Extractor
|
||||
- TypeScript Ast to Tree Data
|
||||
- Direct Visualization of `ts.Node`s
|
||||
- Visualization of `Record<string, ts.Node>` and `[ts.Node]`. If the record contains a key `fn`, its value is displayed for each node.
|
||||
Just calls `.toString()` on values and treats the result as text.
|
||||
|
||||

|
||||
### TypeScript AST
|
||||
|
||||
- As Is Data Extractor. Allows to return data directly.
|
||||
- Direct Visualization of `ts.Node`s
|
||||
- Visualization of `Record<string, ts.Node>` and `[ts.Node]`. If the record contains a key `fn`, its value is displayed for each node.
|
||||
|
||||
### As Is Data Extractor
|
||||
|
||||
Treats the data as direct input to the visualizer.
|
||||
|
||||
### Use Method 'getDebugVisualization'
|
||||
|
||||
Calls `.getDebugVisualization()` on values and treats the result as direct input to the visualizer.
|
||||
|
||||
## Limitations
|
||||
|
||||
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.
|
||||
|
||||
# See Also
|
||||
|
||||
This extension works very well together with my library [`@hediet/node-reload`](https://github.com/hediet/node-reload):
|
||||
This extension works very well together with my library [`@hediet/node-reload`](https://github.com/hediet/node-reload).
|
||||
Together, they provide an interactive typescript playground.
|
||||
|
||||

|
||||

|
||||
|
||||
```
|
||||
|
||||
```
|
||||
|
||||
BIN
extension/docs/logo.png
Normal file
|
After Width: | Height: | Size: 2.8 KiB |
@@ -2,7 +2,8 @@
|
||||
"name": "debug-visualizer",
|
||||
"displayName": "Debug Visualizer",
|
||||
"description": "An extension that visualizes values while debugging.",
|
||||
"version": "0.2.1",
|
||||
"icon": "docs/logo.png",
|
||||
"version": "0.9.0",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"vscode": "^1.35.0"
|
||||
@@ -20,7 +21,6 @@
|
||||
"categories": [
|
||||
"Other"
|
||||
],
|
||||
"preview": true,
|
||||
"activationEvents": [
|
||||
"onCommand:vscode-debug-visualizer.new-visualizer",
|
||||
"onDebug",
|
||||
@@ -46,6 +46,7 @@
|
||||
}
|
||||
},
|
||||
"scripts": {
|
||||
"pub": "vsce publish --baseImagesUrl https://github.com/hediet/vscode-debug-visualizer/raw/master/extension",
|
||||
"vscode:prepublish": "yarn build",
|
||||
"build": "webpack --mode production",
|
||||
"dev": "tsc -watch -p ./"
|
||||
@@ -59,7 +60,8 @@
|
||||
"express": "^4.17.1",
|
||||
"open": "^6.4.0",
|
||||
"serve-static": "^1.14.1",
|
||||
"ws": "^7.1.1"
|
||||
"ws": "^7.1.1",
|
||||
"crypto-random-string": "^3.1.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/express": "^4.17.0",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { Disposable } from "@hediet/std/disposable";
|
||||
import { debugVisualizerUIContract } from "@hediet/debug-visualizer-vscode-shared";
|
||||
import { ConsoleRpcLogger } from "@hediet/typed-json-rpc";
|
||||
import { ConsoleRpcLogger, RequestHandlingError } from "@hediet/typed-json-rpc";
|
||||
import { EvaluationWatcher } from "./DataSource/DataSource";
|
||||
import { WebSocketStream } from "@hediet/typed-json-rpc-websocket";
|
||||
import { observable, autorun } from "mobx";
|
||||
@@ -19,8 +19,17 @@ export class ConnectionHandler {
|
||||
sources: Sources,
|
||||
stream: WebSocketStream,
|
||||
server: Server,
|
||||
config: Config
|
||||
config: Config,
|
||||
serverSecret: string
|
||||
) {
|
||||
let authenticated = false;
|
||||
|
||||
function throwIfNotAuthenticated() {
|
||||
if (!authenticated) {
|
||||
throw new RequestHandlingError("Not authenticated");
|
||||
}
|
||||
}
|
||||
|
||||
const {
|
||||
client,
|
||||
channel,
|
||||
@@ -28,12 +37,23 @@ export class ConnectionHandler {
|
||||
stream,
|
||||
new ConsoleRpcLogger(),
|
||||
{
|
||||
authenticate: async ({ secret }, { newErr }) => {
|
||||
if (secret !== serverSecret) {
|
||||
return newErr({ errorMessage: "Invalid Secret" });
|
||||
} else {
|
||||
authenticated = true;
|
||||
}
|
||||
},
|
||||
refresh: async () => {
|
||||
throwIfNotAuthenticated();
|
||||
|
||||
if (this.watcher) {
|
||||
this.watcher.refresh();
|
||||
}
|
||||
},
|
||||
setExpression: async ({ newExpression }) => {
|
||||
throwIfNotAuthenticated();
|
||||
|
||||
let oldPreferredDataExtractor: EvaluationWatcher["preferredDataExtractor"];
|
||||
if (this.watcher) {
|
||||
oldPreferredDataExtractor = this.watcher
|
||||
@@ -50,6 +70,8 @@ export class ConnectionHandler {
|
||||
);
|
||||
},
|
||||
openInBrowser: async ({}) => {
|
||||
throwIfNotAuthenticated();
|
||||
|
||||
let opened = false;
|
||||
if (config.useChromeKioskMode()) {
|
||||
opened = await launchChrome(server.indexUrl);
|
||||
@@ -59,11 +81,15 @@ export class ConnectionHandler {
|
||||
}
|
||||
},
|
||||
setPreferredDataExtractor: async ({ dataExtractorId }) => {
|
||||
throwIfNotAuthenticated();
|
||||
|
||||
if (this.watcher) {
|
||||
this.watcher.setPreferredDataExtractor(dataExtractorId);
|
||||
}
|
||||
},
|
||||
getCompletions: async ({ text, column }) => {
|
||||
throwIfNotAuthenticated();
|
||||
|
||||
const completions = await sources.jsSource.getCompletions(
|
||||
text,
|
||||
column
|
||||
|
||||
@@ -8,9 +8,11 @@ import * as express from "express";
|
||||
import * as http from "http";
|
||||
import * as serveStatic from "serve-static";
|
||||
import { Config } from "./Config";
|
||||
import cryptoRandomString = require("crypto-random-string");
|
||||
|
||||
export class Server {
|
||||
private server: http.Server;
|
||||
public readonly secret = cryptoRandomString({ length: 20 });
|
||||
|
||||
constructor(sources: Sources, config: Config) {
|
||||
const app = express();
|
||||
@@ -22,13 +24,13 @@ export class Server {
|
||||
const wss = new WebSocket.Server({ server: this.server });
|
||||
wss.on("connection", ws => {
|
||||
const stream = new WebSocketStream(ws);
|
||||
new ConnectionHandler(sources, stream, this, config);
|
||||
new ConnectionHandler(sources, stream, this, config, this.secret);
|
||||
});
|
||||
}
|
||||
|
||||
public get indexUrl(): string {
|
||||
const port = process.env.USE_DEV_UI ? 8080 : this.port;
|
||||
return `http://localhost:${port}/index.html?serverPort=${this.port}`;
|
||||
return `http://localhost:${port}/index.html?serverPort=${this.port}&serverSecret=${this.secret}`;
|
||||
}
|
||||
|
||||
public get mainBundleUrl(): string {
|
||||
|
||||
@@ -72,6 +72,7 @@ export function getHtml(server: Server) {
|
||||
<body>
|
||||
<script>
|
||||
window.serverPort = ${server.port};
|
||||
window.serverSecret = ${JSON.stringify(server.secret)};
|
||||
const api = window.VsCodeApi = acquireVsCodeApi();
|
||||
window.addEventListener('message', event => {
|
||||
if (event.source === window.frames[0]) {
|
||||
|
||||
@@ -1,90 +1,42 @@
|
||||
import React = require("react");
|
||||
import { Model } from "../Model/Model";
|
||||
import { observer } from "mobx-react";
|
||||
import { observable, computed } from "mobx";
|
||||
import { Popover, Button, Spinner } from "@blueprintjs/core";
|
||||
import classnames = require("classnames");
|
||||
import { Popover } from "@blueprintjs/core";
|
||||
import { DataExtractorInfo } from "@hediet/debug-visualizer-data-extraction";
|
||||
import { Visualizer } from "./Visualizer";
|
||||
import { ExpressionInput } from "./ExpressionInput";
|
||||
|
||||
@observer
|
||||
export class GUI extends React.Component<{ model: Model }> {
|
||||
@observable text: string = "node";
|
||||
|
||||
@observable expanded = false;
|
||||
|
||||
render() {
|
||||
const m = this.props.model;
|
||||
return (
|
||||
<div className="component-GUI" tabIndex={0}>
|
||||
<div className="part-header">
|
||||
<ExpandButton
|
||||
expanded={this.expanded}
|
||||
setExpanded={v => (this.expanded = v)}
|
||||
/>
|
||||
<div className="part-header-content">
|
||||
<div className="part-header-main">
|
||||
<div className="part-expression-input ">
|
||||
<ExpressionInput model={m} />
|
||||
</div>
|
||||
<div style={{ width: 4 }} />
|
||||
{m.loading ? (
|
||||
<div style={{ padding: "0 4px" }}>
|
||||
<Spinner size={Spinner.SIZE_SMALL} />
|
||||
</div>
|
||||
) : (
|
||||
<Button
|
||||
minimal
|
||||
small
|
||||
className="part-icon"
|
||||
icon="refresh"
|
||||
onClick={() => m.refresh()}
|
||||
/>
|
||||
)}
|
||||
<Button
|
||||
minimal
|
||||
small
|
||||
className="part-icon"
|
||||
icon="log-in"
|
||||
onClick={() => m.openBrowser()}
|
||||
/>
|
||||
<Button
|
||||
minimal
|
||||
small
|
||||
className="part-icon"
|
||||
icon="locate"
|
||||
onClick={() => m.useSelectionAsExpression()}
|
||||
/>
|
||||
</div>
|
||||
{this.expanded && <ExpandedOptions model={m} />}
|
||||
</div>
|
||||
</div>
|
||||
<div className="part-visualizer">
|
||||
<Visualizer model={m} />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
renderValue(value: string) {
|
||||
return <div>{`${value.length}`}</div>;
|
||||
}
|
||||
}
|
||||
import { computed } from "mobx";
|
||||
import { observer } from "mobx-react";
|
||||
import { Model } from "../Model/Model";
|
||||
import { VisualizationId } from "../Visualizers/Visualizer";
|
||||
|
||||
@observer
|
||||
export class ExpandedOptions extends React.Component<{ model: Model }> {
|
||||
render() {
|
||||
const m = this.props.model;
|
||||
let availableExtractors = new Array<DataExtractorInfo>();
|
||||
let selected = -1;
|
||||
let selectedExtractor = -1;
|
||||
if (m.state.kind === "data") {
|
||||
const result = m.state.result;
|
||||
availableExtractors = result.availableExtractors;
|
||||
selected = result.availableExtractors.findIndex(
|
||||
selectedExtractor = result.availableExtractors.findIndex(
|
||||
i => i.id === result.usedExtractor.id
|
||||
);
|
||||
}
|
||||
|
||||
let visualizations = new Array<{
|
||||
label: string;
|
||||
id: VisualizationId;
|
||||
}>();
|
||||
let selectedVisualization = -1;
|
||||
if (m.visualizations) {
|
||||
visualizations = m.visualizations.allVisualizations.map(v => ({
|
||||
label: v.name,
|
||||
id: v.id,
|
||||
}));
|
||||
if (m.visualizations.visualization) {
|
||||
selectedVisualization = visualizations.findIndex(
|
||||
v => v.id === m.visualizations!.visualization!.id
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<div style={{ height: 6 }} />
|
||||
@@ -99,7 +51,7 @@ export class ExpandedOptions extends React.Component<{ model: Model }> {
|
||||
|
||||
<NamedSelect
|
||||
name="Extractor"
|
||||
selected={selected}
|
||||
selected={selectedExtractor}
|
||||
options={availableExtractors.map(e => ({
|
||||
label: e.name,
|
||||
id: e.id,
|
||||
@@ -112,7 +64,7 @@ export class ExpandedOptions extends React.Component<{ model: Model }> {
|
||||
|
||||
<NamedSelect
|
||||
name="Visualizer"
|
||||
selected={0}
|
||||
selected={selectedVisualization}
|
||||
onSelected={item => {
|
||||
m.setPreferredVisualizationId(item.id);
|
||||
}}
|
||||
@@ -131,23 +83,6 @@ export class ExpandedOptions extends React.Component<{ model: Model }> {
|
||||
}
|
||||
}
|
||||
|
||||
export class ExpandButton extends React.Component<{
|
||||
expanded: boolean;
|
||||
setExpanded: (val: boolean) => void;
|
||||
}> {
|
||||
render() {
|
||||
return (
|
||||
<div
|
||||
onClick={() => this.props.setExpanded(!this.props.expanded)}
|
||||
className={classnames(
|
||||
"component-ExpandButton",
|
||||
this.props.expanded && "expanded"
|
||||
)}
|
||||
/>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
function NamedSelect<T extends SelectableItem>(props: {
|
||||
name: string;
|
||||
selected: number;
|
||||
@@ -169,7 +104,7 @@ interface SelectableItem {
|
||||
}
|
||||
|
||||
@observer
|
||||
export class Select<T extends SelectableItem> extends React.Component<{
|
||||
class Select<T extends SelectableItem> extends React.Component<{
|
||||
selected: number;
|
||||
onSelected: (item: T) => void;
|
||||
options: T[];
|
||||
93
extension/ui/src/Components/GUI.tsx
Normal file
@@ -0,0 +1,93 @@
|
||||
import React = require("react");
|
||||
import { Button, Spinner } from "@blueprintjs/core";
|
||||
import { observable } from "mobx";
|
||||
import { observer } from "mobx-react";
|
||||
import { Model } from "../Model/Model";
|
||||
import { ExpressionInput } from "./ExpressionInput";
|
||||
import { Visualizer } from "./Visualizer";
|
||||
import classnames = require("classnames");
|
||||
import { ExpandedOptions } from "./ExpandedOptions";
|
||||
|
||||
@observer
|
||||
export class GUI extends React.Component<{ model: Model }> {
|
||||
@observable text: string = "node";
|
||||
|
||||
@observable expanded = false;
|
||||
|
||||
render() {
|
||||
const m = this.props.model;
|
||||
return (
|
||||
<div className="component-GUI" tabIndex={0}>
|
||||
<div className="part-header">
|
||||
<ExpandButton
|
||||
expanded={this.expanded}
|
||||
setExpanded={v => (this.expanded = v)}
|
||||
/>
|
||||
<div className="part-header-content">
|
||||
<div className="part-header-main">
|
||||
<div className="part-expression-input ">
|
||||
<ExpressionInput model={m} />
|
||||
</div>
|
||||
<div style={{ width: 4 }} />
|
||||
{m.loading ? (
|
||||
<div style={{ padding: "0 4px" }}>
|
||||
<Spinner size={Spinner.SIZE_SMALL} />
|
||||
</div>
|
||||
) : (
|
||||
<Button
|
||||
minimal
|
||||
small
|
||||
className="part-icon"
|
||||
icon="refresh"
|
||||
onClick={() => m.refresh()}
|
||||
/>
|
||||
)}
|
||||
<Button
|
||||
minimal
|
||||
small
|
||||
className="part-icon"
|
||||
icon="log-in"
|
||||
onClick={() => m.openBrowser()}
|
||||
/>
|
||||
{/*
|
||||
TODO
|
||||
<Button
|
||||
minimal
|
||||
small
|
||||
className="part-icon"
|
||||
icon="locate"
|
||||
onClick={() => m.useSelectionAsExpression()}
|
||||
/>
|
||||
*/}
|
||||
</div>
|
||||
{this.expanded && <ExpandedOptions model={m} />}
|
||||
</div>
|
||||
</div>
|
||||
<div className="part-visualizer">
|
||||
<Visualizer model={m} />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
renderValue(value: string) {
|
||||
return <div>{`${value.length}`}</div>;
|
||||
}
|
||||
}
|
||||
|
||||
export class ExpandButton extends React.Component<{
|
||||
expanded: boolean;
|
||||
setExpanded: (val: boolean) => void;
|
||||
}> {
|
||||
render() {
|
||||
return (
|
||||
<div
|
||||
onClick={() => this.props.setExpanded(!this.props.expanded)}
|
||||
className={classnames(
|
||||
"component-ExpandButton",
|
||||
this.props.expanded && "expanded"
|
||||
)}
|
||||
/>
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -13,10 +13,12 @@ import { MonacoBridge } from "./MonacoBridge";
|
||||
|
||||
declare const window: Window & {
|
||||
serverPort?: number;
|
||||
serverSecret?: string;
|
||||
};
|
||||
|
||||
export class Model {
|
||||
port: number;
|
||||
private port: number;
|
||||
private serverSecret: string;
|
||||
|
||||
@observable expression: string = "";
|
||||
@observable state: DataExtractionState | { kind: "noExpression" } = {
|
||||
@@ -67,6 +69,7 @@ export class Model {
|
||||
constructor() {
|
||||
if (window.serverPort) {
|
||||
this.port = window.serverPort;
|
||||
this.serverSecret = window.serverSecret!;
|
||||
} else {
|
||||
const url = new URL(window.location.href);
|
||||
const portStr = url.searchParams.get("serverPort");
|
||||
@@ -79,6 +82,12 @@ export class Model {
|
||||
if (expr) {
|
||||
this.setExpression(expr);
|
||||
}
|
||||
|
||||
const secret = url.searchParams.get("serverSecret");
|
||||
if (!secret) {
|
||||
throw new Error("Server secret not set.");
|
||||
}
|
||||
this.serverSecret = secret;
|
||||
}
|
||||
|
||||
this.stayConnected();
|
||||
@@ -141,6 +150,11 @@ export class Model {
|
||||
},
|
||||
}
|
||||
);
|
||||
try {
|
||||
await server.authenticate({ secret: this.serverSecret });
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
}
|
||||
this.server = server;
|
||||
|
||||
await stream.onClosed;
|
||||
|
||||
@@ -9,16 +9,12 @@ import {
|
||||
CommonDataTypes,
|
||||
} from "@hediet/debug-visualizer-data-extraction";
|
||||
import { createTreeViewModelFromTreeNodeData } from "./TreeVisualizer/Visualizer";
|
||||
import {
|
||||
TreeWithPathView,
|
||||
TreeViewModel,
|
||||
TreeView,
|
||||
} from "./TreeVisualizer/Views";
|
||||
import { TreeWithPathView, TreeViewModel } from "./TreeVisualizer/Views";
|
||||
import React = require("react");
|
||||
import { observer, disposeOnUnmount } from "mobx-react";
|
||||
import { observable, autorun, trace } from "mobx";
|
||||
import * as monaco from "monaco-editor";
|
||||
import { getLanguageId } from "./MonacoTextVisualizer";
|
||||
import { getLanguageId } from "./TextVisualizer/MonacoTextVisualizer";
|
||||
import LineColumn = require("line-column");
|
||||
|
||||
export class AstVisualizer extends VisualizationProvider {
|
||||
@@ -70,7 +66,7 @@ interface NodeInfo {
|
||||
@observer
|
||||
export class AstTree extends React.Component<{
|
||||
model: TreeViewModel<NodeInfo>;
|
||||
data: CommonDataTypes.AstData;
|
||||
data: CommonDataTypes.Ast;
|
||||
nodeInfoToRange: (info: NodeInfo) => monaco.IRange;
|
||||
}> {
|
||||
render() {
|
||||
|
||||
@@ -3,12 +3,12 @@ import * as React from "react";
|
||||
import { observable, autorun, runInAction } from "mobx";
|
||||
import Viz from "viz.js";
|
||||
import { Module, render } from "viz.js/full.render.js";
|
||||
import { SvgViewer } from "./SvgVisualizer";
|
||||
import { SvgViewer } from "../SvgVisualizer";
|
||||
import {
|
||||
VisualizationProvider,
|
||||
VisualizationCollector,
|
||||
asVisualizationId,
|
||||
} from "./Visualizer";
|
||||
} from "../Visualizer";
|
||||
import {
|
||||
ExtractedData,
|
||||
isCommonDataType,
|
||||
@@ -19,18 +19,18 @@ const viz: any = new Viz({
|
||||
render,
|
||||
});
|
||||
|
||||
export class DotVisualizer extends VisualizationProvider {
|
||||
export class GraphvizDotVisualizer extends VisualizationProvider {
|
||||
getVisualizations(
|
||||
data: ExtractedData,
|
||||
collector: VisualizationCollector
|
||||
): void {
|
||||
if (isCommonDataType(data, { dotGraph: true })) {
|
||||
collector.addVisualization({
|
||||
id: asVisualizationId("dot-graph"),
|
||||
name: "Dot Graph",
|
||||
id: asVisualizationId("graphviz-dot"),
|
||||
name: "Graphviz (Dot Data)",
|
||||
priority: 100,
|
||||
render() {
|
||||
return <DotViewer dotCode={data.text} />;
|
||||
return <GraphvizDotViewer dotCode={data.text} />;
|
||||
},
|
||||
});
|
||||
}
|
||||
@@ -38,7 +38,7 @@ export class DotVisualizer extends VisualizationProvider {
|
||||
}
|
||||
|
||||
@observer
|
||||
export class DotViewer extends React.Component<{
|
||||
export class GraphvizDotViewer extends React.Component<{
|
||||
dotCode: string;
|
||||
svgRef?: (element: SVGSVGElement | null) => void;
|
||||
}> {
|
||||
@@ -1,29 +1,32 @@
|
||||
import { observer } from "mobx-react";
|
||||
import * as React from "react";
|
||||
import { DotViewer } from "./DotVisualizer";
|
||||
import { GraphvizDotViewer } from "./GraphvizDotVisualizer";
|
||||
import {
|
||||
VisualizationProvider,
|
||||
VisualizationCollector,
|
||||
asVisualizationId,
|
||||
} from "./Visualizer";
|
||||
} from "../Visualizer";
|
||||
import {
|
||||
ExtractedData,
|
||||
isCommonDataType,
|
||||
} from "@hediet/debug-visualizer-data-extraction";
|
||||
|
||||
export class DotGraphVisualizer extends VisualizationProvider {
|
||||
export class GraphvizGraphVisualizer extends VisualizationProvider {
|
||||
getVisualizations(
|
||||
data: ExtractedData,
|
||||
collector: VisualizationCollector
|
||||
): void {
|
||||
if (isCommonDataType(data, { graph: true })) {
|
||||
collector.addVisualization({
|
||||
id: asVisualizationId("dot-graph"),
|
||||
name: "Dot Graph",
|
||||
id: asVisualizationId("graphviz-graph"),
|
||||
name: "Graphviz",
|
||||
priority: 100,
|
||||
render() {
|
||||
return (
|
||||
<DotGraphViewer edges={data.edges} nodes={data.nodes} />
|
||||
<GraphvizGraphViewer
|
||||
edges={data.edges}
|
||||
nodes={data.nodes}
|
||||
/>
|
||||
);
|
||||
},
|
||||
});
|
||||
@@ -32,7 +35,7 @@ export class DotGraphVisualizer extends VisualizationProvider {
|
||||
}
|
||||
|
||||
@observer
|
||||
export class DotGraphViewer extends React.Component<{
|
||||
export class GraphvizGraphViewer extends React.Component<{
|
||||
nodes: { id: string; label: string }[];
|
||||
edges: { from: string; to: string; label: string }[];
|
||||
}> {
|
||||
@@ -53,6 +56,6 @@ export class DotGraphViewer extends React.Component<{
|
||||
.join("\n")}
|
||||
}
|
||||
`;
|
||||
return <DotViewer dotCode={dotContent} />;
|
||||
return <GraphvizDotViewer dotCode={dotContent} />;
|
||||
}
|
||||
}
|
||||
@@ -4,10 +4,12 @@ import {
|
||||
VisualizationProvider,
|
||||
VisualizationCollector,
|
||||
asVisualizationId,
|
||||
} from "./Visualizer";
|
||||
} from "../Visualizer";
|
||||
import {
|
||||
ExtractedData,
|
||||
isCommonDataType,
|
||||
NodeGraphData,
|
||||
EdgeGraphData,
|
||||
} from "@hediet/debug-visualizer-data-extraction";
|
||||
import { DataSet, Network } from "vis-network";
|
||||
|
||||
@@ -19,7 +21,7 @@ export class VisJsGraphVisualizer extends VisualizationProvider {
|
||||
if (isCommonDataType(data, { graph: true })) {
|
||||
collector.addVisualization({
|
||||
id: asVisualizationId("vis-js-graph"),
|
||||
name: "VisJs Graph",
|
||||
name: "vis.js",
|
||||
priority: 1001,
|
||||
render() {
|
||||
return (
|
||||
@@ -34,34 +36,26 @@ export class VisJsGraphVisualizer extends VisualizationProvider {
|
||||
}
|
||||
}
|
||||
|
||||
interface GraphNode {
|
||||
id: string;
|
||||
label: string;
|
||||
}
|
||||
|
||||
interface GraphEdge {
|
||||
id?: string;
|
||||
label: string;
|
||||
from: string;
|
||||
to: string;
|
||||
}
|
||||
|
||||
@observer
|
||||
export class VisJsGraphViewer extends React.Component<{
|
||||
nodes: GraphNode[];
|
||||
edges: GraphEdge[];
|
||||
nodes: NodeGraphData[];
|
||||
edges: EdgeGraphData[];
|
||||
}> {
|
||||
private readonly divRef = React.createRef<HTMLDivElement>();
|
||||
private readonly nodes = new DataSet<{ id: string; label: string }>();
|
||||
private readonly nodes = new DataSet<{
|
||||
id: string;
|
||||
label: string;
|
||||
color?: string;
|
||||
}>();
|
||||
private readonly edges = new DataSet<{
|
||||
id: string;
|
||||
label: string;
|
||||
from: string;
|
||||
to: string;
|
||||
color?: string;
|
||||
}>();
|
||||
|
||||
render() {
|
||||
//const { nodes, edges } = this.props;
|
||||
return <div style={{ height: "100%" }} ref={this.divRef} />;
|
||||
}
|
||||
|
||||
@@ -69,7 +63,11 @@ export class VisJsGraphViewer extends React.Component<{
|
||||
const newNodes = new Set<string>();
|
||||
for (const n of this.props.nodes) {
|
||||
newNodes.add(n.id);
|
||||
this.nodes.update({ id: n.id, label: n.label });
|
||||
this.nodes.update({
|
||||
id: n.id,
|
||||
label: n.label,
|
||||
color: n.color,
|
||||
});
|
||||
}
|
||||
this.nodes.forEach(item => {
|
||||
if (!newNodes.has(item.id)) {
|
||||
@@ -77,11 +75,11 @@ export class VisJsGraphViewer extends React.Component<{
|
||||
}
|
||||
});
|
||||
|
||||
function getIdOfEdge(e: GraphEdge): string {
|
||||
function getIdOfEdge(e: EdgeGraphData): string {
|
||||
if (e.id) {
|
||||
return e.id;
|
||||
}
|
||||
return e.from + "####" + e.to;
|
||||
return e.from + "####" + e.to + "|" + e.label;
|
||||
}
|
||||
|
||||
const newEdges = new Set<string>();
|
||||
@@ -93,6 +91,7 @@ export class VisJsGraphViewer extends React.Component<{
|
||||
label: n.label,
|
||||
from: n.from,
|
||||
to: n.to,
|
||||
color: n.color,
|
||||
});
|
||||
}
|
||||
this.edges.forEach(item => {
|
||||
@@ -121,6 +120,5 @@ export class VisJsGraphViewer extends React.Component<{
|
||||
},
|
||||
};
|
||||
const network = new Network(this.divRef.current!, data, options);
|
||||
//network.stabilize();
|
||||
}
|
||||
}
|
||||
@@ -2,7 +2,7 @@ import {
|
||||
VisualizationProvider,
|
||||
VisualizationCollector,
|
||||
asVisualizationId,
|
||||
} from "./Visualizer";
|
||||
} from "../Visualizer";
|
||||
import {
|
||||
ExtractedData,
|
||||
isCommonDataType,
|
||||
@@ -2,7 +2,7 @@ import {
|
||||
VisualizationProvider,
|
||||
VisualizationCollector,
|
||||
asVisualizationId,
|
||||
} from "./Visualizer";
|
||||
} from "../Visualizer";
|
||||
import {
|
||||
ExtractedData,
|
||||
isCommonDataType,
|
||||
@@ -5,7 +5,6 @@ import {
|
||||
TreeWithPathView,
|
||||
} from "./Views";
|
||||
import {
|
||||
CommonDataTypes,
|
||||
ExtractedData,
|
||||
isCommonDataType,
|
||||
TreeNode,
|
||||
@@ -40,7 +39,7 @@ export function createTreeViewModelFromTreeNodeData<TData>(
|
||||
children,
|
||||
node.data
|
||||
);
|
||||
model.isMarked = node.isMarked;
|
||||
model.isMarked = !!node.isMarked;
|
||||
for (const c of children) {
|
||||
c.parent = model;
|
||||
}
|
||||
|
||||
@@ -1,20 +1,20 @@
|
||||
import "./style.scss";
|
||||
import { ComposedVisualizationProvider } from "./Visualizer";
|
||||
import { TreeVisualizer } from "./TreeVisualizer/Visualizer";
|
||||
import { DotGraphVisualizer } from "./DotGraphVisualizer";
|
||||
import { GraphvizGraphVisualizer } from "./GraphVisualizer/GraphvizGraphVisualizer";
|
||||
import { SvgVisualizer } from "./SvgVisualizer";
|
||||
import { DotVisualizer } from "./DotVisualizer";
|
||||
import { TextVisualizer } from "./TextVisualizer";
|
||||
import { MonacoTextVisualizer } from "./MonacoTextVisualizer";
|
||||
import { GraphvizDotVisualizer } from "./GraphVisualizer/GraphvizDotVisualizer";
|
||||
import { TextVisualizer } from "./TextVisualizer/TextVisualizer";
|
||||
import { MonacoTextVisualizer } from "./TextVisualizer/MonacoTextVisualizer";
|
||||
import { AstVisualizer } from "./AstViewer";
|
||||
import { VisJsGraphVisualizer } from "./VisJsGraphVisualizer";
|
||||
import { VisJsGraphVisualizer } from "./GraphVisualizer/VisJsGraphVisualizer";
|
||||
|
||||
export const knownVisualizations = new ComposedVisualizationProvider([
|
||||
new TreeVisualizer(),
|
||||
new DotGraphVisualizer(),
|
||||
new GraphvizGraphVisualizer(),
|
||||
new VisJsGraphVisualizer(),
|
||||
new SvgVisualizer(),
|
||||
new DotVisualizer(),
|
||||
new GraphvizDotVisualizer(),
|
||||
new TextVisualizer(),
|
||||
new MonacoTextVisualizer(),
|
||||
new AstVisualizer(),
|
||||
|
||||
@@ -2,12 +2,12 @@ import * as React from "react";
|
||||
import * as ReactDOM from "react-dom";
|
||||
import "./style.scss";
|
||||
import { Model } from "./Model/Model";
|
||||
import Components = require("./Components");
|
||||
import Components = require("./Components/GUI");
|
||||
|
||||
const model = new Model();
|
||||
|
||||
function render(target: HTMLDivElement) {
|
||||
const c = require("./Components") as typeof Components;
|
||||
const c = require("./Components/GUI") as typeof Components;
|
||||
ReactDOM.render(<c.GUI model={model} />, target);
|
||||
}
|
||||
|
||||
@@ -23,7 +23,7 @@ declare var module: {
|
||||
declare var require: (name: string) => any;
|
||||
|
||||
if (module.hot) {
|
||||
module.hot.accept("./Components", () => {
|
||||
module.hot.accept("./Components/GUI", () => {
|
||||
render(target);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -87,6 +87,11 @@ export const debugVisualizerUIContract = contract({
|
||||
}),
|
||||
},
|
||||
server: {
|
||||
authenticate: requestContract({
|
||||
params: types.type({
|
||||
secret: types.string,
|
||||
}),
|
||||
}),
|
||||
setPreferredDataExtractor: notificationContract({
|
||||
params: types.type({
|
||||
dataExtractorId: unchecked<DataExtractorId>(),
|
||||
|
||||
12
yarn.lock
@@ -1512,6 +1512,13 @@ crypto-browserify@^3.11.0:
|
||||
randombytes "^2.0.0"
|
||||
randomfill "^1.0.3"
|
||||
|
||||
crypto-random-string@^3.1.0:
|
||||
version "3.1.0"
|
||||
resolved "https://registry.yarnpkg.com/crypto-random-string/-/crypto-random-string-3.1.0.tgz#0368382de82e914179ad2ca9c7a788e260184bae"
|
||||
integrity sha512-Tip3yGB+bA7B0W8E4K4mNf2rZhu5r2G5Tb89/utEl5tP1QuLjTF/S9a1b8ifDrR4ORc9Utf6tscpSEtBY3YcPQ==
|
||||
dependencies:
|
||||
type-fest "^0.8.1"
|
||||
|
||||
css-loader@^3.0.0:
|
||||
version "3.1.0"
|
||||
resolved "https://registry.yarnpkg.com/css-loader/-/css-loader-3.1.0.tgz#6f008b993b8ce812e6bab57f3cbfdc7a7cf28685"
|
||||
@@ -5727,6 +5734,11 @@ type-detect@4.0.8, type-detect@^4.0.0, type-detect@^4.0.5:
|
||||
resolved "https://registry.yarnpkg.com/type-detect/-/type-detect-4.0.8.tgz#7646fb5f18871cfbb7749e69bd39a6388eb7450c"
|
||||
integrity sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==
|
||||
|
||||
type-fest@^0.8.1:
|
||||
version "0.8.1"
|
||||
resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.8.1.tgz#09e249ebde851d3b1e48d27c105444667f17b83d"
|
||||
integrity sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA==
|
||||
|
||||
type-is@~1.6.17, type-is@~1.6.18:
|
||||
version "1.6.18"
|
||||
resolved "https://registry.yarnpkg.com/type-is/-/type-is-1.6.18.tgz#4e552cd05df09467dcbc4ef739de89f2cf37c131"
|
||||
|
||||