* Updates visualization library.

* Adds support for more js debug adapters.
* Minor improvements.
This commit is contained in:
Henning Dieterichs
2021-06-12 16:02:54 +02:00
parent 6da44bd722
commit 6ce5d666a3
16 changed files with 340 additions and 38 deletions

View File

@@ -107,7 +107,7 @@ export type GraphEdge = {
label?: string;
id?: string;
color?: string;
dashes?: boolean;
style?: "solid" | "dashed" | "dotted";
};
export type GridVisualizationData = {
@@ -157,12 +157,38 @@ export type MonacoTextVisualizationData = {
* The text to show
*/
text: string;
decorations?: {
range: LineColumnRange;
label?: string;
}[];
/**
* An optional filename that might be used for chosing a syntax highlighter
*/
fileName?: string;
};
export type LineColumnRange = {
/**
* The start position
*/
start: LineColumnPosition;
/**
* The end position
*/
end: LineColumnPosition;
};
export type LineColumnPosition = {
/**
* The 0-based line number
*/
line: number;
/**
* The 0-based column number
*/
column: number;
};
export type MonacoTextDiffVisualizationData = {
kind: {
text: true;

View File

@@ -22,7 +22,8 @@ export interface DataExtractorApi {
getData(
valueFn: () => unknown,
evalFn: <T>(expression: string) => T,
preferredDataExtractorId: string | undefined
preferredDataExtractorId: string | undefined,
variablesInScope: Record<string, unknown>
): JSONString<DataResult>;
/**
@@ -68,6 +69,8 @@ export interface DataExtractorContext {
* Evaluates an expression in the context of the active stack frame.
*/
evalFn: <TEval>(expression: string) => TEval;
variablesInScope: Record<string, unknown>;
}
export interface DataExtraction {

View File

@@ -14,6 +14,9 @@ import { registerDefaultExtractors } from "./default-extractors";
*/
export class DataExtractorApiImpl implements DataExtractorApi {
public static lastEvalFn: (<T>(expression: string) => T) | undefined;
public static lastVariablesInScope:
| Record<string, unknown>
| undefined = undefined;
private readonly extractors = new Map<string, DataExtractor>();
@@ -34,7 +37,8 @@ export class DataExtractorApiImpl implements DataExtractorApi {
public getData(
valueFn: () => unknown,
evalFn: <T>(expression: string) => T,
preferredDataExtractorId: string | undefined
preferredDataExtractorId: string | undefined,
variablesInScope: Record<string, unknown>
): JSONString<DataResult> {
const extractions = new Array<DataExtraction>();
const extractionCollector: ExtractionCollector = {
@@ -43,13 +47,20 @@ export class DataExtractorApiImpl implements DataExtractorApi {
},
};
DataExtractorApiImpl.lastVariablesInScope = variablesInScope;
DataExtractorApiImpl.lastEvalFn = evalFn;
const value = valueFn();
DataExtractorApiImpl.lastEvalFn = undefined;
for (const e of this.extractors.values()) {
e.getExtractions(value, extractionCollector, { evalFn });
e.getExtractions(value, extractionCollector, {
evalFn,
variablesInScope,
});
}
DataExtractorApiImpl.lastVariablesInScope = undefined;
DataExtractorApiImpl.lastEvalFn = undefined;
extractions.sort((a, b) => b.priority - a.priority);
let usedExtraction = extractions[0];
if (!usedExtraction) {

View File

@@ -0,0 +1,22 @@
import { DataExtractor, DataExtractorContext, ExtractionCollector } from "../..";
export class StringDiffExtractor implements DataExtractor {
public readonly id = "string-diff";
public getExtractions(data: unknown,
collector: ExtractionCollector,
context: DataExtractorContext) {
if (Array.isArray(data) && data.length === 2 && typeof data[0] === "string" && typeof data[1] === "string") {
collector.addExtraction({
id: "string-diff",
name: "String Diff",
priority: 1000,
extractData: () => ({
kind: { text: true },
text: data[0],
otherText: data[1]
})
})
}
}
}

View File

@@ -8,6 +8,8 @@ import { ObjectGraphExtractor } from "./ObjectGraphExtractor";
import { getDataExtractorApi } from "../injection";
import { GridExtractor } from "./GridExtractor";
import { TableDataExtractor } from "./TableExtractor";
import { StringDiffExtractor } from "./StringDiffExtractor";
import { find } from "../../helpers";
/**
* The default data extractors should be registered by VS Code automatically.
@@ -25,7 +27,102 @@ export function registerDefaultExtractors(
new ObjectGraphExtractor(),
new GridExtractor(),
new TableDataExtractor(),
new StringDiffExtractor(),
]) {
api.registerExtractor(item);
}
function isRange(
value: unknown
): value is {
startColumn: number;
startLineNumber: number;
endColumn: number;
endLineNumber: number;
} {
return (
typeof value === "object" &&
!!value &&
"startColumn" in value &&
"startLineNumber" in value
);
}
function isPosition(
value: unknown
): value is { column: number; lineNumber: number } {
return (
typeof value === "object" &&
!!value &&
"column" in value &&
"lineNumber" in value
);
}
// hedietDbgVis.find(x => x.constructor.name === 'TextModel').getValue()
api.registerExtractor({
id: "positionInTextModel",
getExtractions(data, collector, context) {
let range: {
start: {
line: number;
column: number;
};
end: {
line: number;
column: number;
};
};
if (isRange(data)) {
range = {
start: {
line: data.startLineNumber - 1,
column: data.startColumn - 1,
},
end: {
line: data.endLineNumber - 1,
column: data.endColumn - 1,
},
};
} else if (isPosition(data)) {
range = {
start: {
line: data.lineNumber - 1,
column: data.column - 1,
},
end: {
line: data.lineNumber - 1,
column: data.column - 1,
},
};
} else {
return;
}
if (!isRange(data) && !isPosition(data)) {
return;
}
const textModel: any = find(
x =>
typeof x === "object" &&
!!x &&
"constructor" in x &&
(x as any).constructor.name === "TextModel"
);
collector.addExtraction({
id: "positionInTextModel",
name: "Position In TextModel",
extractData() {
return {
kind: { text: true },
text: textModel.getValue(),
decorations: [{ range }],
};
},
priority: 1000,
});
},
});
}

View File

@@ -4,7 +4,8 @@ import {
GraphVisualizationData,
} from "../../CommonDataTypes";
export type CreateGraphEdge<T> = { to: T } & Omit<GraphEdge, "from" | "to">;
export type CreateGraphEdge<T> = ({ to: T } | { from: T } | { include: T }) &
Omit<GraphEdge, "from" | "to">;
/**
* Given a list of roots, it creates a graph by following their edges recursively.
@@ -58,13 +59,31 @@ export function createGraph<T>(
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,
});
let shouldPush = !processed.has(e.to);
let other: T;
let toId: string | undefined;
let fromId_: string | undefined;
if ("to" in e) {
other = e.to;
toId = getId(e.to);
fromId_ = fromId;
} else if ("from" in e) {
other = e.from;
toId = getId(e.from);
fromId_ = fromId;
} else if ("include" in e) {
other = e.include;
} else {
throw new Error("Every edge must either have 'to' or 'from'");
}
if (fromId_ !== undefined && toId !== undefined) {
r.edges.push({
...e,
from: fromId_,
to: toId,
});
}
let shouldPush = !processed.has(other);
if (
options.maxSize &&
processed.size + queue.length > options.maxSize
@@ -72,7 +91,7 @@ export function createGraph<T>(
shouldPush = false;
}
if (shouldPush) {
queue.push({ item: e.to, dist: dist + 1 });
queue.push({ item: other, dist: dist + 1 });
}
}
}

View File

@@ -0,0 +1,34 @@
import { DataExtractorApiImpl } from "../api";
export function find(predicate: (obj: unknown) => boolean): unknown {
const processed = new Set();
if (!DataExtractorApiImpl.lastVariablesInScope) {
throw new Error("No variables in scope!");
}
const values = Object.values(DataExtractorApiImpl.lastVariablesInScope);
const queue = [...values];
let i = 10000;
while (i > 0) {
i--;
const top = queue.shift();
processed.add(top);
if (predicate(top)) {
return top;
}
if (typeof top === "object" && top) {
for (const val of Object.values(top)) {
if (!processed.has(val)) {
processed.add(val);
queue.push(val);
}
}
}
}
return undefined;
}

View File

@@ -4,3 +4,4 @@ export * from "./tryEval";
export * from "./markedGrid";
export * from "./cache";
export * from "./asData";
export * from "./find";