Various visualizer improvements, uses registerDebugAdapterTrackerFactory now.
This commit is contained in:
4
.vscode/launch.json
vendored
4
.vscode/launch.json
vendored
@@ -10,6 +10,9 @@
|
||||
"--extensionDevelopmentPath=${workspaceFolder}/extension",
|
||||
"${workspaceFolder}\\example-project"
|
||||
],
|
||||
"env": {
|
||||
"HOT_RELOAD": "true"
|
||||
},
|
||||
"outFiles": ["${workspaceFolder}/extension/dist/**/*.js"],
|
||||
"preLaunchTask": "npm: dev - extension"
|
||||
},
|
||||
@@ -23,6 +26,7 @@
|
||||
"${workspaceFolder}\\example-project"
|
||||
],
|
||||
"env": {
|
||||
"HOT_RELOAD": "true",
|
||||
"USE_DEV_UI": "true"
|
||||
},
|
||||
"outFiles": ["${workspaceFolder}/extension/dist/**/*.js"],
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "@hediet/debug-visualizer-data-extraction",
|
||||
"private": true,
|
||||
"version": "0.1.0",
|
||||
"version": "0.2.0",
|
||||
"main": "dist/index.js",
|
||||
"types": "dist/index.d.ts",
|
||||
"author": {
|
||||
|
||||
@@ -4,7 +4,7 @@ export type DataExtractionResult = {
|
||||
availableExtractors: DataExtractorInfo[];
|
||||
};
|
||||
|
||||
// is json
|
||||
// Instances must be valid json values.
|
||||
export type ExtractedData = {
|
||||
kind: Record<string, true>;
|
||||
};
|
||||
|
||||
@@ -8,23 +8,27 @@ export class AsIsDataExtractor implements DataExtractor<ExtractedData> {
|
||||
extractionCollector: ExtractionCollector<ExtractedData>,
|
||||
evalFn: <TEval>(expression: string) => TEval
|
||||
): void {
|
||||
if (typeof data === "object" && data && "kind" in data) {
|
||||
const obj = data as any;
|
||||
if (typeof obj.kind === "object" && obj.kind) {
|
||||
const areAllTrue = Object.values(obj.kind).every(
|
||||
val => val === true
|
||||
);
|
||||
if (areAllTrue) {
|
||||
extractionCollector.addExtraction({
|
||||
id: this.id,
|
||||
name: "As Is",
|
||||
priority: 500,
|
||||
extractData() {
|
||||
return obj;
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
if (typeof data !== "object" || !data || !("kind" in data)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const obj = data as any;
|
||||
if (typeof obj.kind !== "object" || !obj.kind) {
|
||||
return;
|
||||
}
|
||||
|
||||
const areAllTrue = Object.values(obj.kind).every(val => val === true);
|
||||
if (!areAllTrue) {
|
||||
return;
|
||||
}
|
||||
|
||||
extractionCollector.addExtraction({
|
||||
id: this.id,
|
||||
name: "As Is",
|
||||
priority: 500,
|
||||
extractData() {
|
||||
return obj;
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,18 +10,20 @@ export class GetDebugVisualizationDataExtractor
|
||||
evalFn: <TEval>(expression: string) => TEval
|
||||
): void {
|
||||
if (
|
||||
typeof data === "object" &&
|
||||
data &&
|
||||
"getDebugVisualization" in data
|
||||
typeof data !== "object" ||
|
||||
!data ||
|
||||
!("getDebugVisualization" in data)
|
||||
) {
|
||||
extractionCollector.addExtraction({
|
||||
id: this.id,
|
||||
name: "getDebugVisualization",
|
||||
priority: 500,
|
||||
extractData() {
|
||||
return (data as any).getDebugVisualization();
|
||||
},
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
extractionCollector.addExtraction({
|
||||
id: this.id,
|
||||
name: "Use Method 'getDebugVisualization'",
|
||||
priority: 500,
|
||||
extractData() {
|
||||
return (data as any).getDebugVisualization();
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
80
data-extraction/src/js/getClosure.ts
Normal file
80
data-extraction/src/js/getClosure.ts
Normal file
@@ -0,0 +1,80 @@
|
||||
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,3 +1,4 @@
|
||||
export * from "./Extractors";
|
||||
export * from "./DataExtractor";
|
||||
export * from "./DataExtractorApi";
|
||||
export * from "./getClosure";
|
||||
|
||||
BIN
docs/linked-list-demo.gif
Normal file
BIN
docs/linked-list-demo.gif
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.1 MiB |
12
example-project/.vscode/launch.json
vendored
12
example-project/.vscode/launch.json
vendored
@@ -5,12 +5,20 @@
|
||||
"version": "0.2.0",
|
||||
"configurations": [
|
||||
{
|
||||
"type": "node2-with-pause-event",
|
||||
"type": "node",
|
||||
"request": "launch",
|
||||
"name": "Launch Program",
|
||||
"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"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
72
example-project/src/DoubleLinkedList.ts
Normal file
72
example-project/src/DoubleLinkedList.ts
Normal file
@@ -0,0 +1,72 @@
|
||||
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;
|
||||
}
|
||||
}
|
||||
@@ -10,6 +10,7 @@ enableHotReload();
|
||||
import {
|
||||
registerAll,
|
||||
CommonDataTypes,
|
||||
getClosure,
|
||||
} from "@hediet/debug-visualizer-data-extraction";
|
||||
import { MockLanguageServiceHost } from "./MockLanguageServiceHost";
|
||||
|
||||
@@ -17,82 +18,6 @@ registerAll();
|
||||
|
||||
registerUpdateReconciler(module);
|
||||
|
||||
function getClosure<T>(
|
||||
root: T,
|
||||
edgeSelector: (item: T) => { item: T; edgeLabel: string }[],
|
||||
labelSelector: (item: T) => string
|
||||
): 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 {
|
||||
let id = ids.get(item);
|
||||
if (!id) {
|
||||
id = (idCounter++).toString();
|
||||
ids.set(item, id);
|
||||
}
|
||||
return id;
|
||||
}
|
||||
|
||||
const queue = new Array<T>(root);
|
||||
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;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
toData(): CommonDataTypes.GraphData {
|
||||
return getClosure<DoubleLinkedList>(
|
||||
this,
|
||||
r =>
|
||||
[
|
||||
{ item: r.next!, edgeLabel: "next" },
|
||||
{ item: r.prev!, edgeLabel: "prev" },
|
||||
].filter(r => !!r.item),
|
||||
item => item.name
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@hotClass(module)
|
||||
class Main {
|
||||
run() {
|
||||
@@ -149,26 +74,11 @@ class Test1 {
|
||||
},
|
||||
};
|
||||
|
||||
debugger;
|
||||
for (const ident of identifiers) {
|
||||
const s = c.getSymbolAtLocation(ident);
|
||||
myValue = ident;
|
||||
}
|
||||
|
||||
const first = (myValue = new DoubleLinkedList("1"));
|
||||
myValue.setNext(new DoubleLinkedList("2"));
|
||||
myValue.next!.setNext(new DoubleLinkedList("3"));
|
||||
myValue.next!.next!.setNext(new DoubleLinkedList("4"));
|
||||
|
||||
let last: DoubleLinkedList | undefined = undefined;
|
||||
while (myValue) {
|
||||
myValue.prev = myValue.next;
|
||||
myValue.next = last;
|
||||
last = myValue;
|
||||
myValue = myValue.prev;
|
||||
}
|
||||
myValue = first;
|
||||
|
||||
myValue = {
|
||||
kind: { text: true, svg: true },
|
||||
text: `
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
"name": "debug-visualizer",
|
||||
"displayName": "Debug Visualizer",
|
||||
"description": "An extension that visualizes values while debugging.",
|
||||
"version": "0.1.2",
|
||||
"version": "0.2.0",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"vscode": "^1.35.0"
|
||||
|
||||
@@ -3,7 +3,7 @@ import {
|
||||
EvaluationWatcher,
|
||||
EvaluationWatcherOptions,
|
||||
} from "./DataSource";
|
||||
import { observable } from "mobx";
|
||||
import { observable, autorun } from "mobx";
|
||||
import * as vscode from "vscode";
|
||||
import { Disposable } from "@hediet/std/disposable";
|
||||
import {
|
||||
@@ -14,15 +14,19 @@ import {
|
||||
selfContainedInitDataExtractorApi,
|
||||
TypeScriptAstDataExtractor,
|
||||
AsIsDataExtractor,
|
||||
GetDebugVisualizationDataExtractor,
|
||||
} from "@hediet/debug-visualizer-data-extraction";
|
||||
import {
|
||||
DataExtractionState,
|
||||
CompletionItem,
|
||||
} from "@hediet/debug-visualizer-vscode-shared";
|
||||
import { hotClass } from "@hediet/node-reload";
|
||||
import { VsCodeDebuggerView, VsCodeDebugSession } from "../VsCodeDebugger";
|
||||
|
||||
export function createJsDebuggerSource(): JsDataSource {
|
||||
return new JsDebuggerSourceImplementation();
|
||||
export function createJsDebuggerSource(args: {
|
||||
vsCodeDebuggerView: VsCodeDebuggerView;
|
||||
}): JsDataSource {
|
||||
return new JsDebuggerSourceImplementation(args.vsCodeDebuggerView);
|
||||
}
|
||||
|
||||
export interface JsCode extends String {
|
||||
@@ -35,48 +39,41 @@ export interface JsDataSource extends DataSource {
|
||||
|
||||
@hotClass(module)
|
||||
export class JsDebuggerSourceImplementation implements JsDataSource {
|
||||
private lastFrameId: number | null = null;
|
||||
public readonly dispose = Disposable.fn();
|
||||
private readonly watchers = new Set<ObservableEvaluationWatcher>();
|
||||
|
||||
public readonly dispose = Disposable.fn();
|
||||
|
||||
constructor() {
|
||||
this.dispose.track(
|
||||
vscode.debug.onDidReceiveDebugSessionCustomEvent(e => {
|
||||
if (e.event === "paused") {
|
||||
const frameId = e.body.currentFrameId;
|
||||
this.lastFrameId = frameId;
|
||||
|
||||
constructor(private readonly vsCodeDebuggerView: VsCodeDebuggerView) {
|
||||
this.dispose.track({
|
||||
dispose: autorun(() => {
|
||||
if (
|
||||
vsCodeDebuggerView.activeDebugSession &&
|
||||
vsCodeDebuggerView.activeFrameId !== undefined
|
||||
) {
|
||||
for (const w of this.watchers) {
|
||||
w.refresh();
|
||||
}
|
||||
}
|
||||
})
|
||||
);
|
||||
}),
|
||||
});
|
||||
}
|
||||
|
||||
registerDataExtractor(classExpression: JsCode): void {}
|
||||
registerDataExtractor(classExpression: JsCode): void {
|
||||
// TODO implement
|
||||
}
|
||||
|
||||
async getCompletions(
|
||||
public async getCompletions(
|
||||
text: string,
|
||||
column: number
|
||||
): Promise<CompletionItem[]> {
|
||||
const session = vscode.debug.activeDebugSession;
|
||||
const session = this.vsCodeDebuggerView.activeDebugSession;
|
||||
if (!session) {
|
||||
return [];
|
||||
}
|
||||
|
||||
try {
|
||||
const reply = await session.customRequest("completions", {
|
||||
text,
|
||||
frameId: this.lastFrameId,
|
||||
column,
|
||||
});
|
||||
return reply.targets;
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
return [];
|
||||
}
|
||||
return await session.getCompletions({
|
||||
text,
|
||||
frameId: this.vsCodeDebuggerView.activeFrameId,
|
||||
column,
|
||||
});
|
||||
}
|
||||
|
||||
public createEvaluationWatcher(
|
||||
@@ -94,12 +91,14 @@ export class JsDebuggerSourceImplementation implements JsDataSource {
|
||||
}
|
||||
|
||||
public async refresh(w: ObservableEvaluationWatcher): Promise<void> {
|
||||
const session = vscode.debug.activeDebugSession;
|
||||
const session = this.vsCodeDebuggerView.activeDebugSession;
|
||||
if (!session) {
|
||||
w._state = { kind: "noDebugSession" };
|
||||
return;
|
||||
}
|
||||
|
||||
const frameId = this.vsCodeDebuggerView.activeFrameId;
|
||||
|
||||
try {
|
||||
w._state = { kind: "loading" };
|
||||
const fnSrc = selfContainedGetInitializedDataExtractorApi.toString();
|
||||
@@ -111,12 +110,11 @@ export class JsDebuggerSourceImplementation implements JsDataSource {
|
||||
const body = `(${fnSrc})().getData(${w.expression}, expr => eval(expr), ${preferredExtractor})`;
|
||||
const expression = `(() => { try { return ${body}; } catch (e) { return JSON.stringify({ kind: "Error", message: e.message, stack: e.stack }); } })()`;
|
||||
|
||||
const reply = await session.customRequest("evaluate", {
|
||||
const reply = await session.evaluate({
|
||||
expression,
|
||||
frameId: this.lastFrameId,
|
||||
context: "watch",
|
||||
frameId,
|
||||
});
|
||||
const resultStr = reply.result as string;
|
||||
const resultStr = reply.result;
|
||||
const jsonData = resultStr.substr(1, resultStr.length - 2);
|
||||
const result = JSON.parse(jsonData) as DataResult;
|
||||
if (result.kind === "NoExtractors") {
|
||||
@@ -132,7 +130,7 @@ export class JsDebuggerSourceImplementation implements JsDataSource {
|
||||
} catch (error) {
|
||||
const msg = error.message as string | undefined;
|
||||
if (msg && msg.includes(ApiHasNotBeenInitializedCode)) {
|
||||
if (await this.initializeApi(session)) {
|
||||
if (await this.initializeApi(session, frameId)) {
|
||||
await this.refresh(w);
|
||||
return;
|
||||
}
|
||||
@@ -146,21 +144,23 @@ export class JsDebuggerSourceImplementation implements JsDataSource {
|
||||
}
|
||||
|
||||
private async initializeApi(
|
||||
session: vscode.DebugSession
|
||||
session: VsCodeDebugSession,
|
||||
frameId: number | undefined
|
||||
): Promise<boolean> {
|
||||
try {
|
||||
let expression = `(${selfContainedInitDataExtractorApi.toString()})();`;
|
||||
|
||||
const es = [TypeScriptAstDataExtractor, AsIsDataExtractor].map(
|
||||
e => `new (${e.toString()})()`
|
||||
);
|
||||
const es = [
|
||||
TypeScriptAstDataExtractor,
|
||||
AsIsDataExtractor,
|
||||
GetDebugVisualizationDataExtractor,
|
||||
].map(e => `new (${e.toString()})()`);
|
||||
expression += `(${selfContainedGetInitializedDataExtractorApi.toString()})()`;
|
||||
expression += `.registerExtractors([${es.join(",")}])`;
|
||||
|
||||
const reply = await session.customRequest("evaluate", {
|
||||
const reply = await session.evaluate({
|
||||
expression,
|
||||
frameId: this.lastFrameId,
|
||||
context: "watch",
|
||||
frameId,
|
||||
});
|
||||
if (reply.result === "true") {
|
||||
// register extractors
|
||||
|
||||
15
extension/src/DataSource/Sources.ts
Normal file
15
extension/src/DataSource/Sources.ts
Normal file
@@ -0,0 +1,15 @@
|
||||
import { Disposable } from "@hediet/std/disposable";
|
||||
import { VsCodeDebugger, VsCodeDebuggerView } from "../VsCodeDebugger";
|
||||
import { createJsDebuggerSource } from "./JsDebuggerSource";
|
||||
|
||||
export class Sources {
|
||||
public readonly dispose = Disposable.fn();
|
||||
private readonly debugger = this.dispose.track(new VsCodeDebugger());
|
||||
private readonly debuggerView = this.dispose.track(
|
||||
new VsCodeDebuggerView(this.debugger)
|
||||
);
|
||||
|
||||
public readonly jsSource = createJsDebuggerSource({
|
||||
vsCodeDebuggerView: this.debuggerView,
|
||||
});
|
||||
}
|
||||
@@ -1,5 +1 @@
|
||||
import { createJsDebuggerSource } from "./JsDebuggerSource";
|
||||
|
||||
export class Sources {
|
||||
public readonly jsSource = createJsDebuggerSource();
|
||||
}
|
||||
export * from "./Sources";
|
||||
|
||||
192
extension/src/VsCodeDebugger.ts
Normal file
192
extension/src/VsCodeDebugger.ts
Normal file
@@ -0,0 +1,192 @@
|
||||
import { Disposable } from "@hediet/std/disposable";
|
||||
import { debug, DebugSession } from "vscode";
|
||||
import { EventEmitter } from "@hediet/std/events";
|
||||
import { CompletionItem } from "@hediet/debug-visualizer-vscode-shared";
|
||||
import { observable, runInAction } from "mobx";
|
||||
|
||||
export class VsCodeDebugger {
|
||||
public readonly dispose = Disposable.fn();
|
||||
private readonly sessions = new Map<DebugSession, VsCodeDebugSession>();
|
||||
|
||||
private readonly _onDidStartDebugSession = new EventEmitter<{
|
||||
session: VsCodeDebugSession;
|
||||
}>();
|
||||
public readonly onDidStartDebugSession = this._onDidStartDebugSession.asEvent();
|
||||
|
||||
public getDebugSession(session: DebugSession): VsCodeDebugSession {
|
||||
return this.sessions.get(session)!;
|
||||
}
|
||||
|
||||
constructor() {
|
||||
this.dispose.track([
|
||||
debug.onDidStartDebugSession(session => {
|
||||
const e = this.sessions.get(session)!;
|
||||
this._onDidStartDebugSession.emit({ session: e });
|
||||
}),
|
||||
debug.onDidTerminateDebugSession(session => {
|
||||
const e = this.sessions.get(session)!;
|
||||
// TODO add proper event
|
||||
this.sessions.delete(session);
|
||||
}),
|
||||
debug.registerDebugAdapterTrackerFactory("*", {
|
||||
createDebugAdapterTracker: session => {
|
||||
const extendedSession = new VsCodeDebugSession(session);
|
||||
this.sessions.set(session, extendedSession);
|
||||
|
||||
return {
|
||||
onDidSendMessage: async msg => {
|
||||
type Message =
|
||||
| StoppedEvent
|
||||
| ThreadsResponse
|
||||
| ContinueResponse
|
||||
| NextResponse;
|
||||
|
||||
interface ContinueResponse {
|
||||
type: "response";
|
||||
command: "continue";
|
||||
}
|
||||
|
||||
interface NextResponse {
|
||||
type: "response";
|
||||
command: "next";
|
||||
}
|
||||
|
||||
interface StoppedEvent {
|
||||
type: "event";
|
||||
event: "stopped";
|
||||
body: {
|
||||
threadId: number;
|
||||
};
|
||||
}
|
||||
|
||||
interface ThreadsResponse {
|
||||
type: "response";
|
||||
command: "threads";
|
||||
success: boolean;
|
||||
body: {
|
||||
threads: ThreadInfo[];
|
||||
};
|
||||
}
|
||||
|
||||
interface ThreadInfo {
|
||||
id: number;
|
||||
name: string;
|
||||
}
|
||||
|
||||
const m = msg as Message;
|
||||
if (m.type === "event") {
|
||||
if (m.event === "stopped") {
|
||||
const threadId = m.body.threadId;
|
||||
const r = await extendedSession[
|
||||
"getStackTrace"
|
||||
]({
|
||||
threadId,
|
||||
levels: 1,
|
||||
});
|
||||
extendedSession["activeStackFrame"] =
|
||||
r.stackFrames.length > 0
|
||||
? r.stackFrames[0].id
|
||||
: undefined;
|
||||
}
|
||||
} else if (m.type === "response") {
|
||||
if (
|
||||
m.command === "continue" ||
|
||||
m.command === "next"
|
||||
) {
|
||||
extendedSession[
|
||||
"activeStackFrame"
|
||||
] = undefined;
|
||||
}
|
||||
}
|
||||
},
|
||||
};
|
||||
},
|
||||
}),
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
export class VsCodeDebugSession {
|
||||
@observable protected activeStackFrame: number | undefined;
|
||||
|
||||
constructor(public readonly session: DebugSession) {}
|
||||
|
||||
protected async getStackTrace(args: { threadId: number; levels?: number }) {
|
||||
interface StackFrame {
|
||||
id: number;
|
||||
name: string;
|
||||
}
|
||||
|
||||
const reply = (await this.session.customRequest("stackTrace", {
|
||||
threadId: args.threadId,
|
||||
levels: args.levels,
|
||||
})) as { totalFrames?: number; stackFrames: StackFrame[] };
|
||||
|
||||
return reply;
|
||||
}
|
||||
|
||||
public async getCompletions(args: {
|
||||
text: string;
|
||||
column: number;
|
||||
frameId: number | undefined;
|
||||
}): Promise<CompletionItem[]> {
|
||||
try {
|
||||
const reply = await this.session.customRequest("completions", {
|
||||
text: args.text,
|
||||
frameId: args.frameId,
|
||||
column: args.column,
|
||||
});
|
||||
return reply.targets;
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
public async evaluate(args: {
|
||||
expression: string;
|
||||
frameId: number | undefined;
|
||||
}): Promise<{ result: string }> {
|
||||
const reply = await this.session.customRequest("evaluate", {
|
||||
expression: args.expression,
|
||||
frameId: args.frameId,
|
||||
context: "watch",
|
||||
});
|
||||
return { result: reply.result };
|
||||
}
|
||||
}
|
||||
|
||||
export class VsCodeDebuggerView {
|
||||
public readonly dispose = Disposable.fn();
|
||||
|
||||
@observable private _activeDebugSession: VsCodeDebugSession | undefined;
|
||||
|
||||
public get activeDebugSession(): VsCodeDebugSession | undefined {
|
||||
return this._activeDebugSession;
|
||||
}
|
||||
|
||||
public get activeFrameId(): number | undefined {
|
||||
if (!this._activeDebugSession) {
|
||||
return undefined;
|
||||
} else {
|
||||
return this._activeDebugSession["activeStackFrame"];
|
||||
}
|
||||
}
|
||||
|
||||
constructor(private vsCodeDebugger: VsCodeDebugger) {
|
||||
this.dispose.track(
|
||||
debug.onDidChangeActiveDebugSession(activeSession => {
|
||||
runInAction("Update active debug session", () => {
|
||||
if (!activeSession) {
|
||||
this._activeDebugSession = undefined;
|
||||
} else {
|
||||
const s = this.vsCodeDebugger.getDebugSession(
|
||||
activeSession
|
||||
);
|
||||
this._activeDebugSession = s;
|
||||
}
|
||||
});
|
||||
})
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
import { window, ExtensionContext, WebviewPanel, commands } from "vscode";
|
||||
import { window, ExtensionContext, commands } from "vscode";
|
||||
import { Disposable } from "@hediet/std/disposable";
|
||||
import {
|
||||
enableHotReload,
|
||||
@@ -7,22 +7,21 @@ import {
|
||||
getReloadCount,
|
||||
} from "@hediet/node-reload";
|
||||
|
||||
if (process.env.USE_DEV_UI) {
|
||||
if (process.env.HOT_RELOAD) {
|
||||
enableHotReload({ entryModule: module, loggingEnabled: true });
|
||||
}
|
||||
registerUpdateReconciler(module);
|
||||
|
||||
import { WebViews } from "./WebViews";
|
||||
import { Server } from "./Server";
|
||||
import { Config } from "./Config";
|
||||
import { Sources } from "./DataSource";
|
||||
|
||||
registerUpdateReconciler(module);
|
||||
|
||||
export class Extension {
|
||||
public dispose = Disposable.fn();
|
||||
|
||||
private readonly config = new Config();
|
||||
private readonly sources = new Sources();
|
||||
private readonly sources = this.dispose.track(new Sources());
|
||||
private readonly server = new Server(this.sources, this.config);
|
||||
private readonly views = this.dispose.track(new WebViews(this.server));
|
||||
|
||||
|
||||
1
extension/src/types.d.ts
vendored
1
extension/src/types.d.ts
vendored
@@ -1,5 +1,6 @@
|
||||
declare namespace NodeJS {
|
||||
declare interface ProcessEnv {
|
||||
HOT_RELOAD?: "true";
|
||||
USE_DEV_UI?: "true";
|
||||
}
|
||||
}
|
||||
|
||||
@@ -23,12 +23,15 @@
|
||||
"react-measure": "^2.3.0",
|
||||
"react-svg-pan-zoom": "^3.1.0",
|
||||
"viz.js": "^2.1.2",
|
||||
"line-column": "^1.0.2"
|
||||
"line-column": "^1.0.2",
|
||||
"vis-network": "^7.3.4",
|
||||
"vis-data": "^6.3.5"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/classnames": "^2.2.8",
|
||||
"@types/react": "^16.8.21",
|
||||
"@types/react-dom": "^16.8.4",
|
||||
"@types/line-column": "^1.0.0",
|
||||
"clean-webpack-plugin": "^3.0.0",
|
||||
"css-loader": "^3.0.0",
|
||||
"file-loader": "^4.0.0",
|
||||
|
||||
@@ -19,7 +19,7 @@ import { observer, disposeOnUnmount } from "mobx-react";
|
||||
import { observable, autorun, trace } from "mobx";
|
||||
import * as monaco from "monaco-editor";
|
||||
import { getLanguageId } from "./MonacoTextVisualizer";
|
||||
import * as LineColumn from "line-column";
|
||||
import LineColumn = require("line-column");
|
||||
|
||||
export class AstVisualizer extends VisualizationProvider {
|
||||
getVisualizations(
|
||||
|
||||
@@ -11,6 +11,26 @@ import {
|
||||
isCommonDataType,
|
||||
} from "@hediet/debug-visualizer-data-extraction";
|
||||
|
||||
export class DotGraphVisualizer extends VisualizationProvider {
|
||||
getVisualizations(
|
||||
data: ExtractedData,
|
||||
collector: VisualizationCollector
|
||||
): void {
|
||||
if (isCommonDataType(data, { graph: true })) {
|
||||
collector.addVisualization({
|
||||
id: asVisualizationId("dot-graph"),
|
||||
name: "Dot Graph",
|
||||
priority: 100,
|
||||
render() {
|
||||
return (
|
||||
<DotGraphViewer edges={data.edges} nodes={data.nodes} />
|
||||
);
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@observer
|
||||
export class DotGraphViewer extends React.Component<{
|
||||
nodes: { id: string; label: string }[];
|
||||
@@ -36,23 +56,3 @@ export class DotGraphViewer extends React.Component<{
|
||||
return <DotViewer dotCode={dotContent} />;
|
||||
}
|
||||
}
|
||||
|
||||
export class DotGraphVisualizer extends VisualizationProvider {
|
||||
getVisualizations(
|
||||
data: ExtractedData,
|
||||
collector: VisualizationCollector
|
||||
): void {
|
||||
if (isCommonDataType(data, { graph: true })) {
|
||||
collector.addVisualization({
|
||||
id: asVisualizationId("dot-graph"),
|
||||
name: "Dot Graph",
|
||||
priority: 100,
|
||||
render() {
|
||||
return (
|
||||
<DotGraphViewer edges={data.edges} nodes={data.nodes} />
|
||||
);
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -19,6 +19,24 @@ const viz: any = new Viz({
|
||||
render,
|
||||
});
|
||||
|
||||
export class DotVisualizer extends VisualizationProvider {
|
||||
getVisualizations(
|
||||
data: ExtractedData,
|
||||
collector: VisualizationCollector
|
||||
): void {
|
||||
if (isCommonDataType(data, { dotGraph: true })) {
|
||||
collector.addVisualization({
|
||||
id: asVisualizationId("dot-graph"),
|
||||
name: "Dot Graph",
|
||||
priority: 100,
|
||||
render() {
|
||||
return <DotViewer dotCode={data.text} />;
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@observer
|
||||
export class DotViewer extends React.Component<{
|
||||
dotCode: string;
|
||||
@@ -40,21 +58,3 @@ export class DotViewer extends React.Component<{
|
||||
return <SvgViewer svgRef={this.props.svgRef} svgContent={this.svg} />;
|
||||
}
|
||||
}
|
||||
|
||||
export class DotVisualizer extends VisualizationProvider {
|
||||
getVisualizations(
|
||||
data: ExtractedData,
|
||||
collector: VisualizationCollector
|
||||
): void {
|
||||
if (isCommonDataType(data, { dotGraph: true })) {
|
||||
collector.addVisualization({
|
||||
id: asVisualizationId("dot-graph"),
|
||||
name: "Dot Graph",
|
||||
priority: 100,
|
||||
render() {
|
||||
return <DotViewer dotCode={data.text} />;
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,6 +13,24 @@ import {
|
||||
isCommonDataType,
|
||||
} from "@hediet/debug-visualizer-data-extraction";
|
||||
|
||||
export class SvgVisualizer extends VisualizationProvider {
|
||||
getVisualizations(
|
||||
data: ExtractedData,
|
||||
collector: VisualizationCollector
|
||||
): void {
|
||||
if (isCommonDataType(data, { svg: true })) {
|
||||
collector.addVisualization({
|
||||
id: asVisualizationId("svg"),
|
||||
name: "Svg",
|
||||
priority: 100,
|
||||
render() {
|
||||
return <SvgViewer svgContent={data.text} />;
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function widthOrDefault(r: ContentRect): number {
|
||||
if (r.bounds && r.bounds.width) {
|
||||
return r.bounds.width;
|
||||
@@ -96,21 +114,3 @@ export class SvgViewer extends React.Component<{
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export class SvgVisualizer extends VisualizationProvider {
|
||||
getVisualizations(
|
||||
data: ExtractedData,
|
||||
collector: VisualizationCollector
|
||||
): void {
|
||||
if (isCommonDataType(data, { svg: true })) {
|
||||
collector.addVisualization({
|
||||
id: asVisualizationId("svg"),
|
||||
name: "Svg",
|
||||
priority: 100,
|
||||
render() {
|
||||
return <SvgViewer svgContent={data.text} />;
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
126
extension/ui/src/Visualizers/VisJsGraphVisualizer.tsx
Normal file
126
extension/ui/src/Visualizers/VisJsGraphVisualizer.tsx
Normal file
@@ -0,0 +1,126 @@
|
||||
import { observer } from "mobx-react";
|
||||
import * as React from "react";
|
||||
import {
|
||||
VisualizationProvider,
|
||||
VisualizationCollector,
|
||||
asVisualizationId,
|
||||
} from "./Visualizer";
|
||||
import {
|
||||
ExtractedData,
|
||||
isCommonDataType,
|
||||
} from "@hediet/debug-visualizer-data-extraction";
|
||||
import { DataSet, Network } from "vis-network";
|
||||
|
||||
export class VisJsGraphVisualizer extends VisualizationProvider {
|
||||
getVisualizations(
|
||||
data: ExtractedData,
|
||||
collector: VisualizationCollector
|
||||
): void {
|
||||
if (isCommonDataType(data, { graph: true })) {
|
||||
collector.addVisualization({
|
||||
id: asVisualizationId("vis-js-graph"),
|
||||
name: "VisJs Graph",
|
||||
priority: 1001,
|
||||
render() {
|
||||
return (
|
||||
<VisJsGraphViewer
|
||||
edges={data.edges}
|
||||
nodes={data.nodes}
|
||||
/>
|
||||
);
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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[];
|
||||
}> {
|
||||
private readonly divRef = React.createRef<HTMLDivElement>();
|
||||
private readonly nodes = new DataSet<{ id: string; label: string }>();
|
||||
private readonly edges = new DataSet<{
|
||||
id: string;
|
||||
label: string;
|
||||
from: string;
|
||||
to: string;
|
||||
}>();
|
||||
|
||||
render() {
|
||||
//const { nodes, edges } = this.props;
|
||||
return <div style={{ height: "100%" }} ref={this.divRef} />;
|
||||
}
|
||||
|
||||
synchronizeData() {
|
||||
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.forEach(item => {
|
||||
if (!newNodes.has(item.id)) {
|
||||
this.nodes.remove(item);
|
||||
}
|
||||
});
|
||||
|
||||
function getIdOfEdge(e: GraphEdge): string {
|
||||
if (e.id) {
|
||||
return e.id;
|
||||
}
|
||||
return e.from + "####" + e.to;
|
||||
}
|
||||
|
||||
const newEdges = new Set<string>();
|
||||
for (const n of this.props.edges) {
|
||||
const id = getIdOfEdge(n);
|
||||
newEdges.add(id);
|
||||
this.edges.update({
|
||||
id: id,
|
||||
label: n.label,
|
||||
from: n.from,
|
||||
to: n.to,
|
||||
});
|
||||
}
|
||||
this.edges.forEach(item => {
|
||||
if (!newEdges.has(item.id)) {
|
||||
this.edges.remove(item);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
componentDidUpdate() {
|
||||
this.synchronizeData();
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
this.synchronizeData();
|
||||
|
||||
const data = {
|
||||
nodes: this.nodes,
|
||||
edges: this.edges,
|
||||
};
|
||||
const options = {
|
||||
edges: {
|
||||
arrows: {
|
||||
to: { enabled: true, scaleFactor: 1, type: "arrow" },
|
||||
},
|
||||
},
|
||||
};
|
||||
const network = new Network(this.divRef.current!, data, options);
|
||||
//network.stabilize();
|
||||
}
|
||||
}
|
||||
@@ -7,10 +7,12 @@ import { DotVisualizer } from "./DotVisualizer";
|
||||
import { TextVisualizer } from "./TextVisualizer";
|
||||
import { MonacoTextVisualizer } from "./MonacoTextVisualizer";
|
||||
import { AstVisualizer } from "./AstViewer";
|
||||
import { VisJsGraphVisualizer } from "./VisJsGraphVisualizer";
|
||||
|
||||
export const knownVisualizations = new ComposedVisualizationProvider([
|
||||
new TreeVisualizer(),
|
||||
new DotGraphVisualizer(),
|
||||
new VisJsGraphVisualizer(),
|
||||
new SvgVisualizer(),
|
||||
new DotVisualizer(),
|
||||
new TextVisualizer(),
|
||||
|
||||
28
yarn.lock
28
yarn.lock
@@ -233,6 +233,11 @@
|
||||
"@types/tapable" "*"
|
||||
"@types/webpack" "*"
|
||||
|
||||
"@types/line-column@^1.0.0":
|
||||
version "1.0.0"
|
||||
resolved "https://registry.yarnpkg.com/@types/line-column/-/line-column-1.0.0.tgz#fa5a59c21e885fef3739a273b43dacf55b63437f"
|
||||
integrity sha512-wbw+IDRw/xY/RGy+BL6f4Eey4jsUgHQrMuA4Qj0CSG3x/7C2Oc57pmRoM2z3M4DkylWRz+G1pfX06sCXQm0J+w==
|
||||
|
||||
"@types/mime@*":
|
||||
version "2.0.1"
|
||||
resolved "https://registry.yarnpkg.com/@types/mime/-/mime-2.0.1.tgz#dc488842312a7f075149312905b5e3c0b054c79d"
|
||||
@@ -5912,6 +5917,29 @@ verror@1.10.0:
|
||||
core-util-is "1.0.2"
|
||||
extsprintf "^1.2.0"
|
||||
|
||||
vis-data@^6.3.5:
|
||||
version "6.3.5"
|
||||
resolved "https://registry.yarnpkg.com/vis-data/-/vis-data-6.3.5.tgz#db53cb0828601aa0908f86eca1cfe80f69f17873"
|
||||
integrity sha512-H8lD1NTmC5HimYeqTouxhbUya5qrYQGjpJhIHkxsm5R34EEdcm/D1cLTMLFFyIoZC6SQkADQj+dlFKf5DOnSww==
|
||||
optionalDependencies:
|
||||
vis-util "2.0.1"
|
||||
vis-uuid "1.1.3"
|
||||
|
||||
vis-network@^7.3.4:
|
||||
version "7.3.4"
|
||||
resolved "https://registry.yarnpkg.com/vis-network/-/vis-network-7.3.4.tgz#818ccf7d82afffae7059e6b2cb520cd28ea01070"
|
||||
integrity sha512-4hfB1DDnzclUUdniBh2WN2agcCmrL2ERmWsRB8wv8XtiiQ+Gg6HNzx5v7xySRWK+yH88LVnrrzaM+mDb5jcQtg==
|
||||
|
||||
vis-util@2.0.1:
|
||||
version "2.0.1"
|
||||
resolved "https://registry.yarnpkg.com/vis-util/-/vis-util-2.0.1.tgz#4538aee119795bde1f7996a3ee8e0148baf71124"
|
||||
integrity sha512-VBQ6zCT+Iaum70uzLbjk0nLMOjp9NhPzU42j0gaM6q6tg0eTcgIWNXzAqamhQtmm1kDj0jMLzKvkFOPENYv34w==
|
||||
|
||||
vis-uuid@1.1.3:
|
||||
version "1.1.3"
|
||||
resolved "https://registry.yarnpkg.com/vis-uuid/-/vis-uuid-1.1.3.tgz#2f53ff35e9e026b0ec93bc433ce685c40c2f784c"
|
||||
integrity sha512-2B6XdY1bkzbUh+TugmnAaFa61KO9R5pzBzIuFIm8a9FrkbxIdSmQXV+FbfkL8QunkQV/bT0JDLQ2puqCS2+0Og==
|
||||
|
||||
viz.js@^2.1.2:
|
||||
version "2.1.2"
|
||||
resolved "https://registry.yarnpkg.com/viz.js/-/viz.js-2.1.2.tgz#6f09cd4e10af28754a6d50b055bd2e4a7693983a"
|
||||
|
||||
Reference in New Issue
Block a user