Compare commits
66 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f2b4f620fe | ||
|
|
d22e5d4111 | ||
|
|
6ce5d666a3 | ||
|
|
6da44bd722 | ||
|
|
db1e5a8086 | ||
|
|
28d02ca4ab | ||
|
|
1e65dd1990 | ||
|
|
76fa30f71a | ||
|
|
3c8ccd9a53 | ||
|
|
9c70e06ee7 | ||
|
|
8f83a1d96e | ||
|
|
9cd290ab57 | ||
|
|
061ccdca33 | ||
|
|
12099d6152 | ||
|
|
b47efb7176 | ||
|
|
ee9f405033 | ||
|
|
cc272acb06 | ||
|
|
4a81e9910b | ||
|
|
d8840bf33b | ||
|
|
e882593cea | ||
|
|
7ab4e0c4b5 | ||
|
|
4aca50f67a | ||
|
|
6ef0ecb60f | ||
|
|
0ce1b02b2c | ||
|
|
abfa44b4f9 | ||
|
|
fa1432ffc8 | ||
|
|
fc142926b8 | ||
|
|
d68d3b4076 | ||
|
|
f8c3b73d4f | ||
|
|
a8e1a5a36c | ||
|
|
d421fa1497 | ||
|
|
118d02e797 | ||
|
|
c420ff7f6f | ||
|
|
7aaebf0332 | ||
|
|
2f44c2d6f8 | ||
|
|
f5f6f5d56d | ||
|
|
3e64d3b05c | ||
|
|
0429eac23e | ||
|
|
dc9b07a4b6 | ||
|
|
2b6dbf52e9 | ||
|
|
e4606a4ae3 | ||
|
|
508062768c | ||
|
|
d7dc1f27e0 | ||
|
|
4525c226c8 | ||
|
|
c09dadb199 | ||
|
|
c88e98cc24 | ||
|
|
17be602906 | ||
|
|
453e0ae5a8 | ||
|
|
08d97c2a82 | ||
|
|
e487dead95 | ||
|
|
8ce8e7dad8 | ||
|
|
41747c5dd0 | ||
|
|
1dd0349483 | ||
|
|
62415f1775 | ||
|
|
b23b90c8bf | ||
|
|
24aa6857e5 | ||
|
|
7ba2428819 | ||
|
|
f09e345908 | ||
|
|
c56f21d8ab | ||
|
|
5964c1c503 | ||
|
|
e5707a0f2e | ||
|
|
d0f1a0144e | ||
|
|
83b93b1369 | ||
|
|
39de71521b | ||
|
|
51a42eb497 | ||
|
|
ac90389aa1 |
1
.github/FUNDING.yml
vendored
Normal file
@@ -0,0 +1 @@
|
||||
github: hediet
|
||||
23
.github/workflows/ci.yml
vendored
Normal file
@@ -0,0 +1,23 @@
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- master
|
||||
pull_request:
|
||||
|
||||
jobs:
|
||||
build:
|
||||
strategy:
|
||||
matrix:
|
||||
os: [macos-latest, ubuntu-latest, windows-latest]
|
||||
runs-on: ${{ matrix.os }}
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v2
|
||||
with:
|
||||
submodules: true
|
||||
- name: Install Node.js
|
||||
uses: actions/setup-node@v1
|
||||
with:
|
||||
node-version: 10.x
|
||||
- run: yarn install
|
||||
- run: yarn build
|
||||
15
.github/workflows/opened-issues-triage.yml
vendored
Normal file
@@ -0,0 +1,15 @@
|
||||
name: Move new issues into Triage
|
||||
|
||||
on:
|
||||
issues:
|
||||
types: [opened]
|
||||
|
||||
jobs:
|
||||
automate-project-columns:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: alex-page/github-project-automation-plus@v0.2.3
|
||||
with:
|
||||
project: Backlog
|
||||
column: Triage
|
||||
repo-token: ${{ secrets.GH_TOKEN }}
|
||||
16
.vscode/launch.json
vendored
@@ -15,7 +15,7 @@
|
||||
"runtimeExecutable": "${execPath}",
|
||||
"args": [
|
||||
"--extensionDevelopmentPath=${workspaceFolder}/extension",
|
||||
"${workspaceFolder}\\demos\\php"
|
||||
"${workspaceFolder}/demos/php"
|
||||
],
|
||||
"env": {
|
||||
"HOT_RELOAD": "true",
|
||||
@@ -31,7 +31,7 @@
|
||||
"runtimeExecutable": "${execPath}",
|
||||
"args": [
|
||||
"--extensionDevelopmentPath=${workspaceFolder}/extension",
|
||||
"${workspaceFolder}\\demos\\php"
|
||||
"${workspaceFolder}/demos/php"
|
||||
],
|
||||
"env": {
|
||||
"HOT_RELOAD": "true",
|
||||
@@ -47,7 +47,7 @@
|
||||
"runtimeExecutable": "${execPath}",
|
||||
"args": [
|
||||
"--extensionDevelopmentPath=${workspaceFolder}/extension",
|
||||
"${workspaceFolder}\\demos\\js"
|
||||
"${workspaceFolder}/demos/js"
|
||||
],
|
||||
"env": {
|
||||
"HOT_RELOAD": "",
|
||||
@@ -62,7 +62,7 @@
|
||||
"runtimeExecutable": "${execPath}",
|
||||
"args": [
|
||||
"--extensionDevelopmentPath=${workspaceFolder}/extension",
|
||||
"${workspaceFolder}\\demos\\php"
|
||||
"${workspaceFolder}/demos/js"
|
||||
],
|
||||
"env": {
|
||||
"HOT_RELOAD": "",
|
||||
@@ -70,6 +70,12 @@
|
||||
},
|
||||
"outFiles": ["${workspaceFolder}/extension/dist/**/*.js"],
|
||||
"preLaunchTask": "npm: dev - extension"
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "dart",
|
||||
"request": "launch",
|
||||
"name": "Run Dart samples",
|
||||
"program": "demos/dart/demo.dart"
|
||||
},
|
||||
]
|
||||
}
|
||||
|
||||
8
.vscode/settings.json
vendored
@@ -7,5 +7,11 @@
|
||||
"plantuml.exportOutDir": "docs/exported",
|
||||
"typescript.updateImportsOnFileMove.enabled": "prompt",
|
||||
"mochaExplorer.files": "./data-extraction/test/**/*.test.ts",
|
||||
"mochaExplorer.require": ["ts-node/register", "source-map-support/register"]
|
||||
"mochaExplorer.require": [
|
||||
"ts-node/register",
|
||||
"source-map-support/register"
|
||||
],
|
||||
"tasksStatusbar.taskLabelFilter": "dev",
|
||||
"editor.formatOnSave": true,
|
||||
"typescript.tsdk": "node_modules\\typescript\\lib"
|
||||
}
|
||||
|
||||
@@ -42,16 +42,10 @@ Implements the UI and is hosted inside a webview in VS Code.
|
||||
Can be opened in a browser window.
|
||||
Uses websockets and JSON RPC to communicate with the extension.
|
||||
|
||||
### visualization
|
||||
### hediet/visualization
|
||||
|
||||
Contains React components that visualize various JSON documents.
|
||||
Also provides infrastructure for selecting the best visualization for a given JSON document.
|
||||
|
||||
### visualization-playground
|
||||
|
||||
A playground to test and play with the visualizations for the web.
|
||||
Is hosted at [https://hediet.github.io/vscode-debug-visualizer/](https://hediet.github.io/vscode-debug-visualizer/).
|
||||
Aims to have fast build times.
|
||||
Contains all the visualizers and visualization infrastructure.
|
||||
This external project can be found [here](https://github.com/hediet/visualization).
|
||||
|
||||
### extension
|
||||
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
# VS Code Debug Visualizer
|
||||
|
||||
[](https://github.com/sponsors/hediet)
|
||||
[](https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=ZP5F38L4C88UY&source=url)
|
||||
[](https://twitter.com/intent/follow?screen_name=hediet_dev)
|
||||
|
||||
See [README.md](./extension/README.md) for the readme of the extension.
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "@hediet/debug-visualizer-data-extraction",
|
||||
"description": "A library that helps implementing data extractors for the Debug Visualizer VS Code extension.",
|
||||
"version": "0.12.0",
|
||||
"version": "0.14.0",
|
||||
"main": "dist/index.js",
|
||||
"types": "dist/index.d.ts",
|
||||
"author": {
|
||||
@@ -29,17 +29,17 @@
|
||||
"dependencies": {},
|
||||
"devDependencies": {
|
||||
"@types/mocha": "^7.0.2",
|
||||
"@types/node": "^13.7.4",
|
||||
"@types/plotly.js": "1.44.28",
|
||||
"coveralls": "^3.0.11",
|
||||
"mocha": "^7.1.1",
|
||||
"mocha-lcov-reporter": "^1.3.0",
|
||||
"nyc": "^15.0.0",
|
||||
"source-map-support": "^0.5.16",
|
||||
"typescript": "^3.8.2",
|
||||
"@types/plotly.js": "1.44.28",
|
||||
"@types/node": "^13.7.4",
|
||||
"ts-loader": "^6.2.1",
|
||||
"tslint": "^6.0.0",
|
||||
"typescript": "^3.8.2",
|
||||
"webpack": "^4.41.6",
|
||||
"webpack-cli": "^3.3.11",
|
||||
"ts-loader": "^6.2.1"
|
||||
"webpack-cli": "^3.3.11"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,122 +1,301 @@
|
||||
import { ExtractedData } from "./DataExtractionResult";
|
||||
// This file was created automatically. Do not edit it manually!
|
||||
|
||||
export type CommonDataType =
|
||||
| CommonDataTypes.Text
|
||||
| CommonDataTypes.Svg
|
||||
| CommonDataTypes.Html
|
||||
| CommonDataTypes.DotGraph
|
||||
| CommonDataTypes.Tree
|
||||
| CommonDataTypes.Ast
|
||||
| CommonDataTypes.Graph
|
||||
| CommonDataTypes.Plotly
|
||||
| CommonDataTypes.Grid;
|
||||
export type KnownVisualizationData =
|
||||
| TreeVisualizationData
|
||||
| AstTreeVisualizationData
|
||||
| GraphvizDotVisualizationData
|
||||
| GraphVisualizationData
|
||||
| GraphVisualizationData
|
||||
| GridVisualizationData
|
||||
| ImageVisualizationData
|
||||
| MonacoTextVisualizationData
|
||||
| MonacoTextDiffVisualizationData
|
||||
| TableVisualizationData
|
||||
| PlotlyVisualizationData
|
||||
| SimpleTextVisualizationData
|
||||
| SvgVisualizationData;
|
||||
|
||||
export function isCommonDataType<T>(
|
||||
data: ExtractedData,
|
||||
kind: T
|
||||
): data is Narrow<CommonDataType, { kind: T }> {
|
||||
for (const key of Object.keys(kind)) {
|
||||
if (!(key in data.kind)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
export type TreeVisualizationData = {
|
||||
kind: {
|
||||
tree: true;
|
||||
};
|
||||
root: TreeNode;
|
||||
};
|
||||
|
||||
type Narrow<T, TKind> = T extends TKind ? T : never;
|
||||
|
||||
export module CommonDataTypes {
|
||||
export interface Text {
|
||||
kind: { text: true };
|
||||
text: string;
|
||||
mimeType?: string;
|
||||
fileName?: string;
|
||||
}
|
||||
|
||||
export interface Graph {
|
||||
kind: { graph: true };
|
||||
nodes: NodeGraphData[];
|
||||
edges: EdgeGraphData[];
|
||||
}
|
||||
|
||||
export interface Svg extends Text {
|
||||
kind: { text: true; svg: true };
|
||||
}
|
||||
|
||||
export interface Html extends Text {
|
||||
kind: { text: true; html: true };
|
||||
}
|
||||
|
||||
export interface DotGraph extends Text {
|
||||
kind: { text: true; dotGraph: true };
|
||||
}
|
||||
|
||||
export interface Tree<TData = unknown> {
|
||||
kind: { tree: true };
|
||||
root: TreeNode<TData>;
|
||||
}
|
||||
|
||||
export interface Grid {
|
||||
kind: { array: true };
|
||||
columnLabels?: { label?: string }[];
|
||||
rows: {
|
||||
label?: string;
|
||||
columns: {
|
||||
content?: string;
|
||||
tag?: string;
|
||||
color?: string;
|
||||
}[];
|
||||
}[];
|
||||
markers?: {
|
||||
id: string;
|
||||
|
||||
row: number;
|
||||
column: number;
|
||||
rows?: number;
|
||||
columns?: number;
|
||||
|
||||
label?: string;
|
||||
color?: string;
|
||||
}[];
|
||||
}
|
||||
|
||||
export interface Ast
|
||||
extends Tree<{
|
||||
position: number;
|
||||
length: number;
|
||||
}>,
|
||||
Text {
|
||||
kind: { text: true; tree: true; ast: true };
|
||||
}
|
||||
|
||||
export interface Plotly {
|
||||
kind: { plotly: true };
|
||||
data: Partial<Plotly.Data>[];
|
||||
}
|
||||
}
|
||||
|
||||
export interface TreeNode<TExtraData> {
|
||||
name: string;
|
||||
children: TreeNode<TExtraData>[];
|
||||
data: TExtraData;
|
||||
id?: string;
|
||||
value?: string;
|
||||
emphasizedValue?: string;
|
||||
export type TreeNode = {
|
||||
/**
|
||||
* The children of this tree-node
|
||||
*/
|
||||
children: TreeNode[];
|
||||
/**
|
||||
* The parts that make up the text of this item
|
||||
*/
|
||||
items: TreeNodeItem[];
|
||||
/**
|
||||
* If a node is selected, the concatenation of all segment values from root to the selected node is shown to the user.
|
||||
*/
|
||||
segment?: string;
|
||||
/**
|
||||
* Marked nodes are highlighted and scrolled into view on every visualization update.
|
||||
*/
|
||||
isMarked?: boolean;
|
||||
}
|
||||
};
|
||||
|
||||
export interface NodeGraphData {
|
||||
export type TreeNodeItem = {
|
||||
/**
|
||||
* The text to show
|
||||
*/
|
||||
text: string;
|
||||
/**
|
||||
* The style of the text
|
||||
*/
|
||||
emphasis?: "style1" | "style2" | "style3" | string;
|
||||
};
|
||||
|
||||
export type AstTreeVisualizationData = {
|
||||
kind: {
|
||||
ast: true;
|
||||
tree: true;
|
||||
text: true;
|
||||
};
|
||||
root: AstTreeNode;
|
||||
text: string;
|
||||
fileName?: string;
|
||||
};
|
||||
|
||||
export type AstTreeNode = {
|
||||
children: AstTreeNode[];
|
||||
items: AstTreeNodeItem[];
|
||||
segment?: string;
|
||||
isMarked?: boolean;
|
||||
span: {
|
||||
start: number;
|
||||
length: number;
|
||||
};
|
||||
};
|
||||
|
||||
export type AstTreeNodeItem = {
|
||||
text: string;
|
||||
emphasis?: "style1" | "style2" | "style3" | string;
|
||||
};
|
||||
|
||||
export type GraphvizDotVisualizationData = {
|
||||
kind: {
|
||||
dotGraph: true;
|
||||
};
|
||||
text: string;
|
||||
};
|
||||
|
||||
export type GraphVisualizationData = {
|
||||
kind: {
|
||||
graph: true;
|
||||
};
|
||||
nodes: GraphNode[];
|
||||
edges: GraphEdge[];
|
||||
};
|
||||
|
||||
export type GraphNode = {
|
||||
id: string;
|
||||
label?: string;
|
||||
color?: string;
|
||||
shape?: "ellipse" | "box";
|
||||
}
|
||||
};
|
||||
|
||||
export interface EdgeGraphData {
|
||||
export type GraphEdge = {
|
||||
from: string;
|
||||
to: string;
|
||||
label?: string;
|
||||
id?: string;
|
||||
color?: string;
|
||||
dashes?: boolean;
|
||||
}
|
||||
style?: "solid" | "dashed" | "dotted";
|
||||
};
|
||||
|
||||
export type GridVisualizationData = {
|
||||
kind: {
|
||||
grid: true;
|
||||
};
|
||||
columnLabels?: {
|
||||
label?: string;
|
||||
}[];
|
||||
rows: {
|
||||
label?: string;
|
||||
columns: {
|
||||
content?: string;
|
||||
/**
|
||||
* A value to identify this cell. Should be unique.
|
||||
*/
|
||||
tag?: string;
|
||||
color?: string;
|
||||
}[];
|
||||
}[];
|
||||
markers?: {
|
||||
id: string;
|
||||
row: number;
|
||||
column: number;
|
||||
rows?: number;
|
||||
columns?: number;
|
||||
label?: string;
|
||||
color?: string;
|
||||
}[];
|
||||
};
|
||||
|
||||
export type ImageVisualizationData = {
|
||||
kind: {
|
||||
imagePng: true;
|
||||
};
|
||||
/**
|
||||
* The base 64 encoded PNG representation of the image
|
||||
*/
|
||||
base64Data: string;
|
||||
};
|
||||
|
||||
export type MonacoTextVisualizationData = {
|
||||
kind: {
|
||||
text: true;
|
||||
};
|
||||
/**
|
||||
* 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;
|
||||
};
|
||||
/**
|
||||
* The text to show
|
||||
*/
|
||||
text: string;
|
||||
/**
|
||||
* The text to compare against
|
||||
*/
|
||||
otherText: string;
|
||||
/**
|
||||
* An optional filename that might be used for chosing a syntax highlighter
|
||||
*/
|
||||
fileName?: string;
|
||||
};
|
||||
|
||||
export type TableVisualizationData = {
|
||||
kind: {
|
||||
table: true;
|
||||
};
|
||||
/**
|
||||
* An array of objects. The properties of the objects are used as columns.
|
||||
*/
|
||||
rows: {}[];
|
||||
};
|
||||
|
||||
export type PlotlyVisualizationData = {
|
||||
kind: {
|
||||
plotly: true;
|
||||
};
|
||||
/**
|
||||
* Expecting Plotly.Data[] (https://github.com/DefinitelyTyped/DefinitelyTyped/blob/795ce172038dbafcb9cba030d637d733a7eea19c/types/plotly.js/index.d.ts#L1036)
|
||||
*/
|
||||
data: {
|
||||
text?: string | string[];
|
||||
xaxis?: string;
|
||||
yaxis?: string;
|
||||
x?: (string | number | null)[] | (string | number | null)[][];
|
||||
y?: (string | number | null)[] | (string | number | null)[][];
|
||||
z?: (string | number | null)[] | (string | number | null)[][];
|
||||
type?:
|
||||
| "bar"
|
||||
| "box"
|
||||
| "candlestick"
|
||||
| "choropleth"
|
||||
| "contour"
|
||||
| "heatmap"
|
||||
| "histogram"
|
||||
| "indicator"
|
||||
| "mesh3d"
|
||||
| "ohlc"
|
||||
| "parcoords"
|
||||
| "pie"
|
||||
| "pointcloud"
|
||||
| "scatter"
|
||||
| "scatter3d"
|
||||
| "scattergeo"
|
||||
| "scattergl"
|
||||
| "scatterpolar"
|
||||
| "scatterternary"
|
||||
| "sunburst"
|
||||
| "surface"
|
||||
| "treemap"
|
||||
| "waterfall"
|
||||
| "funnel"
|
||||
| "funnelarea"
|
||||
| "scattermapbox";
|
||||
mode?:
|
||||
| "lines"
|
||||
| "markers"
|
||||
| "text"
|
||||
| "lines+markers"
|
||||
| "text+markers"
|
||||
| "text+lines"
|
||||
| "text+lines+markers"
|
||||
| "none"
|
||||
| "gauge"
|
||||
| "number"
|
||||
| "delta"
|
||||
| "number+delta"
|
||||
| "gauge+number"
|
||||
| "gauge+number+delta"
|
||||
| "gauge+delta";
|
||||
}[];
|
||||
/**
|
||||
* Expecting Partial<Plotly.Layout> (https://github.com/DefinitelyTyped/DefinitelyTyped/blob/795ce172038dbafcb9cba030d637d733a7eea19c/types/plotly.js/index.d.ts#L329)
|
||||
*/
|
||||
layout?: {
|
||||
title?: string;
|
||||
};
|
||||
};
|
||||
|
||||
export type SimpleTextVisualizationData = {
|
||||
kind: {
|
||||
text: true;
|
||||
};
|
||||
text: string;
|
||||
};
|
||||
|
||||
export type SvgVisualizationData = {
|
||||
kind: {
|
||||
svg: true;
|
||||
};
|
||||
/**
|
||||
* The svg content
|
||||
*/
|
||||
text: string;
|
||||
};
|
||||
|
||||
@@ -1,27 +1,27 @@
|
||||
export type DataExtractionResult = {
|
||||
data: ExtractedData;
|
||||
export interface DataExtractionResult {
|
||||
data: VisualizationData;
|
||||
usedExtractor: DataExtractorInfo;
|
||||
availableExtractors: DataExtractorInfo[];
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Instances must be valid json values.
|
||||
*/
|
||||
export type ExtractedData = {
|
||||
export interface VisualizationData {
|
||||
kind: Record<string, true>;
|
||||
};
|
||||
}
|
||||
|
||||
export type DataExtractorInfo = {
|
||||
export interface DataExtractorInfo {
|
||||
id: DataExtractorId;
|
||||
name: string;
|
||||
priority: number;
|
||||
};
|
||||
}
|
||||
|
||||
export type DataExtractorId = {
|
||||
__brand: "DataExtractorId";
|
||||
} & string;
|
||||
|
||||
export function isExtractedData(val: unknown): val is ExtractedData {
|
||||
export function isVisualizationData(val: unknown): val is VisualizationData {
|
||||
if (typeof val !== "object" || !val || !("kind" in val)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import {
|
||||
ExtractedData,
|
||||
VisualizationData,
|
||||
DataExtractionResult,
|
||||
} from "../../DataExtractionResult";
|
||||
|
||||
@@ -7,14 +7,12 @@ export interface DataExtractorApi {
|
||||
/**
|
||||
* Registers a single extractor.
|
||||
*/
|
||||
registerExtractor<TExtractedData extends ExtractedData>(
|
||||
extractor: DataExtractor<TExtractedData>
|
||||
): void;
|
||||
registerExtractor(extractor: DataExtractor): void;
|
||||
|
||||
/**
|
||||
* Registers multiple extractors.
|
||||
*/
|
||||
registerExtractors(extractors: DataExtractor<ExtractedData>[]): void;
|
||||
registerExtractors(extractors: DataExtractor[]): void;
|
||||
|
||||
/**
|
||||
* Extracts data from the result of `valueFn`.
|
||||
@@ -24,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>;
|
||||
|
||||
/**
|
||||
@@ -46,23 +45,23 @@ export interface JSONString<T> extends String {
|
||||
__brand: { json: T };
|
||||
}
|
||||
|
||||
export interface DataExtractor<T extends ExtractedData> {
|
||||
export interface DataExtractor {
|
||||
/**
|
||||
* Must be unique among all data extractors.
|
||||
*/
|
||||
id: string;
|
||||
getExtractions(
|
||||
data: unknown,
|
||||
extractionCollector: ExtractionCollector<T>,
|
||||
extractionCollector: ExtractionCollector,
|
||||
context: DataExtractorContext
|
||||
): void;
|
||||
}
|
||||
|
||||
export interface ExtractionCollector<T extends ExtractedData> {
|
||||
export interface ExtractionCollector {
|
||||
/**
|
||||
* Suggests a possible extraction.
|
||||
*/
|
||||
addExtraction(extraction: DataExtraction<T>): void;
|
||||
addExtraction(extraction: DataExtraction): void;
|
||||
}
|
||||
|
||||
export interface DataExtractorContext {
|
||||
@@ -70,9 +69,11 @@ 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<TData extends ExtractedData> {
|
||||
export interface DataExtraction {
|
||||
/**
|
||||
* Higher priorities are preferred.
|
||||
*/
|
||||
@@ -87,5 +88,5 @@ export interface DataExtraction<TData extends ExtractedData> {
|
||||
* A user friendly name of this extraction.
|
||||
*/
|
||||
name: string;
|
||||
extractData(): TData;
|
||||
extractData(): VisualizationData;
|
||||
}
|
||||
|
||||
@@ -6,7 +6,7 @@ import {
|
||||
DataExtraction,
|
||||
ExtractionCollector,
|
||||
} from "./DataExtractorApi";
|
||||
import { ExtractedData, DataExtractorInfo } from "../../DataExtractionResult";
|
||||
import { DataExtractorInfo } from "../../DataExtractionResult";
|
||||
import { registerDefaultExtractors } from "./default-extractors";
|
||||
|
||||
/**
|
||||
@@ -14,25 +14,21 @@ 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<ExtractedData>
|
||||
>();
|
||||
private readonly extractors = new Map<string, DataExtractor>();
|
||||
|
||||
private toJson<TData>(data: TData): JSONString<TData> {
|
||||
return JSON.stringify(data) as any;
|
||||
}
|
||||
|
||||
public registerExtractor<TExtractedData extends ExtractedData>(
|
||||
extractor: DataExtractor<TExtractedData>
|
||||
): void {
|
||||
public registerExtractor(extractor: DataExtractor): void {
|
||||
this.extractors.set(extractor.id, extractor);
|
||||
}
|
||||
|
||||
public registerExtractors(
|
||||
extractors: DataExtractor<ExtractedData>[]
|
||||
): void {
|
||||
public registerExtractors(extractors: DataExtractor[]): void {
|
||||
for (const e of extractors) {
|
||||
this.registerExtractor(e);
|
||||
}
|
||||
@@ -41,22 +37,30 @@ 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<ExtractedData>>();
|
||||
const extractionCollector: ExtractionCollector<ExtractedData> = {
|
||||
const extractions = new Array<DataExtraction>();
|
||||
const extractionCollector: ExtractionCollector = {
|
||||
addExtraction(extraction) {
|
||||
extractions.push(extraction);
|
||||
},
|
||||
};
|
||||
|
||||
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) {
|
||||
@@ -72,9 +76,7 @@ export class DataExtractorApiImpl implements DataExtractorApi {
|
||||
}
|
||||
}
|
||||
|
||||
function mapExtractor(
|
||||
e: DataExtraction<ExtractedData>
|
||||
): DataExtractorInfo {
|
||||
function mapExtractor(e: DataExtraction): DataExtractorInfo {
|
||||
return {
|
||||
id: e.id as any,
|
||||
name: e.name,
|
||||
|
||||
7
data-extraction/src/js/api/LoadDataExtractorsFn.ts
Normal file
@@ -0,0 +1,7 @@
|
||||
import { DataExtractor } from "./DataExtractorApi";
|
||||
import type * as helpersTypes from "../helpers"
|
||||
|
||||
export type LoadDataExtractorsFn = (
|
||||
register: (extractor: DataExtractor) => void,
|
||||
helpers: typeof helpersTypes
|
||||
) => void;
|
||||
@@ -1,18 +1,18 @@
|
||||
import { ExtractedData, isExtractedData } from "../../../DataExtractionResult";
|
||||
import { isVisualizationData } from "../../../DataExtractionResult";
|
||||
import {
|
||||
DataExtractor,
|
||||
ExtractionCollector,
|
||||
DataExtractorContext,
|
||||
} from "../..";
|
||||
|
||||
export class AsIsDataExtractor implements DataExtractor<ExtractedData> {
|
||||
export class AsIsDataExtractor implements DataExtractor {
|
||||
readonly id = "as-is";
|
||||
getExtractions(
|
||||
data: unknown,
|
||||
extractionCollector: ExtractionCollector<ExtractedData>,
|
||||
extractionCollector: ExtractionCollector,
|
||||
context: DataExtractorContext
|
||||
): void {
|
||||
if (!isExtractedData(data)) {
|
||||
if (!isVisualizationData(data)) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,32 +1,33 @@
|
||||
import { ExtractedData } from "../../../DataExtractionResult";
|
||||
import { VisualizationData } from "../../../DataExtractionResult";
|
||||
import {
|
||||
DataExtractor,
|
||||
ExtractionCollector,
|
||||
DataExtractorContext,
|
||||
} from "../DataExtractorApi";
|
||||
|
||||
export class GetDebugVisualizationDataExtractor
|
||||
implements DataExtractor<ExtractedData> {
|
||||
readonly id = "get-debug-visualization";
|
||||
export class GetVisualizationDataExtractor implements DataExtractor {
|
||||
readonly id = "get-visualization-data";
|
||||
getExtractions(
|
||||
data: unknown,
|
||||
collector: ExtractionCollector<ExtractedData>,
|
||||
collector: ExtractionCollector,
|
||||
context: DataExtractorContext
|
||||
): void {
|
||||
if (
|
||||
typeof data !== "object" ||
|
||||
!data ||
|
||||
!("getDebugVisualization" in data)
|
||||
) {
|
||||
if (typeof data !== "object" || !data) {
|
||||
return;
|
||||
}
|
||||
|
||||
const getVisualizationData = (data as any)
|
||||
.getVisualizationData as Function;
|
||||
if (typeof getVisualizationData !== "function") {
|
||||
return;
|
||||
}
|
||||
|
||||
collector.addExtraction({
|
||||
id: this.id,
|
||||
name: "Use Method 'getDebugVisualization'",
|
||||
name: "Use Method 'getVisualizationData'",
|
||||
priority: 600,
|
||||
extractData() {
|
||||
return (data as any).getDebugVisualization();
|
||||
return getVisualizationData.apply(data);
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1,16 +1,16 @@
|
||||
import { ExtractedData } from "../../../DataExtractionResult";
|
||||
import {
|
||||
DataExtractor,
|
||||
ExtractionCollector,
|
||||
DataExtractorContext,
|
||||
} from "../..";
|
||||
import { CommonDataTypes } from "../../../CommonDataTypes";
|
||||
import { GridVisualizationData } from "../../../CommonDataTypes";
|
||||
import { expect } from "../../../util";
|
||||
|
||||
export class GridExtractor implements DataExtractor<ExtractedData> {
|
||||
export class GridExtractor implements DataExtractor {
|
||||
readonly id = "grid";
|
||||
getExtractions(
|
||||
data: unknown,
|
||||
extractionCollector: ExtractionCollector<ExtractedData>,
|
||||
extractionCollector: ExtractionCollector,
|
||||
context: DataExtractorContext
|
||||
): void {
|
||||
if (!Array.isArray(data)) {
|
||||
@@ -21,12 +21,11 @@ export class GridExtractor implements DataExtractor<ExtractedData> {
|
||||
id: this.id,
|
||||
name: "Array As Grid",
|
||||
priority: 500,
|
||||
extractData(): CommonDataTypes.Grid {
|
||||
return {
|
||||
kind: { array: true },
|
||||
rows: [{ columns: data.map(d => ({ tag: d })) }],
|
||||
};
|
||||
},
|
||||
extractData: () =>
|
||||
expect<GridVisualizationData>({
|
||||
kind: { grid: true },
|
||||
rows: [{ columns: data.map(d => ({ tag: "" + d })) }],
|
||||
}),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,34 @@
|
||||
import {
|
||||
DataExtractor,
|
||||
ExtractionCollector,
|
||||
DataExtractorContext,
|
||||
LoadDataExtractorsFn,
|
||||
} from "..";
|
||||
import * as helpers from "../../helpers";
|
||||
|
||||
export class InjectedExtractor implements DataExtractor {
|
||||
readonly id = "injected-extractor";
|
||||
|
||||
getExtractions(
|
||||
data: unknown,
|
||||
extractionCollector: ExtractionCollector,
|
||||
context: DataExtractorContext
|
||||
): void {
|
||||
const key = "@hediet/debug-visualizer/injectedScripts";
|
||||
const injectedExtractorsFns = Object.values(
|
||||
(global as any)[key]
|
||||
) as LoadDataExtractorsFn[];
|
||||
|
||||
const extractors = new Array<DataExtractor>();
|
||||
|
||||
for (const fn of injectedExtractorsFns) {
|
||||
fn(extractor => {
|
||||
extractors.push(extractor);
|
||||
}, helpers);
|
||||
}
|
||||
|
||||
for (const extractor of extractors) {
|
||||
extractor.getExtractions(data, extractionCollector, context);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
import { ExtractedData } from "../../../DataExtractionResult";
|
||||
import { VisualizationData } from "../../../DataExtractionResult";
|
||||
import {
|
||||
DataExtractor,
|
||||
ExtractionCollector,
|
||||
@@ -6,12 +6,12 @@ import {
|
||||
} from "../DataExtractorApi";
|
||||
import { createGraph, CreateGraphEdge } from "../../helpers";
|
||||
|
||||
export class ObjectGraphExtractor implements DataExtractor<ExtractedData> {
|
||||
export class ObjectGraphExtractor implements DataExtractor {
|
||||
readonly id = "object-graph";
|
||||
|
||||
getExtractions(
|
||||
data: unknown,
|
||||
collector: ExtractionCollector<ExtractedData>,
|
||||
collector: ExtractionCollector,
|
||||
context: DataExtractorContext
|
||||
): void {
|
||||
function isObject(val: unknown): val is object {
|
||||
|
||||
@@ -1,17 +1,17 @@
|
||||
import { ExtractedData } from "../../../DataExtractionResult";
|
||||
import { PlotlyVisualizationData } from "../../../CommonDataTypes";
|
||||
import { expect } from "../../../util";
|
||||
import {
|
||||
DataExtractor,
|
||||
ExtractionCollector,
|
||||
DataExtractorContext,
|
||||
} from "../DataExtractorApi";
|
||||
import { CommonDataTypes } from "../../../CommonDataTypes";
|
||||
|
||||
export class PlotDataExtractor implements DataExtractor<ExtractedData> {
|
||||
export class PlotlyDataExtractor implements DataExtractor {
|
||||
readonly id = "plot";
|
||||
|
||||
getExtractions(
|
||||
data: unknown,
|
||||
collector: ExtractionCollector<ExtractedData>,
|
||||
collector: ExtractionCollector,
|
||||
context: DataExtractorContext
|
||||
): void {
|
||||
if (!Array.isArray(data)) {
|
||||
@@ -25,14 +25,13 @@ export class PlotDataExtractor implements DataExtractor<ExtractedData> {
|
||||
id: "plot-y",
|
||||
name: "Plot as y-Values",
|
||||
priority: 1001,
|
||||
extractData() {
|
||||
return {
|
||||
extractData: () =>
|
||||
expect<PlotlyVisualizationData>({
|
||||
kind: {
|
||||
plotly: true,
|
||||
},
|
||||
data: [{ y: data }],
|
||||
} as CommonDataTypes.Plotly;
|
||||
},
|
||||
}),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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]
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
import { isAssertionExpression } from "typescript";
|
||||
import { DataExtractor, ExtractionCollector, DataExtractorContext } from "..";
|
||||
import { TableVisualizationData } from "../../../CommonDataTypes";
|
||||
import { expect } from "../../../util";
|
||||
|
||||
function assert<T>(value: unknown): asserts value {}
|
||||
|
||||
export class TableDataExtractor implements DataExtractor {
|
||||
readonly id = "table";
|
||||
|
||||
getExtractions(
|
||||
data: unknown,
|
||||
collector: ExtractionCollector,
|
||||
context: DataExtractorContext
|
||||
): void {
|
||||
if (!Array.isArray(data)) {
|
||||
return;
|
||||
}
|
||||
if (!data.every(d => typeof d === "object" && d)) {
|
||||
return;
|
||||
}
|
||||
assert<object[]>(data);
|
||||
|
||||
collector.addExtraction({
|
||||
id: "table",
|
||||
name: "Table",
|
||||
priority: 1000,
|
||||
extractData() {
|
||||
return expect<TableVisualizationData>({
|
||||
kind: {
|
||||
table: true,
|
||||
},
|
||||
rows: data,
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
collector.addExtraction({
|
||||
id: "table-with-type-name",
|
||||
name: "Table (With Type Name)",
|
||||
priority: 950,
|
||||
extractData() {
|
||||
return expect<TableVisualizationData>({
|
||||
kind: {
|
||||
table: true,
|
||||
},
|
||||
rows: data.map(d => ({ type: d.constructor.name, ...d })),
|
||||
});
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -1,16 +1,17 @@
|
||||
import { ExtractedData } from "../../../DataExtractionResult";
|
||||
import { MonacoTextVisualizationData } from "../../../CommonDataTypes";
|
||||
import { expect } from "../../../util";
|
||||
import {
|
||||
DataExtractor,
|
||||
ExtractionCollector,
|
||||
DataExtractorContext,
|
||||
} from "../DataExtractorApi";
|
||||
|
||||
export class ToStringDataExtractor implements DataExtractor<ExtractedData> {
|
||||
export class ToStringDataExtractor implements DataExtractor {
|
||||
readonly id = "to-string";
|
||||
|
||||
getExtractions(
|
||||
data: unknown,
|
||||
collector: ExtractionCollector<ExtractedData>,
|
||||
collector: ExtractionCollector,
|
||||
context: DataExtractorContext
|
||||
): void {
|
||||
collector.addExtraction({
|
||||
@@ -18,12 +19,12 @@ export class ToStringDataExtractor implements DataExtractor<ExtractedData> {
|
||||
name: "To String",
|
||||
priority: 100,
|
||||
extractData() {
|
||||
return {
|
||||
return expect<MonacoTextVisualizationData>({
|
||||
kind: {
|
||||
text: true,
|
||||
},
|
||||
text: "" + data,
|
||||
};
|
||||
});
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1,19 +1,19 @@
|
||||
import * as ts from "typescript"; // Only compile-time import!
|
||||
import { AstTreeNode } from "../../..";
|
||||
import { AstTreeVisualizationData } from "../../../CommonDataTypes";
|
||||
import { expect } from "../../../util";
|
||||
import {
|
||||
DataExtractor,
|
||||
ExtractionCollector,
|
||||
DataExtractorContext,
|
||||
} from "../DataExtractorApi";
|
||||
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.Ast> {
|
||||
export class TypeScriptAstDataExtractor implements DataExtractor {
|
||||
readonly id = "typescript-ast";
|
||||
|
||||
getExtractions(
|
||||
data: unknown,
|
||||
collector: ExtractionCollector<CommonDataTypes.Ast>,
|
||||
collector: ExtractionCollector,
|
||||
{ evalFn }: DataExtractorContext
|
||||
): void {
|
||||
if (!data) {
|
||||
@@ -40,114 +40,21 @@ export class TypeScriptAstDataExtractor
|
||||
return;
|
||||
}
|
||||
|
||||
function findKey(value: any, object: any): string | null {
|
||||
for (var key in object) {
|
||||
if (key.startsWith("_")) continue;
|
||||
|
||||
var member = object[key];
|
||||
if (member === value) return key;
|
||||
|
||||
if (Array.isArray(member) && member.indexOf(value) !== -1) {
|
||||
return key;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
function getChildren(node: ts.Node): ts.Node[] {
|
||||
const result = new Array<ts.Node>();
|
||||
tsApi.forEachChild(node, n => {
|
||||
result.push(n);
|
||||
});
|
||||
return result;
|
||||
}
|
||||
|
||||
function toTreeNode(
|
||||
node: ts.Node,
|
||||
memberName: string,
|
||||
marked: Set<ts.Node>,
|
||||
emphasizedValueFn: (node: ts.Node) => string | undefined
|
||||
): CommonDataTypes.Ast["root"] {
|
||||
const name = tsApi.SyntaxKind[node.kind];
|
||||
const children = getChildren(node)
|
||||
.map((childNode, idx) => {
|
||||
let parentPropertyName = findKey(childNode, node) || "";
|
||||
if (childNode.kind == tsApi.SyntaxKind.SyntaxList) {
|
||||
const children = getChildren(childNode);
|
||||
children.some(c => {
|
||||
parentPropertyName = findKey(c, node) || "";
|
||||
return !!parentPropertyName;
|
||||
});
|
||||
|
||||
if (children.length === 0) return null!;
|
||||
}
|
||||
|
||||
if (node.kind == tsApi.SyntaxKind.SyntaxList) {
|
||||
parentPropertyName = "" + idx;
|
||||
}
|
||||
|
||||
return toTreeNode(
|
||||
childNode,
|
||||
parentPropertyName,
|
||||
marked,
|
||||
emphasizedValueFn
|
||||
);
|
||||
})
|
||||
.filter(c => c !== null);
|
||||
|
||||
let value: string | undefined = undefined;
|
||||
|
||||
if (tsApi.isIdentifier(node)) {
|
||||
value = node.text || (node.escapedText as string);
|
||||
} else if (tsApi.isLiteralExpression(node)) {
|
||||
value = node.text;
|
||||
}
|
||||
|
||||
return {
|
||||
name: name,
|
||||
id: memberName,
|
||||
children: children,
|
||||
data: {
|
||||
length: node.end - node.pos,
|
||||
position: node.pos,
|
||||
},
|
||||
emphasizedValue: emphasizedValueFn(node),
|
||||
isMarked: marked.has(node),
|
||||
value,
|
||||
};
|
||||
}
|
||||
|
||||
function isNode(node: unknown): node is ts.Node {
|
||||
return (
|
||||
typeof node === "object" &&
|
||||
node !== null &&
|
||||
(tsApi.isToken(node as any) || (tsApi as any).isNode(node))
|
||||
);
|
||||
}
|
||||
|
||||
function getSourceFile(node: ts.Node | any): ts.SourceFile {
|
||||
if (!node) {
|
||||
throw new Error("Detached node");
|
||||
}
|
||||
if (tsApi.isSourceFile(node)) {
|
||||
return node;
|
||||
}
|
||||
if (!("getSourceFile" in node)) {
|
||||
return getSourceFile(node.parent);
|
||||
}
|
||||
return node.getSourceFile();
|
||||
}
|
||||
const helper = new Helper(tsApi);
|
||||
|
||||
let rootSourceFile: ts.SourceFile | undefined = undefined;
|
||||
let rootNode: ts.Node | undefined = undefined;
|
||||
let marked: Set<ts.Node>;
|
||||
let fn: (n: ts.Node) => string | undefined = (n: ts.Node) => undefined;
|
||||
if (Array.isArray(data) && data.every(isNode) && data.length > 0) {
|
||||
rootSourceFile = getSourceFile(data[0] as ts.Node);
|
||||
if (
|
||||
Array.isArray(data) &&
|
||||
data.every(helper.isNode) &&
|
||||
data.length > 0
|
||||
) {
|
||||
rootSourceFile = helper.getSourceFile(data[0] as ts.Node);
|
||||
marked = new Set(data);
|
||||
} else if (isNode(data)) {
|
||||
rootSourceFile = getSourceFile(data);
|
||||
} else if (helper.isNode(data)) {
|
||||
rootSourceFile = helper.getSourceFile(data);
|
||||
marked = new Set([data]);
|
||||
} else if (typeof data === "object" && data) {
|
||||
marked = new Set();
|
||||
@@ -162,15 +69,18 @@ export class TypeScriptAstDataExtractor
|
||||
rootNode = item;
|
||||
}
|
||||
let nodes: Array<ts.Node>;
|
||||
if (isNode(item)) {
|
||||
if (helper.isNode(item)) {
|
||||
nodes = [item];
|
||||
} else if (Array.isArray(item) && item.every(isNode)) {
|
||||
} else if (
|
||||
Array.isArray(item) &&
|
||||
item.every(helper.isNode)
|
||||
) {
|
||||
nodes = item;
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
if (nodes.length > 0 && !rootSourceFile) {
|
||||
rootSourceFile = getSourceFile(nodes[0]);
|
||||
rootSourceFile = helper.getSourceFile(nodes[0]);
|
||||
}
|
||||
for (const n of nodes) {
|
||||
marked.add(n);
|
||||
@@ -192,18 +102,147 @@ export class TypeScriptAstDataExtractor
|
||||
name: "TypeScript AST",
|
||||
priority: 1000,
|
||||
extractData() {
|
||||
return {
|
||||
return expect<AstTreeVisualizationData>({
|
||||
kind: { text: true, tree: true, ast: true },
|
||||
root: toTreeNode(
|
||||
root: helper.toTreeNode(
|
||||
rootNode || finalRootSourceFile,
|
||||
"root",
|
||||
"",
|
||||
marked,
|
||||
fn
|
||||
),
|
||||
text: finalRootSourceFile.text,
|
||||
fileName: "index.ts",
|
||||
};
|
||||
});
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
class Helper {
|
||||
constructor(private readonly tsApi: typeof ts) {}
|
||||
|
||||
getPropertyNameInParent(value: any, parent: any): string | undefined {
|
||||
for (const propertyName in parent) {
|
||||
if (propertyName.startsWith("_")) continue;
|
||||
|
||||
const member = parent[propertyName];
|
||||
if (member === value) {
|
||||
return propertyName;
|
||||
}
|
||||
|
||||
if (Array.isArray(member)) {
|
||||
const index = member.indexOf(value);
|
||||
if (index !== -1) {
|
||||
return `${propertyName}[${index}]`;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return undefined;
|
||||
}
|
||||
|
||||
getChildren(node: ts.Node): ts.Node[] {
|
||||
const result = new Array<ts.Node>();
|
||||
this.tsApi.forEachChild(node, n => {
|
||||
result.push(n);
|
||||
});
|
||||
return result;
|
||||
}
|
||||
|
||||
toTreeNode(
|
||||
node: ts.Node,
|
||||
memberName: string,
|
||||
segmentName: string,
|
||||
marked: Set<ts.Node>,
|
||||
emphasizedValueFn: (node: ts.Node) => string | undefined
|
||||
): AstTreeNode {
|
||||
const name = this.tsApi.SyntaxKind[node.kind];
|
||||
const children = this.getChildren(node)
|
||||
.map((childNode, idx) => {
|
||||
let parentPropertyName =
|
||||
this.getPropertyNameInParent(childNode, node) || "";
|
||||
|
||||
if (childNode.kind == this.tsApi.SyntaxKind.SyntaxList) {
|
||||
const children = this.getChildren(childNode);
|
||||
for (const c of children) {
|
||||
const name =
|
||||
this.getPropertyNameInParent(c, node) || "";
|
||||
if (name) {
|
||||
parentPropertyName = name;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let segmentName = "." + parentPropertyName;
|
||||
if (node.kind == this.tsApi.SyntaxKind.SyntaxList) {
|
||||
parentPropertyName = "" + idx;
|
||||
segmentName = `[${idx}]`;
|
||||
}
|
||||
|
||||
return this.toTreeNode(
|
||||
childNode,
|
||||
parentPropertyName,
|
||||
segmentName,
|
||||
marked,
|
||||
emphasizedValueFn
|
||||
);
|
||||
})
|
||||
.filter(c => c !== null);
|
||||
|
||||
let value: string | undefined = undefined;
|
||||
|
||||
if (this.tsApi.isIdentifier(node)) {
|
||||
value = node.text || (node.escapedText as string);
|
||||
} else if (this.tsApi.isLiteralExpression(node)) {
|
||||
value = node.text;
|
||||
}
|
||||
|
||||
const items: AstTreeNode["items"] = [
|
||||
{ text: `${memberName}: `, emphasis: "style1" },
|
||||
{ text: name },
|
||||
];
|
||||
|
||||
const emphasizedVal = emphasizedValueFn(node);
|
||||
if (value) {
|
||||
items.push({ text: value, emphasis: "style2" });
|
||||
}
|
||||
if (emphasizedVal) {
|
||||
items.push({ text: emphasizedVal, emphasis: "style3" });
|
||||
}
|
||||
|
||||
return {
|
||||
items,
|
||||
children: children,
|
||||
segment: segmentName,
|
||||
span: {
|
||||
length: node.end - node.pos,
|
||||
start: node.pos,
|
||||
},
|
||||
isMarked: marked.has(node),
|
||||
};
|
||||
}
|
||||
|
||||
isNode = (node: unknown): node is ts.Node => {
|
||||
return (
|
||||
typeof node === "object" &&
|
||||
node !== null &&
|
||||
(this.tsApi.isToken(node as any) ||
|
||||
(this.tsApi as any).isNode(node))
|
||||
);
|
||||
};
|
||||
|
||||
getSourceFile(node: ts.Node | any): ts.SourceFile {
|
||||
if (!node) {
|
||||
throw new Error("Detached node");
|
||||
}
|
||||
if (this.tsApi.isSourceFile(node)) {
|
||||
return node;
|
||||
}
|
||||
if (!("getSourceFile" in node)) {
|
||||
return this.getSourceFile(node.parent);
|
||||
}
|
||||
return node.getSourceFile();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,12 +1,15 @@
|
||||
import { DataExtractorApi } from "../DataExtractorApi";
|
||||
import { TypeScriptAstDataExtractor } from "./TypeScriptDataExtractors";
|
||||
import { AsIsDataExtractor } from "./AsIsDataExtractor";
|
||||
import { GetDebugVisualizationDataExtractor } from "./GetDebugVisualizationDataExtractor";
|
||||
import { GetVisualizationDataExtractor } from "./GetDebugVisualizationDataExtractor";
|
||||
import { ToStringDataExtractor } from "./ToStringExtractor";
|
||||
import { PlotDataExtractor } from "./PlotlyDataExtractor";
|
||||
import { PlotlyDataExtractor } from "./PlotlyDataExtractor";
|
||||
import { ObjectGraphExtractor } from "./ObjectGraphExtractor";
|
||||
import { getDataExtractorApi } from "../injection";
|
||||
import { GridExtractor } from "./GridExtractor";
|
||||
import { TableDataExtractor } from "./TableExtractor";
|
||||
import { StringDiffExtractor } from "./StringDiffExtractor";
|
||||
import { InjectedExtractor } from "./InjectedExtractor";
|
||||
|
||||
/**
|
||||
* The default data extractors should be registered by VS Code automatically.
|
||||
@@ -18,11 +21,14 @@ export function registerDefaultExtractors(
|
||||
for (const item of [
|
||||
new TypeScriptAstDataExtractor(),
|
||||
new AsIsDataExtractor(),
|
||||
new GetDebugVisualizationDataExtractor(),
|
||||
new GetVisualizationDataExtractor(),
|
||||
new ToStringDataExtractor(),
|
||||
new PlotDataExtractor(),
|
||||
new PlotlyDataExtractor(),
|
||||
new ObjectGraphExtractor(),
|
||||
new GridExtractor(),
|
||||
new TableDataExtractor(),
|
||||
new StringDiffExtractor(),
|
||||
new InjectedExtractor(),
|
||||
]) {
|
||||
api.registerExtractor(item);
|
||||
}
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
export * from "./DataExtractorApi";
|
||||
export * from "./injection";
|
||||
export { DataExtractorApiImpl } from "./DataExtractorApiImpl";
|
||||
export * from "./LoadDataExtractorsFn";
|
||||
|
||||
@@ -20,16 +20,18 @@ export function getExpressionToInitializeDataExtractorApi(): string {
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns standalone JS code representing an expression returns the data extraction API.
|
||||
* Returns standalone JS code representing an expression that returns the data extraction API.
|
||||
* This expression returns an object of type `DataExtractorApi`.
|
||||
* If the API must be initialized with `getExpressionToInitializeDataExtractorApi()`,
|
||||
* it throws an exception containing the text of `ApiHasNotBeenInitializedCode`.
|
||||
* This function is called in the VS Code extension, the expression is evaluated in the debugee.
|
||||
*/
|
||||
export function getExpressionForDataExtractorApi(): string {
|
||||
return `((${selfContainedGetInitializedDataExtractorApi.toString()})())`;
|
||||
}
|
||||
|
||||
export function getExpressionToDetectDataExtractorApiPresence(): string {
|
||||
return `((${selfContainedIsDataExtractorApiInitialized.toString()})())`;
|
||||
}
|
||||
|
||||
const apiKey = "@hediet/data-extractor-api/v2";
|
||||
|
||||
export function getDataExtractorApi(): DataExtractorApi {
|
||||
@@ -43,10 +45,15 @@ export function getDataExtractorApi(): DataExtractorApi {
|
||||
}
|
||||
|
||||
/**
|
||||
* This code is used to detect if the API has not been initialized yet.
|
||||
* @internal
|
||||
*/
|
||||
export const ApiHasNotBeenInitializedCode = "EgH0cybXij1jYUozyakO" as const;
|
||||
function selfContainedIsDataExtractorApiInitialized(): boolean {
|
||||
const globalObj =
|
||||
typeof window === "object" ? (window as any) : (global as any);
|
||||
const key: typeof apiKey = "@hediet/data-extractor-api/v2";
|
||||
let api: DataExtractorApi | undefined = globalObj[key];
|
||||
return api !== undefined;
|
||||
}
|
||||
|
||||
/**
|
||||
* @internal
|
||||
@@ -57,11 +64,7 @@ function selfContainedGetInitializedDataExtractorApi(): DataExtractorApi {
|
||||
const key: typeof apiKey = "@hediet/data-extractor-api/v2";
|
||||
let api: DataExtractorApi | undefined = globalObj[key];
|
||||
if (!api) {
|
||||
const code: typeof ApiHasNotBeenInitializedCode =
|
||||
"EgH0cybXij1jYUozyakO";
|
||||
throw new Error(
|
||||
`Data Extractor API has not been initialized. Code: ${code}`
|
||||
);
|
||||
throw new Error(`Data Extractor API has not been initialized.`);
|
||||
}
|
||||
return api;
|
||||
}
|
||||
|
||||
6
data-extraction/src/js/helpers/asData.ts
Normal file
@@ -0,0 +1,6 @@
|
||||
import { KnownVisualizationData } from "../../CommonDataTypes";
|
||||
import { VisualizationData } from "../../DataExtractionResult";
|
||||
|
||||
export function asData(data: KnownVisualizationData): VisualizationData {
|
||||
return data;
|
||||
}
|
||||
@@ -1,10 +1,11 @@
|
||||
import {
|
||||
CommonDataTypes,
|
||||
EdgeGraphData,
|
||||
NodeGraphData,
|
||||
GraphEdge,
|
||||
GraphNode,
|
||||
GraphVisualizationData,
|
||||
} from "../../CommonDataTypes";
|
||||
|
||||
export type CreateGraphEdge<T> = { to: T } & Omit<EdgeGraphData, "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.
|
||||
@@ -17,10 +18,10 @@ export function createGraph<T>(
|
||||
) => {
|
||||
id?: string | number;
|
||||
edges: CreateGraphEdge<T>[];
|
||||
} & Omit<NodeGraphData, "id">,
|
||||
} & Omit<GraphNode, "id">,
|
||||
options: { maxSize?: number } = {}
|
||||
): CommonDataTypes.Graph {
|
||||
const r: CommonDataTypes.Graph = {
|
||||
): GraphVisualizationData {
|
||||
const r: GraphVisualizationData = {
|
||||
kind: {
|
||||
graph: true,
|
||||
},
|
||||
@@ -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 });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { NodeGraphData, CommonDataTypes } from "../../CommonDataTypes";
|
||||
import { GraphNode, GraphVisualizationData } from "../../CommonDataTypes";
|
||||
import { CreateGraphEdge, createGraph } from "./createGraph";
|
||||
|
||||
/**
|
||||
@@ -12,8 +12,8 @@ export function createGraphFromPointers<T>(
|
||||
) => {
|
||||
id?: string | number;
|
||||
edges: CreateGraphEdge<T>[];
|
||||
} & Omit<NodeGraphData, "id">
|
||||
): CommonDataTypes.Graph {
|
||||
} & Omit<GraphNode, "id">
|
||||
): GraphVisualizationData {
|
||||
const marker = {};
|
||||
|
||||
interface Pointer {
|
||||
|
||||
34
data-extraction/src/js/helpers/find.ts
Normal 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;
|
||||
}
|
||||
@@ -3,3 +3,5 @@ export * from "./createGraphFromPointers";
|
||||
export * from "./tryEval";
|
||||
export * from "./markedGrid";
|
||||
export * from "./cache";
|
||||
export * from "./asData";
|
||||
export * from "./find";
|
||||
|
||||
@@ -1,16 +1,18 @@
|
||||
import { CommonDataTypes } from "../../CommonDataTypes";
|
||||
import { GridVisualizationData } from "../../CommonDataTypes";
|
||||
|
||||
export function markedGrid(
|
||||
arr: any[],
|
||||
marked: Record<string, number>
|
||||
): CommonDataTypes.Grid {
|
||||
arr: unknown[],
|
||||
marked: Record<string, number | undefined>
|
||||
): GridVisualizationData {
|
||||
return {
|
||||
kind: { array: true },
|
||||
rows: [{ columns: arr.map(d => ({ tag: d })) }],
|
||||
markers: Object.entries(marked).map(([key, val]) => ({
|
||||
id: key,
|
||||
row: 0,
|
||||
column: val,
|
||||
})),
|
||||
kind: { grid: true },
|
||||
rows: [{ columns: arr.map(d => ({ content: "" + d, tag: "" + d })) }],
|
||||
markers: Object.entries(marked)
|
||||
.map(([key, val]) => ({
|
||||
id: "" + key,
|
||||
row: 0,
|
||||
column: val!,
|
||||
}))
|
||||
.filter(c => c.column !== undefined),
|
||||
};
|
||||
}
|
||||
|
||||
3
data-extraction/src/util.ts
Normal file
@@ -0,0 +1,3 @@
|
||||
export function expect<T>(data: T): T {
|
||||
return data;
|
||||
}
|
||||
85
demos/dart/debug_visualizers.dart
Normal file
@@ -0,0 +1,85 @@
|
||||
import 'dart:convert';
|
||||
|
||||
String graph(List<GraphNode> nodes, List<GraphEdge> edges) {
|
||||
return jsonEncode({
|
||||
'kind': {'graph': true},
|
||||
'nodes': nodes,
|
||||
'edges': edges,
|
||||
});
|
||||
}
|
||||
|
||||
String plotly(List<num> values) {
|
||||
return jsonEncode({
|
||||
'kind': {'plotly': true},
|
||||
'data': [
|
||||
{'y': values}
|
||||
],
|
||||
});
|
||||
}
|
||||
|
||||
String tree(TreeNode root) {
|
||||
return jsonEncode({
|
||||
'kind': {'tree': true},
|
||||
'root': root,
|
||||
});
|
||||
}
|
||||
|
||||
class Graph {
|
||||
List<GraphNode> nodes;
|
||||
List<GraphEdge> edges;
|
||||
}
|
||||
|
||||
class GraphEdge {
|
||||
final String from;
|
||||
final String to;
|
||||
final String label;
|
||||
|
||||
GraphEdge(this.from, this.to, this.label);
|
||||
|
||||
Map<String, dynamic> toJson() => {
|
||||
'from': from,
|
||||
'to': to,
|
||||
'label': label,
|
||||
};
|
||||
}
|
||||
|
||||
class GraphNode {
|
||||
final String id;
|
||||
final String label;
|
||||
|
||||
GraphNode(
|
||||
this.id,
|
||||
this.label,
|
||||
);
|
||||
|
||||
Map<String, dynamic> toJson() => {
|
||||
'id': id,
|
||||
'label': label,
|
||||
};
|
||||
}
|
||||
|
||||
class TreeNode {
|
||||
final List<TreeNode> children;
|
||||
final List<TreeNodeItem> items;
|
||||
final String segment;
|
||||
final bool isMarked;
|
||||
|
||||
TreeNode(this.children, this.items, this.segment, [this.isMarked = false]);
|
||||
|
||||
Map<String, dynamic> toJson() => {
|
||||
'children': children ?? [],
|
||||
'items': items,
|
||||
'segment': segment,
|
||||
'isMarked': isMarked,
|
||||
};
|
||||
}
|
||||
|
||||
class TreeNodeItem {
|
||||
final String text;
|
||||
|
||||
TreeNodeItem(this.text);
|
||||
|
||||
Map<String, dynamic> toJson() => {
|
||||
'text': text,
|
||||
};
|
||||
}
|
||||
83
demos/dart/demo.dart
Normal file
@@ -0,0 +1,83 @@
|
||||
import 'dart:collection';
|
||||
import 'dart:developer';
|
||||
import 'dart:math';
|
||||
|
||||
// ignore: unused_import
|
||||
import 'debug_visualizers.dart';
|
||||
|
||||
final _rnd = Random();
|
||||
|
||||
String Function() visualize;
|
||||
|
||||
void main() {
|
||||
debugger();
|
||||
// Open a Debug Visualizer window and evalute the expression "visualize()"
|
||||
// and then press Continue to step through the samples.
|
||||
plotly_demo();
|
||||
graph_demo();
|
||||
tree_demo();
|
||||
}
|
||||
|
||||
void plotly_demo() {
|
||||
final values = <int>[];
|
||||
visualize = () => plotly(values);
|
||||
for (var i = 0; i < 20; i++) {
|
||||
debugger();
|
||||
values.add(i + _rnd.nextInt(5) - 2);
|
||||
}
|
||||
}
|
||||
|
||||
void graph_demo() {
|
||||
graphFromDoubleLinked<T>(
|
||||
DoubleLinkedQueue<T> values, String Function(T) toString) {
|
||||
node(T s) => GraphNode(toString(s), toString(s));
|
||||
edge(DoubleLinkedQueueEntry<T> e) => [
|
||||
if (e.nextEntry() != null)
|
||||
GraphEdge(
|
||||
toString(e.element),
|
||||
toString(e.nextEntry().element),
|
||||
"next",
|
||||
),
|
||||
if (e.previousEntry() != null)
|
||||
GraphEdge(
|
||||
toString(e.element),
|
||||
toString(e.previousEntry().element),
|
||||
"prev",
|
||||
),
|
||||
];
|
||||
|
||||
final nodes = values.map(node).toList();
|
||||
final edges = <GraphEdge>[];
|
||||
values.forEachEntry((e) => edges.addAll(edge(e)));
|
||||
return graph(nodes, edges);
|
||||
}
|
||||
|
||||
final values = DoubleLinkedQueue<String>();
|
||||
visualize = () => graphFromDoubleLinked(values, (s) => s);
|
||||
|
||||
for (var i = 0; i < 10; i++) {
|
||||
debugger();
|
||||
values.addLast('Node $i');
|
||||
}
|
||||
}
|
||||
|
||||
void tree_demo() {
|
||||
var nodeNum = 1;
|
||||
TreeNode createNode(int levels) {
|
||||
final name = 'Node ${nodeNum++}';
|
||||
final children =
|
||||
levels > 0 ? List.generate(2, (_) => createNode(levels - 1)) : null;
|
||||
return TreeNode(children, [TreeNodeItem(name)], name);
|
||||
}
|
||||
|
||||
var levels = 0;
|
||||
visualize = () {
|
||||
nodeNum = 1;
|
||||
return tree(createNode(levels));
|
||||
};
|
||||
|
||||
for (var i = 1; i < 5; i++) {
|
||||
debugger();
|
||||
levels = i;
|
||||
}
|
||||
}
|
||||
@@ -1,17 +1,28 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<projectDescription>
|
||||
<name>java</name>
|
||||
<comment/>
|
||||
<projects>
|
||||
</projects>
|
||||
<buildSpec>
|
||||
<buildCommand>
|
||||
<name>org.eclipse.jdt.core.javabuilder</name>
|
||||
<arguments>
|
||||
</arguments>
|
||||
</buildCommand>
|
||||
</buildSpec>
|
||||
<natures>
|
||||
<nature>org.eclipse.jdt.core.javanature</nature>
|
||||
</natures>
|
||||
</projectDescription>
|
||||
<name>java</name>
|
||||
<comment></comment>
|
||||
<projects>
|
||||
</projects>
|
||||
<buildSpec>
|
||||
<buildCommand>
|
||||
<name>org.eclipse.jdt.core.javabuilder</name>
|
||||
<arguments>
|
||||
</arguments>
|
||||
</buildCommand>
|
||||
</buildSpec>
|
||||
<natures>
|
||||
<nature>org.eclipse.jdt.core.javanature</nature>
|
||||
</natures>
|
||||
<filteredResources>
|
||||
<filter>
|
||||
<id>1599063072264</id>
|
||||
<name></name>
|
||||
<type>30</type>
|
||||
<matcher>
|
||||
<id>org.eclipse.core.resources.regexFilterMatcher</id>
|
||||
<arguments>node_modules|.git|__CREATED_BY_JAVA_LANGUAGE_SERVER__</arguments>
|
||||
</matcher>
|
||||
</filter>
|
||||
</filteredResources>
|
||||
</projectDescription>
|
||||
|
||||
8
demos/js/.vscode/settings.json
vendored
Normal file
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"editor.minimap.enabled": false,
|
||||
"git.enabled": false,
|
||||
"typescript.tsc.autoDetect": "off",
|
||||
"debugVisualizer.js.customScriptPaths": [
|
||||
"${workspaceFolder}/custom-visualizer.js",
|
||||
]
|
||||
}
|
||||
152
demos/js/custom-visualizer.js
Normal file
@@ -0,0 +1,152 @@
|
||||
// @ts-check
|
||||
/**
|
||||
* @type {import("@hediet/debug-visualizer-data-extraction").LoadDataExtractorsFn}
|
||||
*/
|
||||
module.exports = (register, helpers) => {
|
||||
register({
|
||||
id: "map",
|
||||
getExtractions(data, collector, context) {
|
||||
if (!(data instanceof Map)) {
|
||||
return;
|
||||
}
|
||||
|
||||
collector.addExtraction({
|
||||
priority: 1000,
|
||||
id: "map",
|
||||
name: "Map",
|
||||
extractData() {
|
||||
return helpers.asData({
|
||||
kind: { table: true },
|
||||
rows: [...data].map(([k, v]) => ({ key: k, value: v }))
|
||||
});
|
||||
},
|
||||
});
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
|
||||
|
||||
/*
|
||||
register({
|
||||
id: "binaryViewer",
|
||||
getExtractions(data, collector, context) {
|
||||
if (typeof data !== "number") {
|
||||
return;
|
||||
}
|
||||
|
||||
collector.addExtraction({
|
||||
priority: 100,
|
||||
id: "binary",
|
||||
name: "Bits of Integer",
|
||||
extractData() {
|
||||
return helpers.asData({
|
||||
kind: { text: true },
|
||||
text: data.toString(2),
|
||||
});
|
||||
},
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
*/
|
||||
|
||||
(register, helpers) => {
|
||||
register({
|
||||
id: "positionOrRangeInTextModel",
|
||||
getExtractions(data, collector, context) {
|
||||
collector.addExtraction({
|
||||
priority: 100,
|
||||
id: "foo",
|
||||
name: "Foo",
|
||||
extractData() {
|
||||
return helpers.asData({
|
||||
kind: { text: true },
|
||||
text: Object.keys(context.variablesInScope).join(", "),
|
||||
});
|
||||
},
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
register({
|
||||
id: "positionOrRangeInTextModel",
|
||||
getExtractions(data, collector, context) {
|
||||
/** @type {{ start: { line: number; column: number; }; end: { line: number; column: number; }; }} */
|
||||
let range;
|
||||
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;
|
||||
}
|
||||
|
||||
/** @type {any} */
|
||||
const textModel = helpers.find(
|
||||
x =>
|
||||
typeof x === "object" &&
|
||||
!!x &&
|
||||
"constructor" in x &&
|
||||
x.constructor.name === "TextModel"
|
||||
);
|
||||
|
||||
collector.addExtraction({
|
||||
id: "positionOrRangeInTextModel",
|
||||
name: "Position/Range In TextModel",
|
||||
extractData() {
|
||||
return helpers.asData({
|
||||
kind: { text: true },
|
||||
text: textModel.getValue(),
|
||||
decorations: [{ range }],
|
||||
});
|
||||
},
|
||||
priority: 1000,
|
||||
});
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* @return {value is { startColumn: number; startLineNumber: number; endColumn: number; endLineNumber: number; }}
|
||||
*/
|
||||
function isRange(value) {
|
||||
return (
|
||||
typeof value === "object" &&
|
||||
!!value &&
|
||||
"startColumn" in value &&
|
||||
"startLineNumber" in value
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return {value is { column: number; lineNumber: number }}
|
||||
*/
|
||||
function isPosition(value) {
|
||||
return (
|
||||
typeof value === "object" &&
|
||||
!!value &&
|
||||
"column" in value &&
|
||||
"lineNumber" in value
|
||||
);
|
||||
}
|
||||
@@ -5,6 +5,7 @@
|
||||
"dependencies": {
|
||||
"@hediet/node-reload": "0.7.3",
|
||||
"@types/node": "^13.7.4",
|
||||
"node-fetch": "^2.6.1",
|
||||
"typescript": "^3.8.2"
|
||||
},
|
||||
"scripts": {
|
||||
|
||||
807
demos/js/src/demo_address_book.ts
Normal file
@@ -0,0 +1,807 @@
|
||||
|
||||
class Person {
|
||||
constructor(public id: number, public firstName: string, public lastName: string, public email: string, public gender: string, public company: string) {
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
class Company {
|
||||
constructor(public id: number, public name: string, public street: string, public email: string) { }
|
||||
}
|
||||
|
||||
class AddressBook {
|
||||
public static loadPersons(): Person[] {
|
||||
const contactsData = [{
|
||||
"id": 1,
|
||||
"first_name": "Burg",
|
||||
"last_name": "Legg",
|
||||
"email": "blegg0@elpais.com",
|
||||
"gender": "Male",
|
||||
"company": "Zoozzy"
|
||||
}, {
|
||||
"id": 2,
|
||||
"first_name": "Erminia",
|
||||
"last_name": "Heersema",
|
||||
"email": "eheersema1@jugem.jp",
|
||||
"gender": "Female",
|
||||
"company": "Skipfire"
|
||||
}, {
|
||||
"id": 3,
|
||||
"first_name": "Penrod",
|
||||
"last_name": "Claxton",
|
||||
"email": "pclaxton2@furl.net",
|
||||
"gender": "Male",
|
||||
"company": "Dabvine"
|
||||
}, {
|
||||
"id": 4,
|
||||
"first_name": "Peterus",
|
||||
"last_name": "Alabone",
|
||||
"email": "palabone3@icio.us",
|
||||
"gender": "Male",
|
||||
"company": "Gabtune"
|
||||
}, {
|
||||
"id": 5,
|
||||
"first_name": "Rickie",
|
||||
"last_name": "Belsher",
|
||||
"email": "rbelsher4@marketwatch.com",
|
||||
"gender": "Male",
|
||||
"company": "Digitube"
|
||||
}, {
|
||||
"id": 6,
|
||||
"first_name": "Griffie",
|
||||
"last_name": "Poulsum",
|
||||
"email": "gpoulsum5@printfriendly.com",
|
||||
"gender": "Male",
|
||||
"company": "Mymm"
|
||||
}, {
|
||||
"id": 7,
|
||||
"first_name": "Fidelio",
|
||||
"last_name": "Gateland",
|
||||
"email": "fgateland6@intel.com",
|
||||
"gender": "Male",
|
||||
"company": "Meeveo"
|
||||
}, {
|
||||
"id": 8,
|
||||
"first_name": "Fayth",
|
||||
"last_name": "D'Oyly",
|
||||
"email": "fdoyly7@twitter.com",
|
||||
"gender": "Female",
|
||||
"company": "Podcat"
|
||||
}, {
|
||||
"id": 9,
|
||||
"first_name": "Janela",
|
||||
"last_name": "Shelton",
|
||||
"email": "jshelton8@dion.ne.jp",
|
||||
"gender": "Female",
|
||||
"company": "Kayveo"
|
||||
}, {
|
||||
"id": 10,
|
||||
"first_name": "Yance",
|
||||
"last_name": "Vatcher",
|
||||
"email": "yvatcher9@ask.com",
|
||||
"gender": "Male",
|
||||
"company": "Photobug"
|
||||
}, {
|
||||
"id": 11,
|
||||
"first_name": "Filippo",
|
||||
"last_name": "Timoney",
|
||||
"email": "ftimoneya@spotify.com",
|
||||
"gender": "Male",
|
||||
"company": "Rhyzio"
|
||||
}, {
|
||||
"id": 12,
|
||||
"first_name": "Helli",
|
||||
"last_name": "Friskey",
|
||||
"email": "hfriskeyb@usa.gov",
|
||||
"gender": "Female",
|
||||
"company": "Eare"
|
||||
}, {
|
||||
"id": 13,
|
||||
"first_name": "Enos",
|
||||
"last_name": "Connick",
|
||||
"email": "econnickc@sitemeter.com",
|
||||
"gender": "Male",
|
||||
"company": "Meetz"
|
||||
}, {
|
||||
"id": 14,
|
||||
"first_name": "Lorri",
|
||||
"last_name": "Chessun",
|
||||
"email": "lchessund@wp.com",
|
||||
"gender": "Female",
|
||||
"company": "Jaxworks"
|
||||
}, {
|
||||
"id": 15,
|
||||
"first_name": "Justina",
|
||||
"last_name": "Spedroni",
|
||||
"email": "jspedronie@abc.net.au",
|
||||
"gender": "Female",
|
||||
"company": "Rhyzio"
|
||||
}, {
|
||||
"id": 16,
|
||||
"first_name": "Odilia",
|
||||
"last_name": "Ealden",
|
||||
"email": "oealdenf@fastcompany.com",
|
||||
"gender": "Female",
|
||||
"company": "Blognation"
|
||||
}, {
|
||||
"id": 17,
|
||||
"first_name": "Dale",
|
||||
"last_name": "Bretherick",
|
||||
"email": "dbretherickg@dmoz.org",
|
||||
"gender": "Male",
|
||||
"company": "Skipfire"
|
||||
}, {
|
||||
"id": 18,
|
||||
"first_name": "Syman",
|
||||
"last_name": "Lawdham",
|
||||
"email": "slawdhamh@so-net.ne.jp",
|
||||
"gender": "Male",
|
||||
"company": "Wordify"
|
||||
}, {
|
||||
"id": 19,
|
||||
"first_name": "Catarina",
|
||||
"last_name": "Stoile",
|
||||
"email": "cstoilei@hud.gov",
|
||||
"gender": "Female",
|
||||
"company": "Avaveo"
|
||||
}, {
|
||||
"id": 20,
|
||||
"first_name": "Simonette",
|
||||
"last_name": "Danett",
|
||||
"email": "sdanettj@eepurl.com",
|
||||
"gender": "Female",
|
||||
"company": "Realblab"
|
||||
}, {
|
||||
"id": 21,
|
||||
"first_name": "Fielding",
|
||||
"last_name": "MacHarg",
|
||||
"email": "fmachargk@blogtalkradio.com",
|
||||
"gender": "Male",
|
||||
"company": "Photolist"
|
||||
}, {
|
||||
"id": 22,
|
||||
"first_name": "Anni",
|
||||
"last_name": "Hounsom",
|
||||
"email": "ahounsoml@wikia.com",
|
||||
"gender": "Female",
|
||||
"company": "Devify"
|
||||
}, {
|
||||
"id": 23,
|
||||
"first_name": "Evelin",
|
||||
"last_name": "Gaytor",
|
||||
"email": "egaytorm@myspace.com",
|
||||
"gender": "Male",
|
||||
"company": "Aivee"
|
||||
}, {
|
||||
"id": 24,
|
||||
"first_name": "Lynde",
|
||||
"last_name": "Itscowicz",
|
||||
"email": "litscowiczn@fc2.com",
|
||||
"gender": "Female",
|
||||
"company": "Skipfire"
|
||||
}, {
|
||||
"id": 25,
|
||||
"first_name": "Reta",
|
||||
"last_name": "Felipe",
|
||||
"email": "rfelipeo@free.fr",
|
||||
"gender": "Female",
|
||||
"company": "Ainyx"
|
||||
}, {
|
||||
"id": 26,
|
||||
"first_name": "Bar",
|
||||
"last_name": "Shillingford",
|
||||
"email": "bshillingfordp@sun.com",
|
||||
"gender": "Male",
|
||||
"company": "Skilith"
|
||||
}, {
|
||||
"id": 27,
|
||||
"first_name": "Mel",
|
||||
"last_name": "Cooch",
|
||||
"email": "mcoochq@icq.com",
|
||||
"gender": "Female",
|
||||
"company": "Meevee"
|
||||
}, {
|
||||
"id": 28,
|
||||
"first_name": "Wheeler",
|
||||
"last_name": "Kettlesting",
|
||||
"email": "wkettlestingr@miitbeian.gov.cn",
|
||||
"gender": "Male",
|
||||
"company": "Mybuzz"
|
||||
}, {
|
||||
"id": 29,
|
||||
"first_name": "Corabel",
|
||||
"last_name": "Pordall",
|
||||
"email": "cpordalls@sourceforge.net",
|
||||
"gender": "Female",
|
||||
"company": "Blogtag"
|
||||
}, {
|
||||
"id": 30,
|
||||
"first_name": "Merrily",
|
||||
"last_name": "Weatherhill",
|
||||
"email": "mweatherhillt@printfriendly.com",
|
||||
"gender": "Female",
|
||||
"company": "Divape"
|
||||
}, {
|
||||
"id": 31,
|
||||
"first_name": "Reynard",
|
||||
"last_name": "Osgodby",
|
||||
"email": "rosgodbyu@eepurl.com",
|
||||
"gender": "Male",
|
||||
"company": "Yakijo"
|
||||
}, {
|
||||
"id": 32,
|
||||
"first_name": "Guilbert",
|
||||
"last_name": "Bilston",
|
||||
"email": "gbilstonv@devhub.com",
|
||||
"gender": "Male",
|
||||
"company": "Vinder"
|
||||
}, {
|
||||
"id": 33,
|
||||
"first_name": "Farr",
|
||||
"last_name": "Skehan",
|
||||
"email": "fskehanw@t-online.de",
|
||||
"gender": "Male",
|
||||
"company": "Linkbuzz"
|
||||
}, {
|
||||
"id": 34,
|
||||
"first_name": "Roger",
|
||||
"last_name": "Langelay",
|
||||
"email": "rlangelayx@pcworld.com",
|
||||
"gender": "Male",
|
||||
"company": "Tekfly"
|
||||
}, {
|
||||
"id": 35,
|
||||
"first_name": "Jemmy",
|
||||
"last_name": "Yggo",
|
||||
"email": "jyggoy@ifeng.com",
|
||||
"gender": "Female",
|
||||
"company": "Jabberstorm"
|
||||
}, {
|
||||
"id": 36,
|
||||
"first_name": "Cybill",
|
||||
"last_name": "Chastaing",
|
||||
"email": "cchastaingz@mayoclinic.com",
|
||||
"gender": "Female",
|
||||
"company": "Riffpedia"
|
||||
}, {
|
||||
"id": 37,
|
||||
"first_name": "Samuele",
|
||||
"last_name": "Zorzoni",
|
||||
"email": "szorzoni10@opensource.org",
|
||||
"gender": "Male",
|
||||
"company": "Devcast"
|
||||
}, {
|
||||
"id": 38,
|
||||
"first_name": "Svend",
|
||||
"last_name": "Hamill",
|
||||
"email": "shamill11@google.co.jp",
|
||||
"gender": "Male",
|
||||
"company": "Kwilith"
|
||||
}, {
|
||||
"id": 39,
|
||||
"first_name": "Carlin",
|
||||
"last_name": "Roote",
|
||||
"email": "croote12@gov.uk",
|
||||
"gender": "Male",
|
||||
"company": "Twitterlist"
|
||||
}, {
|
||||
"id": 40,
|
||||
"first_name": "Dawna",
|
||||
"last_name": "Lanfer",
|
||||
"email": "dlanfer13@mlb.com",
|
||||
"gender": "Female",
|
||||
"company": "Jayo"
|
||||
}, {
|
||||
"id": 41,
|
||||
"first_name": "Melissa",
|
||||
"last_name": "Chappelow",
|
||||
"email": "mchappelow14@usa.gov",
|
||||
"gender": "Female",
|
||||
"company": "Skibox"
|
||||
}, {
|
||||
"id": 42,
|
||||
"first_name": "George",
|
||||
"last_name": "Hargreaves",
|
||||
"email": "ghargreaves15@pagesperso-orange.fr",
|
||||
"gender": "Female",
|
||||
"company": "Zazio"
|
||||
}, {
|
||||
"id": 43,
|
||||
"first_name": "Sampson",
|
||||
"last_name": "Steaning",
|
||||
"email": "ssteaning16@state.gov",
|
||||
"gender": "Male",
|
||||
"company": "Jaxworks"
|
||||
}, {
|
||||
"id": 44,
|
||||
"first_name": "Brana",
|
||||
"last_name": "Wims",
|
||||
"email": "bwims17@icio.us",
|
||||
"gender": "Female",
|
||||
"company": "Flashset"
|
||||
}, {
|
||||
"id": 45,
|
||||
"first_name": "Shawn",
|
||||
"last_name": "Saffen",
|
||||
"email": "ssaffen18@shutterfly.com",
|
||||
"gender": "Male",
|
||||
"company": "Twitterwire"
|
||||
}, {
|
||||
"id": 46,
|
||||
"first_name": "Alessandro",
|
||||
"last_name": "Gasker",
|
||||
"email": "agasker19@slashdot.org",
|
||||
"gender": "Male",
|
||||
"company": "Dabtype"
|
||||
}, {
|
||||
"id": 47,
|
||||
"first_name": "Bobby",
|
||||
"last_name": "Mulcock",
|
||||
"email": "bmulcock1a@time.com",
|
||||
"gender": "Female",
|
||||
"company": "Quamba"
|
||||
}, {
|
||||
"id": 48,
|
||||
"first_name": "Ashlen",
|
||||
"last_name": "Midner",
|
||||
"email": "amidner1b@devhub.com",
|
||||
"gender": "Female",
|
||||
"company": "Thoughtstorm"
|
||||
}, {
|
||||
"id": 49,
|
||||
"first_name": "Mallory",
|
||||
"last_name": "Joel",
|
||||
"email": "mjoel1c@upenn.edu",
|
||||
"gender": "Male",
|
||||
"company": "Twitterbeat"
|
||||
}, {
|
||||
"id": 50,
|
||||
"first_name": "Maximilian",
|
||||
"last_name": "Scraggs",
|
||||
"email": "mscraggs1d@ucla.edu",
|
||||
"gender": "Male",
|
||||
"company": "Twiyo"
|
||||
}, {
|
||||
"id": 51,
|
||||
"first_name": "Beverlie",
|
||||
"last_name": "Pittet",
|
||||
"email": "bpittet1e@github.io",
|
||||
"gender": "Female",
|
||||
"company": "Riffpath"
|
||||
}, {
|
||||
"id": 52,
|
||||
"first_name": "Enrique",
|
||||
"last_name": "Rentilll",
|
||||
"email": "erentilll1f@smugmug.com",
|
||||
"gender": "Male",
|
||||
"company": "Blognation"
|
||||
}, {
|
||||
"id": 53,
|
||||
"first_name": "Cathrin",
|
||||
"last_name": "McKendry",
|
||||
"email": "cmckendry1g@hc360.com",
|
||||
"gender": "Female",
|
||||
"company": "Shuffledrive"
|
||||
}, {
|
||||
"id": 54,
|
||||
"first_name": "Isabeau",
|
||||
"last_name": "Quincey",
|
||||
"email": "iquincey1h@msn.com",
|
||||
"gender": "Female",
|
||||
"company": "Tagcat"
|
||||
}, {
|
||||
"id": 55,
|
||||
"first_name": "Geoffrey",
|
||||
"last_name": "Grzegorczyk",
|
||||
"email": "ggrzegorczyk1i@loc.gov",
|
||||
"gender": "Male",
|
||||
"company": "Youfeed"
|
||||
}, {
|
||||
"id": 56,
|
||||
"first_name": "Ruggiero",
|
||||
"last_name": "Vaar",
|
||||
"email": "rvaar1j@unesco.org",
|
||||
"gender": "Male",
|
||||
"company": "Babbleset"
|
||||
}, {
|
||||
"id": 57,
|
||||
"first_name": "Alfonso",
|
||||
"last_name": "Tarzey",
|
||||
"email": "atarzey1k@weebly.com",
|
||||
"gender": "Male",
|
||||
"company": "Pixonyx"
|
||||
}, {
|
||||
"id": 58,
|
||||
"first_name": "Marylin",
|
||||
"last_name": "Bissex",
|
||||
"email": "mbissex1l@microsoft.com",
|
||||
"gender": "Female",
|
||||
"company": "Tagfeed"
|
||||
}, {
|
||||
"id": 59,
|
||||
"first_name": "Cathryn",
|
||||
"last_name": "Morhall",
|
||||
"email": "cmorhall1m@irs.gov",
|
||||
"gender": "Female",
|
||||
"company": "Tagopia"
|
||||
}, {
|
||||
"id": 60,
|
||||
"first_name": "Terza",
|
||||
"last_name": "Easey",
|
||||
"email": "teasey1n@thetimes.co.uk",
|
||||
"gender": "Female",
|
||||
"company": "Devcast"
|
||||
}, {
|
||||
"id": 61,
|
||||
"first_name": "Hale",
|
||||
"last_name": "Gaudon",
|
||||
"email": "hgaudon1o@webnode.com",
|
||||
"gender": "Male",
|
||||
"company": "Quinu"
|
||||
}, {
|
||||
"id": 62,
|
||||
"first_name": "Hadley",
|
||||
"last_name": "Elwood",
|
||||
"email": "helwood1p@japanpost.jp",
|
||||
"gender": "Male",
|
||||
"company": "Plajo"
|
||||
}, {
|
||||
"id": 63,
|
||||
"first_name": "Seamus",
|
||||
"last_name": "Straker",
|
||||
"email": "sstraker1q@live.com",
|
||||
"gender": "Male",
|
||||
"company": "Yoveo"
|
||||
}, {
|
||||
"id": 64,
|
||||
"first_name": "Urbano",
|
||||
"last_name": "Gisbey",
|
||||
"email": "ugisbey1r@sitemeter.com",
|
||||
"gender": "Male",
|
||||
"company": "Quinu"
|
||||
}, {
|
||||
"id": 65,
|
||||
"first_name": "Gerri",
|
||||
"last_name": "McInulty",
|
||||
"email": "gmcinulty1s@businessinsider.com",
|
||||
"gender": "Male",
|
||||
"company": "Agimba"
|
||||
}, {
|
||||
"id": 66,
|
||||
"first_name": "Lena",
|
||||
"last_name": "Gonthier",
|
||||
"email": "lgonthier1t@newsvine.com",
|
||||
"gender": "Female",
|
||||
"company": "Geba"
|
||||
}, {
|
||||
"id": 67,
|
||||
"first_name": "Ameline",
|
||||
"last_name": "Wheatcroft",
|
||||
"email": "awheatcroft1u@163.com",
|
||||
"gender": "Female",
|
||||
"company": "Twitternation"
|
||||
}, {
|
||||
"id": 68,
|
||||
"first_name": "Kathryn",
|
||||
"last_name": "Castelli",
|
||||
"email": "kcastelli1v@about.com",
|
||||
"gender": "Female",
|
||||
"company": "Photospace"
|
||||
}, {
|
||||
"id": 69,
|
||||
"first_name": "Terrell",
|
||||
"last_name": "Walicki",
|
||||
"email": "twalicki1w@amazon.co.jp",
|
||||
"gender": "Male",
|
||||
"company": "Babblestorm"
|
||||
}, {
|
||||
"id": 70,
|
||||
"first_name": "Claudianus",
|
||||
"last_name": "Tremathack",
|
||||
"email": "ctremathack1x@walmart.com",
|
||||
"gender": "Male",
|
||||
"company": "Skyndu"
|
||||
}, {
|
||||
"id": 71,
|
||||
"first_name": "Sanford",
|
||||
"last_name": "Devinn",
|
||||
"email": "sdevinn1y@ehow.com",
|
||||
"gender": "Male",
|
||||
"company": "Dynazzy"
|
||||
}, {
|
||||
"id": 72,
|
||||
"first_name": "Gwendolen",
|
||||
"last_name": "Turfrey",
|
||||
"email": "gturfrey1z@va.gov",
|
||||
"gender": "Female",
|
||||
"company": "Yacero"
|
||||
}, {
|
||||
"id": 73,
|
||||
"first_name": "Alta",
|
||||
"last_name": "Leap",
|
||||
"email": "aleap20@ezinearticles.com",
|
||||
"gender": "Female",
|
||||
"company": "Twitterlist"
|
||||
}, {
|
||||
"id": 74,
|
||||
"first_name": "Johnath",
|
||||
"last_name": "Andrzejowski",
|
||||
"email": "jandrzejowski21@google.com.hk",
|
||||
"gender": "Female",
|
||||
"company": "Lazzy"
|
||||
}, {
|
||||
"id": 75,
|
||||
"first_name": "Yancy",
|
||||
"last_name": "Askham",
|
||||
"email": "yaskham22@sciencedaily.com",
|
||||
"gender": "Male",
|
||||
"company": "Yabox"
|
||||
}, {
|
||||
"id": 76,
|
||||
"first_name": "Paula",
|
||||
"last_name": "Warder",
|
||||
"email": "pwarder23@harvard.edu",
|
||||
"gender": "Female",
|
||||
"company": "Zooxo"
|
||||
}, {
|
||||
"id": 77,
|
||||
"first_name": "Grannie",
|
||||
"last_name": "Hauxley",
|
||||
"email": "ghauxley24@symantec.com",
|
||||
"gender": "Male",
|
||||
"company": "Yakidoo"
|
||||
}, {
|
||||
"id": 78,
|
||||
"first_name": "Enid",
|
||||
"last_name": "Grealy",
|
||||
"email": "egrealy25@homestead.com",
|
||||
"gender": "Female",
|
||||
"company": "Gabcube"
|
||||
}, {
|
||||
"id": 79,
|
||||
"first_name": "Lazarus",
|
||||
"last_name": "Itscowicz",
|
||||
"email": "litscowicz26@epa.gov",
|
||||
"gender": "Male",
|
||||
"company": "Demizz"
|
||||
}, {
|
||||
"id": 80,
|
||||
"first_name": "Marmaduke",
|
||||
"last_name": "Gilligan",
|
||||
"email": "mgilligan27@tiny.cc",
|
||||
"gender": "Male",
|
||||
"company": "Yombu"
|
||||
}, {
|
||||
"id": 81,
|
||||
"first_name": "Linnell",
|
||||
"last_name": "Chree",
|
||||
"email": "lchree28@ocn.ne.jp",
|
||||
"gender": "Female",
|
||||
"company": "Buzzshare"
|
||||
}, {
|
||||
"id": 82,
|
||||
"first_name": "Pavia",
|
||||
"last_name": "Pocke",
|
||||
"email": "ppocke29@newsvine.com",
|
||||
"gender": "Female",
|
||||
"company": "Eidel"
|
||||
}, {
|
||||
"id": 83,
|
||||
"first_name": "Golda",
|
||||
"last_name": "Stuchburie",
|
||||
"email": "gstuchburie2a@oaic.gov.au",
|
||||
"gender": "Female",
|
||||
"company": "Flipstorm"
|
||||
}, {
|
||||
"id": 84,
|
||||
"first_name": "Trace",
|
||||
"last_name": "Lodge",
|
||||
"email": "tlodge2b@whitehouse.gov",
|
||||
"gender": "Male",
|
||||
"company": "Zazio"
|
||||
}, {
|
||||
"id": 85,
|
||||
"first_name": "Nikolia",
|
||||
"last_name": "Bartolomivis",
|
||||
"email": "nbartolomivis2c@opensource.org",
|
||||
"gender": "Female",
|
||||
"company": "Devshare"
|
||||
}, {
|
||||
"id": 86,
|
||||
"first_name": "Dela",
|
||||
"last_name": "Wimp",
|
||||
"email": "dwimp2d@miitbeian.gov.cn",
|
||||
"gender": "Female",
|
||||
"company": "Wordware"
|
||||
}, {
|
||||
"id": 87,
|
||||
"first_name": "Rustie",
|
||||
"last_name": "Drury",
|
||||
"email": "rdrury2e@sun.com",
|
||||
"gender": "Male",
|
||||
"company": "Youbridge"
|
||||
}, {
|
||||
"id": 88,
|
||||
"first_name": "Phedra",
|
||||
"last_name": "Shrimptone",
|
||||
"email": "pshrimptone2f@unicef.org",
|
||||
"gender": "Female",
|
||||
"company": "Realcube"
|
||||
}, {
|
||||
"id": 89,
|
||||
"first_name": "Alaster",
|
||||
"last_name": "Vittori",
|
||||
"email": "avittori2g@senate.gov",
|
||||
"gender": "Male",
|
||||
"company": "Livepath"
|
||||
}, {
|
||||
"id": 90,
|
||||
"first_name": "Pascal",
|
||||
"last_name": "Neissen",
|
||||
"email": "pneissen2h@mediafire.com",
|
||||
"gender": "Male",
|
||||
"company": "Mydo"
|
||||
}, {
|
||||
"id": 91,
|
||||
"first_name": "Brian",
|
||||
"last_name": "Manuel",
|
||||
"email": "bmanuel2i@blog.com",
|
||||
"gender": "Male",
|
||||
"company": "Roomm"
|
||||
}, {
|
||||
"id": 92,
|
||||
"first_name": "Jen",
|
||||
"last_name": "Laydon",
|
||||
"email": "jlaydon2j@hubpages.com",
|
||||
"gender": "Female",
|
||||
"company": "Plambee"
|
||||
}, {
|
||||
"id": 93,
|
||||
"first_name": "Beltran",
|
||||
"last_name": "Brandoni",
|
||||
"email": "bbrandoni2k@digg.com",
|
||||
"gender": "Male",
|
||||
"company": "Yacero"
|
||||
}, {
|
||||
"id": 94,
|
||||
"first_name": "Sarene",
|
||||
"last_name": "Gaymar",
|
||||
"email": "sgaymar2l@surveymonkey.com",
|
||||
"gender": "Female",
|
||||
"company": "Katz"
|
||||
}, {
|
||||
"id": 95,
|
||||
"first_name": "Iormina",
|
||||
"last_name": "Peedell",
|
||||
"email": "ipeedell2m@seattletimes.com",
|
||||
"gender": "Female",
|
||||
"company": "Youfeed"
|
||||
}, {
|
||||
"id": 96,
|
||||
"first_name": "Abigail",
|
||||
"last_name": "Kielty",
|
||||
"email": "akielty2n@npr.org",
|
||||
"gender": "Female",
|
||||
"company": "Reallinks"
|
||||
}, {
|
||||
"id": 97,
|
||||
"first_name": "Hallie",
|
||||
"last_name": "Toomey",
|
||||
"email": "htoomey2o@google.com.au",
|
||||
"gender": "Female",
|
||||
"company": "Flipbug"
|
||||
}, {
|
||||
"id": 98,
|
||||
"first_name": "Sergeant",
|
||||
"last_name": "Pourveer",
|
||||
"email": "spourveer2p@amazon.co.uk",
|
||||
"gender": "Male",
|
||||
"company": "Skinix"
|
||||
}, {
|
||||
"id": 99,
|
||||
"first_name": "Elfie",
|
||||
"last_name": "McKinlay",
|
||||
"email": "emckinlay2q@xrea.com",
|
||||
"gender": "Female",
|
||||
"company": "Devcast"
|
||||
}, {
|
||||
"id": 100,
|
||||
"first_name": "Wainwright",
|
||||
"last_name": "Juggins",
|
||||
"email": "wjuggins2r@tamu.edu",
|
||||
"gender": "Male",
|
||||
"company": "Topiclounge"
|
||||
}];
|
||||
|
||||
|
||||
|
||||
const contacts = contactsData.map(d => new Person(d.id, d.first_name, d.last_name, d.email, d.gender, d.company));
|
||||
return contacts;
|
||||
}
|
||||
|
||||
public static loadCompanies() {
|
||||
const companies = [{
|
||||
"id": 101,
|
||||
"city": "Lakshmīpur",
|
||||
"street": "36300 Lake View Center",
|
||||
"email": "ogovier0@gmpg.org",
|
||||
"company": "Fiveclub"
|
||||
}, {
|
||||
"id": 102,
|
||||
"city": "El Quetzal",
|
||||
"street": "551 New Castle Parkway",
|
||||
"email": "elander1@g.co",
|
||||
"company": "Twitterwire"
|
||||
}, {
|
||||
"id": 103,
|
||||
"city": "Mayakovski",
|
||||
"street": "1 Norway Maple Center",
|
||||
"email": "dcustance2@hostgator.com",
|
||||
"company": "Jabbersphere"
|
||||
}, {
|
||||
"id": 104,
|
||||
"city": "Al Jawādīyah",
|
||||
"street": "02 Carey Hill",
|
||||
"email": "evanoort3@sciencedaily.com",
|
||||
"company": "Zoovu"
|
||||
}, {
|
||||
"id": 105,
|
||||
"city": "Girona",
|
||||
"street": "3 Lillian Center",
|
||||
"email": "dducker4@cbsnews.com",
|
||||
"company": "Innojam"
|
||||
}, {
|
||||
"id": 106,
|
||||
"city": "Vouani",
|
||||
"street": "5165 Vera Avenue",
|
||||
"email": "orzehorz5@arizona.edu",
|
||||
"company": "Zoovu"
|
||||
}, {
|
||||
"id": 107,
|
||||
"city": "Caibarién",
|
||||
"street": "3739 Burrows Drive",
|
||||
"email": "ltrahair6@pinterest.com",
|
||||
"company": "Quamba"
|
||||
}, {
|
||||
"id": 108,
|
||||
"city": "Wushi",
|
||||
"street": "88 Bay Hill",
|
||||
"email": "plebourn7@springer.com",
|
||||
"company": "Feednation"
|
||||
}, {
|
||||
"id": 109,
|
||||
"city": "Zbýšov",
|
||||
"street": "38 Dovetail Place",
|
||||
"email": "slace8@netscape.com",
|
||||
"company": "Zoomzone"
|
||||
}, {
|
||||
"id": 110,
|
||||
"city": "Nová Cerekev",
|
||||
"street": "90194 Dixon Avenue",
|
||||
"email": "mdoxey9@goo.gl",
|
||||
"company": "Devpoint"
|
||||
}];
|
||||
|
||||
return companies.map(c => new Company(c.id, c.company, c.street, c.email));
|
||||
}
|
||||
|
||||
public static loadContacts() {
|
||||
const result = new Array<Person | Company>();
|
||||
result.push(...this.loadPersons());
|
||||
result.push(...this.loadCompanies());
|
||||
let idx = 0;
|
||||
while (idx++ < 1000) {
|
||||
const i = Math.round(Math.random() * (result.length - 1));
|
||||
const j = Math.round(Math.random() * (result.length - 1));
|
||||
const temp = result[i];
|
||||
result[i] = result[j];
|
||||
result[j] = temp;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
const contacts = AddressBook.loadContacts();
|
||||
|
||||
debugger;
|
||||
@@ -10,49 +10,70 @@ setTimeout(() => {
|
||||
}, 0);
|
||||
|
||||
class Main {
|
||||
/** @pure */
|
||||
run() {
|
||||
const list = new DoublyLinkedList("1");
|
||||
list.setNext(new DoublyLinkedList("2"));
|
||||
list.next!.setNext(new DoublyLinkedList("3"));
|
||||
list.next!.next!.setNext(new DoublyLinkedList("4"));
|
||||
|
||||
// Use `visualize()` as expression to visualize in the Debug Visualizer!
|
||||
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;
|
||||
}
|
||||
id = 0;
|
||||
const head = new DoublyLinkedListNode("1");
|
||||
head.setNext(new DoublyLinkedListNode("2"));
|
||||
head.next!.setNext(new DoublyLinkedListNode("3"));
|
||||
const tail = new DoublyLinkedListNode("4");
|
||||
head.next!.next!.setNext(tail);
|
||||
reverse(new DoublyLinkedList(head, tail));
|
||||
console.log("finished");
|
||||
}
|
||||
}
|
||||
|
||||
let id = 0;
|
||||
function reverse(list: DoublyLinkedList) {
|
||||
// Open a new Debug Visualizer
|
||||
// and enter `visualize()`!
|
||||
const visualize = () =>
|
||||
createGraphFromPointers(
|
||||
{
|
||||
last,
|
||||
"list.head": list.head,
|
||||
"list.tail": list.tail
|
||||
},
|
||||
i => ({
|
||||
id: i.id,
|
||||
label: i.name,
|
||||
color: finished.has(i) ? "lime" : "lightblue",
|
||||
edges: [
|
||||
{ to: i.next!, label: "next", color: "lightblue", },
|
||||
{ to: i.prev!, label: "prev", color: "lightgray" },
|
||||
].filter(r => !!r.to),
|
||||
}));
|
||||
|
||||
// Finished nodes have correct pointers,
|
||||
// their next node is also finished.
|
||||
const finished = new Set();
|
||||
var last: DoublyLinkedListNode | null = null;
|
||||
list.tail = list.head;
|
||||
while (list.head) {
|
||||
list.head.prev = list.head.next;
|
||||
list.head.next = last;
|
||||
finished.add(list.head);
|
||||
last = list.head;
|
||||
list.head = list.head.prev;
|
||||
}
|
||||
list.head = last;
|
||||
}
|
||||
|
||||
class DoublyLinkedList {
|
||||
constructor(
|
||||
public head: DoublyLinkedListNode | null,
|
||||
public tail: DoublyLinkedListNode | null
|
||||
) { }
|
||||
}
|
||||
|
||||
let id = 0;
|
||||
class DoublyLinkedListNode {
|
||||
public readonly id = (id++).toString();
|
||||
constructor(public name: string) {}
|
||||
constructor(public name: string) { }
|
||||
|
||||
next: DoublyLinkedList | undefined;
|
||||
prev: DoublyLinkedList | undefined;
|
||||
next: DoublyLinkedListNode | null = null;
|
||||
prev: DoublyLinkedListNode | null = null;
|
||||
|
||||
public setNext(val: DoublyLinkedList): void {
|
||||
public setNext(val: DoublyLinkedListNode): void {
|
||||
val.prev = this;
|
||||
this.next = val;
|
||||
}
|
||||
|
||||
7
demos/js/src/demo_fetch.js
Normal file
@@ -0,0 +1,7 @@
|
||||
const fetch = require("node-fetch");
|
||||
|
||||
fetch("https://jsonplaceholder.typicode.com/users")
|
||||
.then((response) => response.json())
|
||||
.then((json) => {
|
||||
debugger;
|
||||
});
|
||||
@@ -1,12 +1,20 @@
|
||||
// visualize `data`
|
||||
const data = new Array<number>();
|
||||
let value = 0;
|
||||
for (let i = 0; i < 100000; i++) {
|
||||
const delta = Math.random() > 0.5 ? 1 : -1;
|
||||
data.push(value);
|
||||
value += delta;
|
||||
let curValue = 0;
|
||||
|
||||
if (i % 1000 === 0) {
|
||||
debugger;
|
||||
for (let j = 0; j < 100; j++) {
|
||||
addManyRandomValues();
|
||||
}
|
||||
|
||||
function addManyRandomValues() {
|
||||
for (let i = 0; i < 100; i++) {
|
||||
addRandomValue();
|
||||
}
|
||||
}
|
||||
|
||||
function addRandomValue() {
|
||||
const delta = Math.random()
|
||||
> 0.5 ? 1 : -1;
|
||||
data.push(curValue);
|
||||
curValue += delta;
|
||||
}
|
||||
@@ -13,9 +13,13 @@ hedietDbgVis.markedGrid(
|
||||
*/
|
||||
|
||||
// From https://github.com/AvraamMavridis/Algorithms-Data-Structures-in-Typescript/blob/master/algorithms/quickSort.md
|
||||
const array = [1, 2, 33, 31, 1, 2, 63, 123, 6, 32, 943, 346, 24];
|
||||
const sorted = quickSort(array, 0, array.length - 1);
|
||||
console.log(sorted);
|
||||
/** @pure */
|
||||
function main() {
|
||||
const array = [1, 2, 33, 31, 1, 2, 63, 123, 6, 32, 943, 346, 24];
|
||||
const sorted = quickSort(array, 0, array.length - 1);
|
||||
console.log(sorted);
|
||||
}
|
||||
main();
|
||||
|
||||
function swap(array: Array<number>, i: number, j: number) {
|
||||
[array[i], array[j]] = [array[j], array[i]];
|
||||
|
||||
@@ -11,13 +11,14 @@ setTimeout(() => {
|
||||
|
||||
class Main {
|
||||
run() {
|
||||
const files = new Map<string, string>([
|
||||
/*const files = new Map<string, string>([
|
||||
[
|
||||
"main.ts",
|
||||
`
|
||||
class Test1 {
|
||||
public foo(a: number) {
|
||||
const x = { a: 5 };
|
||||
const y = { a: 5 };
|
||||
}
|
||||
}
|
||||
`,
|
||||
@@ -34,7 +35,7 @@ class Test1 {
|
||||
const c = prog.getTypeChecker();
|
||||
let myValue = undefined; // Visualize `myValue` here!
|
||||
const sourceFileAst = prog.getSourceFiles()[0];
|
||||
myValue = sourceFileAst.getText();
|
||||
myValue = sourceFileAst;
|
||||
console.log("myValue is the source code of the AST");
|
||||
debugger;
|
||||
|
||||
@@ -64,7 +65,24 @@ class Test1 {
|
||||
},
|
||||
};
|
||||
console.log("myValue is AST, annotated with symbol information");
|
||||
debugger;
|
||||
debugger;*/
|
||||
require;
|
||||
|
||||
const sf = ts.createSourceFile("main.ts",
|
||||
`
|
||||
class Test1 {
|
||||
public foo(a: number) {
|
||||
const x = { a: 5 };
|
||||
const y = { a: 24 };
|
||||
}
|
||||
}
|
||||
`, ts.ScriptTarget.ESNext, true);
|
||||
const traverse = (node: ts.Node) => {
|
||||
ts.forEachChild(node, child => {
|
||||
traverse(child);
|
||||
});
|
||||
};
|
||||
traverse(sf);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
# yarn lockfile v1
|
||||
|
||||
|
||||
"@hediet/live-debug@^0.5.1", "@hediet/live-debug@^0.5.2":
|
||||
"@hediet/live-debug@^0.5.1":
|
||||
version "0.5.2"
|
||||
resolved "https://registry.yarnpkg.com/@hediet/live-debug/-/live-debug-0.5.2.tgz#f2ee0034f014836bfd68e26c35a168ab42b15486"
|
||||
integrity sha512-HBqVbyFez1oQlIPO9dzkFi7/9Gvt6ZpssgURzYaVFREl9Oq03nPd6V7Ay4bW03xFHPo3YT2Yff9mFmGQUX7JEg==
|
||||
@@ -13,30 +13,21 @@
|
||||
stacktracey "^1.2.112"
|
||||
ws "^7.0.0"
|
||||
|
||||
"@hediet/node-reload@0.7.0":
|
||||
version "0.7.0"
|
||||
resolved "https://registry.yarnpkg.com/@hediet/node-reload/-/node-reload-0.7.0.tgz#ec50160ce26203079f18e7024cd58025fa79652a"
|
||||
integrity sha512-gWFbq2tOThSV9GT+pFx1JJzFuOV7l40IsseOFHMJGyvLmmAdgbJPV3OZuV9gcsXIGocOtbfEyky9S4tQELLLxA==
|
||||
"@hediet/node-reload@0.7.3":
|
||||
version "0.7.3"
|
||||
resolved "https://registry.yarnpkg.com/@hediet/node-reload/-/node-reload-0.7.3.tgz#305d68f54185200b018b14f257037ab8d8ca9857"
|
||||
integrity sha512-icOyfDpArfvqmw0VLf2CoGR/jCSuWk3fScJGFuXQHpb0c9yNoGTiuG3Q8L2iJIuDzCjRu47DR2T+yCvZsY/f+w==
|
||||
dependencies:
|
||||
"@hediet/live-debug" "^0.5.1"
|
||||
"@hediet/std" "^0.6.0"
|
||||
"@hediet/typed-json-rpc" "^0.7.6"
|
||||
"@types/node" "^12.0.7"
|
||||
"@types/stack-trace" "^0.0.29"
|
||||
chalk "^2.4.2"
|
||||
chokidar "^3.0.1"
|
||||
stack-trace "^0.0.10"
|
||||
ws "^7.0.0"
|
||||
|
||||
"@hediet/std@^0.4.0":
|
||||
version "0.4.0"
|
||||
resolved "https://registry.yarnpkg.com/@hediet/std/-/std-0.4.0.tgz#95dfd6e9d62f8bf9d91f3925d37b7b359b2af047"
|
||||
integrity sha512-2kw4vSOprLchTjhcg8qQo6fdbBxJicSUrzZXgL0KzgMv18hjVmpm57NPLCPfI6115kAaMIWvMnlHFLYbx7poQQ==
|
||||
dependencies:
|
||||
"@types/chai" "^4.1.7"
|
||||
"@types/sinon" "^7.0.10"
|
||||
chai "^4.2.0"
|
||||
sinon "^7.2.7"
|
||||
|
||||
"@hediet/std@^0.6.0":
|
||||
version "0.6.0"
|
||||
resolved "https://registry.yarnpkg.com/@hediet/std/-/std-0.6.0.tgz#f7d2ffc1180ecef6ff272d0d6780f0c0d9337e4e"
|
||||
@@ -109,6 +100,11 @@
|
||||
resolved "https://registry.yarnpkg.com/@types/node/-/node-12.6.8.tgz#e469b4bf9d1c9832aee4907ba8a051494357c12c"
|
||||
integrity sha512-aX+gFgA5GHcDi89KG5keey2zf0WfZk/HAQotEamsK2kbey+8yGKcson0hbK8E+v0NArlCJQCqMP161YhV6ZXLg==
|
||||
|
||||
"@types/node@^13.7.4":
|
||||
version "13.13.15"
|
||||
resolved "https://registry.yarnpkg.com/@types/node/-/node-13.13.15.tgz#fe1cc3aa465a3ea6858b793fd380b66c39919766"
|
||||
integrity sha512-kwbcs0jySLxzLsa2nWUAGOd/s21WU1jebrEdtzhsj1D4Yps1EOuyI1Qcu+FD56dL7NRNIJtDDjcqIG22NwkgLw==
|
||||
|
||||
"@types/node@^8.0.7":
|
||||
version "8.10.51"
|
||||
resolved "https://registry.yarnpkg.com/@types/node/-/node-8.10.51.tgz#80600857c0a47a8e8bafc2dae6daed6db58e3627"
|
||||
@@ -132,6 +128,13 @@
|
||||
"@types/events" "*"
|
||||
"@types/node" "*"
|
||||
|
||||
ansi-styles@^3.2.1:
|
||||
version "3.2.1"
|
||||
resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-3.2.1.tgz#41fbb20243e50b12be0f04b8dedbf07520ce841d"
|
||||
integrity sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==
|
||||
dependencies:
|
||||
color-convert "^1.9.0"
|
||||
|
||||
anymatch@^3.0.1:
|
||||
version "3.0.3"
|
||||
resolved "https://registry.yarnpkg.com/anymatch/-/anymatch-3.0.3.tgz#2fb624fe0e84bccab00afee3d0006ed310f22f09"
|
||||
@@ -186,6 +189,15 @@ chai@^4.2.0:
|
||||
pathval "^1.1.0"
|
||||
type-detect "^4.0.5"
|
||||
|
||||
chalk@^2.4.2:
|
||||
version "2.4.2"
|
||||
resolved "https://registry.yarnpkg.com/chalk/-/chalk-2.4.2.tgz#cd42541677a54333cf541a49108c1432b44c9424"
|
||||
integrity sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==
|
||||
dependencies:
|
||||
ansi-styles "^3.2.1"
|
||||
escape-string-regexp "^1.0.5"
|
||||
supports-color "^5.3.0"
|
||||
|
||||
check-error@^1.0.2:
|
||||
version "1.0.2"
|
||||
resolved "https://registry.yarnpkg.com/check-error/-/check-error-1.0.2.tgz#574d312edd88bb5dd8912e9286dd6c0aed4aac82"
|
||||
@@ -206,6 +218,18 @@ chokidar@^3.0.1:
|
||||
optionalDependencies:
|
||||
fsevents "^2.0.6"
|
||||
|
||||
color-convert@^1.9.0:
|
||||
version "1.9.3"
|
||||
resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-1.9.3.tgz#bb71850690e1f136567de629d2d5471deda4c1e8"
|
||||
integrity sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==
|
||||
dependencies:
|
||||
color-name "1.1.3"
|
||||
|
||||
color-name@1.1.3:
|
||||
version "1.1.3"
|
||||
resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.3.tgz#a7d0558bd89c42f795dd42328f740831ca53bc25"
|
||||
integrity sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=
|
||||
|
||||
data-uri-to-buffer@^2.0.0:
|
||||
version "2.0.1"
|
||||
resolved "https://registry.yarnpkg.com/data-uri-to-buffer/-/data-uri-to-buffer-2.0.1.tgz#ca8f56fe38b1fd329473e9d1b4a9afcd8ce1c045"
|
||||
@@ -225,6 +249,11 @@ diff@^3.5.0:
|
||||
resolved "https://registry.yarnpkg.com/diff/-/diff-3.5.0.tgz#800c0dd1e0a8bfbc95835c202ad220fe317e5a12"
|
||||
integrity sha512-A46qtFgd+g7pDZinpnwiRJtxbC1hpgf0uzP3iG89scHk0AUC7A1TGxf5OiiOUv/JMZR8GOt8hL900hV0bOy5xA==
|
||||
|
||||
escape-string-regexp@^1.0.5:
|
||||
version "1.0.5"
|
||||
resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz#1b61c0562190a8dff6ae3bb2cf0200ca130b86d4"
|
||||
integrity sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=
|
||||
|
||||
fill-range@^7.0.1:
|
||||
version "7.0.1"
|
||||
resolved "https://registry.yarnpkg.com/fill-range/-/fill-range-7.0.1.tgz#1919a6a7c75fe38b2c7c77e5198535da9acdda40"
|
||||
@@ -242,6 +271,11 @@ fsevents@^2.0.6:
|
||||
resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.0.7.tgz#382c9b443c6cbac4c57187cdda23aa3bf1ccfc2a"
|
||||
integrity sha512-a7YT0SV3RB+DjYcppwVDLtn13UQnmg0SWZS7ezZD0UjnLwXmy8Zm21GMVGLaFGimIqcvyMQaOJBrop8MyOp1kQ==
|
||||
|
||||
function-bind@^1.1.1:
|
||||
version "1.1.1"
|
||||
resolved "https://registry.yarnpkg.com/function-bind/-/function-bind-1.1.1.tgz#a56899d3ea3c9bab874bb9773b7c5ede92f4895d"
|
||||
integrity sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==
|
||||
|
||||
get-func-name@^2.0.0:
|
||||
version "2.0.0"
|
||||
resolved "https://registry.yarnpkg.com/get-func-name/-/get-func-name-2.0.0.tgz#ead774abee72e20409433a066366023dd6887a41"
|
||||
@@ -267,6 +301,13 @@ has-flag@^3.0.0:
|
||||
resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-3.0.0.tgz#b5d454dc2199ae225699f3467e5a07f3b955bafd"
|
||||
integrity sha1-tdRU3CGZriJWmfNGfloH87lVuv0=
|
||||
|
||||
has@^1.0.3:
|
||||
version "1.0.3"
|
||||
resolved "https://registry.yarnpkg.com/has/-/has-1.0.3.tgz#722d7cbfc1f6aa8241f16dd814e011e1f41e8796"
|
||||
integrity sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==
|
||||
dependencies:
|
||||
function-bind "^1.1.1"
|
||||
|
||||
io-ts@^1.8.6:
|
||||
version "1.10.4"
|
||||
resolved "https://registry.yarnpkg.com/io-ts/-/io-ts-1.10.4.tgz#cd5401b138de88e4f920adbcb7026e2d1967e6e2"
|
||||
@@ -281,6 +322,13 @@ is-binary-path@^2.1.0:
|
||||
dependencies:
|
||||
binary-extensions "^2.0.0"
|
||||
|
||||
is-core-module@^2.2.0:
|
||||
version "2.4.0"
|
||||
resolved "https://registry.yarnpkg.com/is-core-module/-/is-core-module-2.4.0.tgz#8e9fc8e15027b011418026e98f0e6f4d86305cc1"
|
||||
integrity sha512-6A2fkfq1rfeQZjxrZJGerpLCTHRNEBiSgnu0+obeJpEPZRUooHgsizvzv0ZjJwOz3iWIHdJtVWJ/tmPr3D21/A==
|
||||
dependencies:
|
||||
has "^1.0.3"
|
||||
|
||||
is-extglob@^2.1.1:
|
||||
version "2.1.1"
|
||||
resolved "https://registry.yarnpkg.com/is-extglob/-/is-extglob-2.1.1.tgz#a88c02535791f02ed37c76a1b9ea9773c833f8c2"
|
||||
@@ -339,11 +387,21 @@ nise@^1.4.10:
|
||||
lolex "^4.1.0"
|
||||
path-to-regexp "^1.7.0"
|
||||
|
||||
node-fetch@^2.6.1:
|
||||
version "2.6.1"
|
||||
resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.6.1.tgz#045bd323631f76ed2e2b55573394416b639a0052"
|
||||
integrity sha512-V4aYg89jEoVRxRb2fJdAg8FHvI7cEyYdVAh94HH0UIK8oJxUfkjlDQN9RbMx+bEjP7+ggMiFRprSti032Oipxw==
|
||||
|
||||
normalize-path@^3.0.0:
|
||||
version "3.0.0"
|
||||
resolved "https://registry.yarnpkg.com/normalize-path/-/normalize-path-3.0.0.tgz#0dcd69ff23a1c9b11fd0978316644a0388216a65"
|
||||
integrity sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==
|
||||
|
||||
path-parse@^1.0.6:
|
||||
version "1.0.7"
|
||||
resolved "https://registry.yarnpkg.com/path-parse/-/path-parse-1.0.7.tgz#fbc114b60ca42b30d9daf5858e4bd68bbedb6735"
|
||||
integrity sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==
|
||||
|
||||
path-to-regexp@^1.7.0:
|
||||
version "1.7.0"
|
||||
resolved "https://registry.yarnpkg.com/path-to-regexp/-/path-to-regexp-1.7.0.tgz#59fde0f435badacba103a84e9d3bc64e96b9937d"
|
||||
@@ -373,6 +431,14 @@ readdirp@^3.1.1:
|
||||
dependencies:
|
||||
picomatch "^2.0.4"
|
||||
|
||||
resolve@>=1.9.0:
|
||||
version "1.20.0"
|
||||
resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.20.0.tgz#629a013fb3f70755d6f0b7935cc1c2c5378b1975"
|
||||
integrity sha512-wENBPt4ySzg4ybFQW2TT1zMQucPK95HSh/nq2CFTZVOGut2+pQvSsgtda4d26YrYcr067wjbmzOG8byDPBX63A==
|
||||
dependencies:
|
||||
is-core-module "^2.2.0"
|
||||
path-parse "^1.0.6"
|
||||
|
||||
sinon@^7.2.7:
|
||||
version "7.3.2"
|
||||
resolved "https://registry.yarnpkg.com/sinon/-/sinon-7.3.2.tgz#82dba3a6d85f6d2181e1eca2c10d8657c2161f28"
|
||||
@@ -404,7 +470,7 @@ stacktracey@^1.2.112:
|
||||
as-table "^1.0.36"
|
||||
get-source "^1.0.34"
|
||||
|
||||
supports-color@^5.5.0:
|
||||
supports-color@^5.3.0, supports-color@^5.5.0:
|
||||
version "5.5.0"
|
||||
resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-5.5.0.tgz#e2e69a44ac8772f78a1ec0b35b689df6530efc8f"
|
||||
integrity sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==
|
||||
@@ -418,15 +484,22 @@ to-regex-range@^5.0.1:
|
||||
dependencies:
|
||||
is-number "^7.0.0"
|
||||
|
||||
ttypescript@^1.5.12:
|
||||
version "1.5.12"
|
||||
resolved "https://registry.yarnpkg.com/ttypescript/-/ttypescript-1.5.12.tgz#27a8356d7d4e719d0075a8feb4df14b52384f044"
|
||||
integrity sha512-1ojRyJvpnmgN9kIHmUnQPlEV1gq+VVsxVYjk/NfvMlHSmYxjK5hEvOOU2MQASrbekTUiUM7pR/nXeCc8bzvMOQ==
|
||||
dependencies:
|
||||
resolve ">=1.9.0"
|
||||
|
||||
type-detect@4.0.8, type-detect@^4.0.0, type-detect@^4.0.5:
|
||||
version "4.0.8"
|
||||
resolved "https://registry.yarnpkg.com/type-detect/-/type-detect-4.0.8.tgz#7646fb5f18871cfbb7749e69bd39a6388eb7450c"
|
||||
integrity sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==
|
||||
|
||||
typescript@^3.5.3:
|
||||
version "3.5.3"
|
||||
resolved "https://registry.yarnpkg.com/typescript/-/typescript-3.5.3.tgz#c830f657f93f1ea846819e929092f5fe5983e977"
|
||||
integrity sha512-ACzBtm/PhXBDId6a6sDJfroT2pOWt/oOnk4/dElG5G33ZL776N3Y6/6bKZJBFpd+b05F3Ct9qDjMeJmRWtE2/g==
|
||||
typescript@^3.9.0:
|
||||
version "3.9.9"
|
||||
resolved "https://registry.yarnpkg.com/typescript/-/typescript-3.9.9.tgz#e69905c54bc0681d0518bd4d587cc6f2d0b1a674"
|
||||
integrity sha512-kdMjTiekY+z/ubJCATUPlRDl39vXYiMV9iyeMuEuXZh2we6zz80uovNN2WlAxmmdE/Z/YQe+EbOEXB5RHEED3w==
|
||||
|
||||
ws@^7.0.0:
|
||||
version "7.1.1"
|
||||
|
||||
39
demos/python/insertion_sort.py
Normal file
@@ -0,0 +1,39 @@
|
||||
"""Python demo for sorting using VS Code Debug Visualizer."""
|
||||
import json
|
||||
|
||||
|
||||
def serialize(arr):
|
||||
"""Serialize an array into a format the visualizer can understand."""
|
||||
formatted = {
|
||||
"kind": {"grid": True},
|
||||
"rows": [
|
||||
{
|
||||
"columns": [
|
||||
{"content": str(value), "tag": str(value)} for value in arr
|
||||
],
|
||||
}
|
||||
],
|
||||
}
|
||||
return json.dumps(formatted)
|
||||
|
||||
|
||||
arr = [6, 9, 3, 12, 1, 11, 5, 13, 8, 14, 2, 4, 10, 0, 7]
|
||||
|
||||
# Put serialized into the Debug Visualizer console
|
||||
serialized = serialize(arr)
|
||||
|
||||
# Set a breakpoint on the line below and go through the code in debug mode to
|
||||
# watch it update
|
||||
for target_idx in range(1, len(arr)):
|
||||
target_value = arr[target_idx]
|
||||
compare_idx = target_idx - 1
|
||||
|
||||
while compare_idx >= 0 and arr[compare_idx] > target_value:
|
||||
arr[compare_idx + 1] = arr[compare_idx]
|
||||
serialized = serialize(arr)
|
||||
compare_idx -= 1
|
||||
|
||||
arr[compare_idx + 1] = target_value
|
||||
serialized = serialize(arr)
|
||||
|
||||
assert arr == sorted(arr)
|
||||
14
demos/rust/.vscode/launch.json
vendored
@@ -7,8 +7,18 @@
|
||||
{
|
||||
"type": "lldb",
|
||||
"request": "launch",
|
||||
"name": "Debug",
|
||||
"program": "${workspaceFolder}/target/debug/rust_demo",
|
||||
"name": "run rust demo",
|
||||
"cargo": {
|
||||
"args": [
|
||||
"build",
|
||||
"--bin=rust_demo",
|
||||
"--package=rust_demo",
|
||||
],
|
||||
"filter": {
|
||||
"name": "rust_demo",
|
||||
"kind": "bin",
|
||||
},
|
||||
},
|
||||
"args": [],
|
||||
"cwd": "${workspaceFolder}"
|
||||
}
|
||||
|
||||
8
demos/rust/.vscode/settings.json
vendored
Normal file
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"debugVisualizer.debugAdapterConfigurations": {
|
||||
"lldb": {
|
||||
"expressionTemplate": "${expr}",
|
||||
"context": "watch"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,77 +1,77 @@
|
||||
use serde::Serialize;
|
||||
|
||||
#[derive(Debug, Serialize)]
|
||||
struct Label {
|
||||
label: Option<String>,
|
||||
}
|
||||
#[derive(Debug, Serialize)]
|
||||
struct Row {
|
||||
label: Option<String>,
|
||||
columns: Vec<Column>,
|
||||
}
|
||||
#[derive(Debug, Serialize)]
|
||||
struct Column {
|
||||
content: Option<String>,
|
||||
tag: Option<String>,
|
||||
color: Option<String>,
|
||||
}
|
||||
#[derive(Debug, Serialize)]
|
||||
struct Marker {
|
||||
id: String,
|
||||
row: u64,
|
||||
column: u64,
|
||||
rows: Option<u64>,
|
||||
columns: Option<u64>,
|
||||
label: Option<String>,
|
||||
color: Option<String>,
|
||||
}
|
||||
// expected to mirror
|
||||
// https://hediet.github.io/visualization/docs/visualization-data-schema.json
|
||||
//
|
||||
// implements the grid visualizer as an example
|
||||
|
||||
#[allow(clippy::trivially_copy_pass_by_ref)]
|
||||
fn is_false(b: &bool) -> bool {
|
||||
!*b
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize)]
|
||||
struct Kind {
|
||||
#[serde(skip_serializing_if = "is_false")]
|
||||
array: bool,
|
||||
}
|
||||
pub type Label = String;
|
||||
|
||||
/// `GridVisualizationData` in schema
|
||||
#[derive(Debug, Serialize)]
|
||||
pub struct Grid {
|
||||
kind: Kind,
|
||||
#[serde(rename = "columnLabels")]
|
||||
column_labels: Option<Vec<Label>>,
|
||||
rows: Vec<Row>,
|
||||
markers: Option<Vec<Marker>>,
|
||||
|
||||
#[serde(rename = "columnLabels")]
|
||||
#[serde(skip_serializing_if="Option::is_none")]
|
||||
column_labels: Option<Vec<Label>>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize)]
|
||||
pub struct Kind {
|
||||
grid: bool,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize)]
|
||||
pub struct Row {
|
||||
columns: Vec<Column>,
|
||||
|
||||
#[serde(skip_serializing_if="Option::is_none")]
|
||||
label: Option<Label>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize)]
|
||||
pub struct Column {
|
||||
/// value to display to the user
|
||||
#[serde(skip_serializing_if="Option::is_none")]
|
||||
content: Option<String>,
|
||||
|
||||
/// unique value to identify this cell, if desired
|
||||
#[serde(skip_serializing_if="Option::is_none")]
|
||||
tag: Option<String>,
|
||||
|
||||
// TODO: valid values / syntax?
|
||||
#[serde(skip_serializing_if="Option::is_none")]
|
||||
color: Option<String>,
|
||||
}
|
||||
|
||||
fn show_arr(a: &[i32]) -> String {
|
||||
let n = a.len();
|
||||
let labels: Vec<Label> = (0..n)
|
||||
.map(|i| Label {
|
||||
label: Some(i.to_string()),
|
||||
})
|
||||
.collect();
|
||||
let columns: Vec<Column> = a
|
||||
.iter()
|
||||
.map(|x| Column {
|
||||
content: Some(format!("{:?}", x)),
|
||||
tag: Some(x.to_string()),
|
||||
color: None,
|
||||
tag: None,
|
||||
|
||||
// TODO: `color` value doesn't seem to change visualizer's output at all
|
||||
color: "who-knows".to_owned().into(),
|
||||
})
|
||||
.collect();
|
||||
|
||||
let row = Row {
|
||||
label: None,
|
||||
columns,
|
||||
// TODO: visualizer doesn't seem to render this
|
||||
label: "my row".to_owned().into(),
|
||||
};
|
||||
let kind = Kind { array: true };
|
||||
|
||||
let kind = Kind { grid: true };
|
||||
|
||||
let grid = Grid {
|
||||
kind,
|
||||
column_labels: Some(labels),
|
||||
rows: vec![row],
|
||||
markers: None,
|
||||
|
||||
// TODO: visualizer doesn't seem to work if this is non-`None`
|
||||
column_labels: None,
|
||||
};
|
||||
|
||||
serde_json::to_string(&grid).unwrap()
|
||||
@@ -81,9 +81,9 @@ fn main() {
|
||||
let mut arr = vec![1, 2, 3];
|
||||
let mut _s = show_arr(&arr);
|
||||
for _ in 0..5 {
|
||||
arr.swap(0, 2); // break point
|
||||
arr.swap(0, 2); // set a break-point here, to easily observe the change
|
||||
_s = show_arr(&arr);
|
||||
}
|
||||
dbg!(arr); // break point
|
||||
println!("Hello, world!");
|
||||
dbg!(arr);
|
||||
println!("break-point here, too");
|
||||
}
|
||||
295
docs/architecture.drawio.svg
Normal file
@@ -0,0 +1,295 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" width="241px" height="971px" viewBox="-0.5 -0.5 241 971" content="<mxfile host="localhost" modified="2020-05-18T21:20:25.100Z" agent="5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Code/1.45.1 Chrome/78.0.3904.130 Electron/7.2.4 Safari/537.36" etag="wIym9edGWidWU1rSQKSs" version="13.0.9"><diagram id="6hGFLwfOUW9BJ-s0fimq" name="Page-1">7Vpbd6I6FP41Pp4uCKDlcWq9HqVntBXlDUgqseGyIAr660+QoCB06prRdpw1L0o2ySbZ++PbF21IbTfphWbgjH2ISAMIMGlIjw0AZKXFPlPBNhMogpwJliGGmUg8CqZ4h7hQ4NI1higqTaS+TygOykLb9zxk05LMDEM/Lk979Un5qYG5RBXB1DZJVapjSJ1Meg9aR3kf4aWTP1lsqtkd18wn85NEjgn9uCCSOg2pHfo+za7cpI1IarvcLtm67jt3DxsLkUfPWtDMVmxMsuaH4xuj2/y0ob/2IEoXCA3pIXYwRdPAtNO7MXMvkznUJWwksstXTEjbJ37Ixp7vsUkP1T3xbW5QSFFSEPE99pDvIhpu2RR+V+Lm4nhpNvk4PlofyFzmFCwvtbjQ5B5fHlQfjcIuuF3esRG4RRsp4EwbieASNhJu0UbnwkiWL2CiKoo6CUVehH3v10yVjhDEiI6w94bgo0lNdgdth6LtvuEnPHRNPSFP+H4zAmMyFjqb2VwjtjfeTHuzteWStbFVO8ZcC2FPjUfAIUaPENhWgQ20jdF7oUZvhqFuU+iSHdS7kTkPiI1VgvoTYnlaYAF5Y0vD1QjMZMh02oBsLKwI9nYQDTxNWMyHwmAVtAautrHag+a4HWOrx547ZdcrQx21h8Ryv+MnZo0FUKPByk+YLDJ1hbBrZSyJ6gW8r5a9L4pV9x9ehqL7mxfwvlTx/jDqpCOTMgB0vCVmGP9TYCDMwEJPxMV8wmTKnH2vzG76LCNYgJfNZG44Vn/mwB6DydtsbYAUCuq/dnld13JhYLkva9gfYwaHHex1t5DBh51pZYEYP62+RQM3Seew6478Oo2xoStvJahNC1Dbaerr99S0nKAuCqcDuXwGnOT34cQo8y+OfgFHY/FDHD0PpNe5cBUUSconokipoCj1NgtLoWmntDRB0ZrQG4XTG3O/M9JZROoPnYU0KcBqwiDT7TK4bRe6Jpi6un5xZ2xtIoy8yW6AY2xLE8f2ShEpLkSkuBYi+AgRrX+NiKV8JjiqlYFYQQLy4Le0nmIjP0Be2fPsJi/dWAYtPUTUDGk+G2LT9T347GAvv5XPlXNBF5ODKsjqL/5YP6SOv/Q9k3SO0geT4CVT9UjQK7PBQ2pizAq2b1xs+ZT6bqopwXSeIvdO4aNF/gx2/ZhwUO8HWz5415GRvw5tVErv2MaXKK8+MlG6+XOc/Y9YZoJsFCLCUoRN2QV1fuV6//Mx2+VBKWiVISSfQiM7A19VrBRPFInKO+EuV5SdvKJoD7PDYc9CXquCvHaITMoq/o/xdwI6aEbOnqnEE2xeCwZSFQbymTAovtS5l34X78vSB4ou53214n2e2KT+FzpJEKIoK5qEZ/8GECFXEaF8HSIOenKWEX6WD04VXQ8RoliBBHq3dGaRlZY9m4cGm3kJhTXBwcUQ7oMIQxbemdZeVerfIN38/jjKQ0N5THWtqR9xNH1WS6JVE+Av0rOp1qSRw44C/zSb1rXClGsZtVqZ7dvfdyylobda4UuGPgSmPpNGkrZdbFVqAVaptVUt+/5Bo6dQNWmPk7pGz66QVgtP82ukzU31E9NmsZq9xMjaYBT/aa9VXRe+eaXXClRDwBTtw9GX5wA391JquNh97dTWsk+lDm3no3ZHMd9RqvmO+IUp8GmeIp/C8WcTHuV6CU9eTBbQPsPRmrHDDoV3zPoQhW2fUYR3q82ZU/CuLCDGFlCI5U12I9EIbGnmMAA2jflgDfuafEZTJnmaC9HAna2NUm+vU+jtvSjX6tvd1+QYYl2ScYkYU9P+nexBkeZuwqCaFP9lxK9kRLGmBMxf8d+BEpuXosTWaTF5QUqs9qpzStz/gsZZ8UbpEJhzTVzoSYDc7r4fzRGsH+Wz7Q9608VfSxOtX0eD47g85xqJtirXkKB0LRKsJtpXIsG/hHZCaDl5lQjtK5tainrXUsrx+P5nOa0l3IGyLlW5FK2x4fG/Xdn04x/kpM7/</diagram></mxfile>">
|
||||
<defs/>
|
||||
<g>
|
||||
<rect x="0" y="600" width="240" height="370" fill="none" stroke="#000000" pointer-events="all"/>
|
||||
<rect x="0" y="460" width="240" height="120" fill="none" stroke="#000000" pointer-events="all"/>
|
||||
<rect x="0" y="0" width="240" height="440" fill="none" stroke="#000000" pointer-events="all"/>
|
||||
<rect x="60" y="50" width="120" height="60" fill="#ffffff" stroke="#000000" pointer-events="all"/>
|
||||
<g transform="translate(-0.5 -0.5)">
|
||||
<switch>
|
||||
<foreignObject style="overflow: visible; text-align: left;" pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility">
|
||||
<div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 118px; height: 1px; padding-top: 80px; margin-left: 61px;">
|
||||
<div style="box-sizing: border-box; font-size: 0; text-align: center; ">
|
||||
<div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: #000000; line-height: 1.2; pointer-events: all; white-space: normal; word-wrap: normal; ">
|
||||
Extension
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</foreignObject>
|
||||
<text x="120" y="84" fill="#000000" font-family="Helvetica" font-size="12px" text-anchor="middle">
|
||||
Extension
|
||||
</text>
|
||||
</switch>
|
||||
</g>
|
||||
<rect x="60" y="170" width="120" height="60" fill="#ffffff" stroke="#000000" pointer-events="all"/>
|
||||
<g transform="translate(-0.5 -0.5)">
|
||||
<switch>
|
||||
<foreignObject style="overflow: visible; text-align: left;" pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility">
|
||||
<div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 118px; height: 1px; padding-top: 200px; margin-left: 61px;">
|
||||
<div style="box-sizing: border-box; font-size: 0; text-align: center; ">
|
||||
<div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: #000000; line-height: 1.2; pointer-events: all; white-space: normal; word-wrap: normal; ">
|
||||
JsEvaluationEngine
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</foreignObject>
|
||||
<text x="120" y="204" fill="#000000" font-family="Helvetica" font-size="12px" text-anchor="middle">
|
||||
JsEvaluationEngine
|
||||
</text>
|
||||
</switch>
|
||||
</g>
|
||||
<rect x="60" y="290" width="120" height="60" fill="#ffffff" stroke="#000000" pointer-events="all"/>
|
||||
<g transform="translate(-0.5 -0.5)">
|
||||
<switch>
|
||||
<foreignObject style="overflow: visible; text-align: left;" pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility">
|
||||
<div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 118px; height: 1px; padding-top: 320px; margin-left: 61px;">
|
||||
<div style="box-sizing: border-box; font-size: 0; text-align: center; ">
|
||||
<div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: #000000; line-height: 1.2; pointer-events: all; white-space: normal; word-wrap: normal; ">
|
||||
JsEvaluator
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</foreignObject>
|
||||
<text x="120" y="324" fill="#000000" font-family="Helvetica" font-size="12px" text-anchor="middle">
|
||||
JsEvaluator
|
||||
</text>
|
||||
</switch>
|
||||
</g>
|
||||
<rect x="60" y="490" width="120" height="60" fill="#ffffff" stroke="#000000" pointer-events="all"/>
|
||||
<g transform="translate(-0.5 -0.5)">
|
||||
<switch>
|
||||
<foreignObject style="overflow: visible; text-align: left;" pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility">
|
||||
<div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 118px; height: 1px; padding-top: 520px; margin-left: 61px;">
|
||||
<div style="box-sizing: border-box; font-size: 0; text-align: center; ">
|
||||
<div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: #000000; line-height: 1.2; pointer-events: all; white-space: normal; word-wrap: normal; ">
|
||||
DataExtractionResult
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</foreignObject>
|
||||
<text x="120" y="524" fill="#000000" font-family="Helvetica" font-size="12px" text-anchor="middle">
|
||||
DataExtractionResult
|
||||
</text>
|
||||
</switch>
|
||||
</g>
|
||||
<path d="M 120 125.99 L 120 167.76" fill="none" stroke="#000000" stroke-miterlimit="10" pointer-events="stroke"/>
|
||||
<path d="M 120 110.99 L 124.41 118.49 L 120 125.99 L 115.59 118.49 Z" fill="#000000" stroke="#000000" stroke-miterlimit="10" pointer-events="all"/>
|
||||
<path d="M 113.5 155.88 L 120 168.88 L 126.5 155.88" fill="none" stroke="#000000" stroke-miterlimit="10" pointer-events="all"/>
|
||||
<g transform="translate(-0.5 -0.5)">
|
||||
<switch>
|
||||
<foreignObject style="overflow: visible; text-align: left;" pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility">
|
||||
<div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe flex-end; justify-content: unsafe flex-start; width: 1px; height: 1px; padding-top: 107px; margin-left: 125px;">
|
||||
<div style="box-sizing: border-box; font-size: 0; text-align: left; ">
|
||||
<div style="display: inline-block; font-size: 11px; font-family: Helvetica; color: #000000; line-height: 1.2; pointer-events: all; background-color: #ffffff; white-space: nowrap; ">
|
||||
1
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</foreignObject>
|
||||
<text x="125" y="107" fill="#000000" font-family="Helvetica" font-size="11px">
|
||||
1
|
||||
</text>
|
||||
</switch>
|
||||
</g>
|
||||
<path d="M 120 230 L 120 287.76" fill="none" stroke="#000000" stroke-miterlimit="10" stroke-dasharray="3 3" pointer-events="stroke"/>
|
||||
<path d="M 113.5 275.88 L 120 288.88 L 126.5 275.88" fill="none" stroke="#000000" stroke-miterlimit="10" pointer-events="all"/>
|
||||
<g transform="translate(-0.5 -0.5)">
|
||||
<switch>
|
||||
<foreignObject style="overflow: visible; text-align: left;" pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility">
|
||||
<div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 1px; height: 1px; padding-top: 260px; margin-left: 120px;">
|
||||
<div style="box-sizing: border-box; font-size: 0; text-align: center; ">
|
||||
<div style="display: inline-block; font-size: 11px; font-family: Helvetica; color: #000000; line-height: 1.2; pointer-events: all; background-color: #ffffff; white-space: nowrap; ">
|
||||
Creates
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</foreignObject>
|
||||
<text x="120" y="263" fill="#000000" font-family="Helvetica" font-size="11px" text-anchor="middle">
|
||||
Creates
|
||||
</text>
|
||||
</switch>
|
||||
</g>
|
||||
<path d="M 120 350 L 120 487.76" fill="none" stroke="#000000" stroke-miterlimit="10" stroke-dasharray="3 3" pointer-events="stroke"/>
|
||||
<path d="M 113.5 475.88 L 120 488.88 L 126.5 475.88" fill="none" stroke="#000000" stroke-miterlimit="10" pointer-events="all"/>
|
||||
<g transform="translate(-0.5 -0.5)">
|
||||
<switch>
|
||||
<foreignObject style="overflow: visible; text-align: left;" pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility">
|
||||
<div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 1px; height: 1px; padding-top: 420px; margin-left: 120px;">
|
||||
<div style="box-sizing: border-box; font-size: 0; text-align: center; ">
|
||||
<div style="display: inline-block; font-size: 11px; font-family: Helvetica; color: #000000; line-height: 1.2; pointer-events: all; background-color: #ffffff; white-space: nowrap; ">
|
||||
Evaluates Expression To
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</foreignObject>
|
||||
<text x="120" y="423" fill="#000000" font-family="Helvetica" font-size="11px" text-anchor="middle">
|
||||
Evaluates Expression To
|
||||
</text>
|
||||
</switch>
|
||||
</g>
|
||||
<rect x="0" y="0" width="70" height="20" fill="none" stroke="none" pointer-events="all"/>
|
||||
<g transform="translate(-0.5 -0.5)">
|
||||
<switch>
|
||||
<foreignObject style="overflow: visible; text-align: left;" pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility">
|
||||
<div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 1px; height: 1px; padding-top: 10px; margin-left: 35px;">
|
||||
<div style="box-sizing: border-box; font-size: 0; text-align: center; ">
|
||||
<div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: #000000; line-height: 1.2; pointer-events: all; white-space: nowrap; ">
|
||||
extension
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</foreignObject>
|
||||
<text x="35" y="14" fill="#000000" font-family="Helvetica" font-size="12px" text-anchor="middle">
|
||||
extension
|
||||
</text>
|
||||
</switch>
|
||||
</g>
|
||||
<rect x="0" y="460" width="50" height="20" fill="none" stroke="none" pointer-events="all"/>
|
||||
<g transform="translate(-0.5 -0.5)">
|
||||
<switch>
|
||||
<foreignObject style="overflow: visible; text-align: left;" pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility">
|
||||
<div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 1px; height: 1px; padding-top: 470px; margin-left: 25px;">
|
||||
<div style="box-sizing: border-box; font-size: 0; text-align: center; ">
|
||||
<div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: #000000; line-height: 1.2; pointer-events: all; white-space: nowrap; ">
|
||||
shared
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</foreignObject>
|
||||
<text x="25" y="474" fill="#000000" font-family="Helvetica" font-size="12px" text-anchor="middle">
|
||||
shared
|
||||
</text>
|
||||
</switch>
|
||||
</g>
|
||||
<rect x="60" y="630" width="120" height="60" fill="#ffffff" stroke="#000000" pointer-events="all"/>
|
||||
<g transform="translate(-0.5 -0.5)">
|
||||
<switch>
|
||||
<foreignObject style="overflow: visible; text-align: left;" pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility">
|
||||
<div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 118px; height: 1px; padding-top: 660px; margin-left: 61px;">
|
||||
<div style="box-sizing: border-box; font-size: 0; text-align: center; ">
|
||||
<div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: #000000; line-height: 1.2; pointer-events: all; white-space: normal; word-wrap: normal; ">
|
||||
Model.state
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</foreignObject>
|
||||
<text x="120" y="664" fill="#000000" font-family="Helvetica" font-size="12px" text-anchor="middle">
|
||||
Model.state
|
||||
</text>
|
||||
</switch>
|
||||
</g>
|
||||
<rect x="0" y="600" width="60" height="20" fill="none" stroke="none" pointer-events="all"/>
|
||||
<g transform="translate(-0.5 -0.5)">
|
||||
<switch>
|
||||
<foreignObject style="overflow: visible; text-align: left;" pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility">
|
||||
<div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 1px; height: 1px; padding-top: 610px; margin-left: 30px;">
|
||||
<div style="box-sizing: border-box; font-size: 0; text-align: center; ">
|
||||
<div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: #000000; line-height: 1.2; pointer-events: all; white-space: nowrap; ">
|
||||
webview
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</foreignObject>
|
||||
<text x="30" y="614" fill="#000000" font-family="Helvetica" font-size="12px" text-anchor="middle">
|
||||
webview
|
||||
</text>
|
||||
</switch>
|
||||
</g>
|
||||
<path d="M 120 550 L 120 627.76" fill="none" stroke="#000000" stroke-miterlimit="10" stroke-dasharray="3 3" pointer-events="stroke"/>
|
||||
<path d="M 113.5 615.88 L 120 628.88 L 126.5 615.88" fill="none" stroke="#000000" stroke-miterlimit="10" pointer-events="all"/>
|
||||
<g transform="translate(-0.5 -0.5)">
|
||||
<switch>
|
||||
<foreignObject style="overflow: visible; text-align: left;" pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility">
|
||||
<div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 1px; height: 1px; padding-top: 590px; margin-left: 120px;">
|
||||
<div style="box-sizing: border-box; font-size: 0; text-align: center; ">
|
||||
<div style="display: inline-block; font-size: 11px; font-family: Helvetica; color: #000000; line-height: 1.2; pointer-events: all; background-color: #ffffff; white-space: nowrap; ">
|
||||
Sent To
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</foreignObject>
|
||||
<text x="120" y="593" fill="#000000" font-family="Helvetica" font-size="11px" text-anchor="middle">
|
||||
Sent To
|
||||
</text>
|
||||
</switch>
|
||||
</g>
|
||||
<rect x="60" y="760" width="150" height="60" fill="#ffffff" stroke="#000000" pointer-events="all"/>
|
||||
<g transform="translate(-0.5 -0.5)">
|
||||
<switch>
|
||||
<foreignObject style="overflow: visible; text-align: left;" pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility">
|
||||
<div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 148px; height: 1px; padding-top: 790px; margin-left: 61px;">
|
||||
<div style="box-sizing: border-box; font-size: 0; text-align: center; ">
|
||||
<div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: #000000; line-height: 1.2; pointer-events: all; white-space: normal; word-wrap: normal; ">
|
||||
Visualizer.renderContent
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</foreignObject>
|
||||
<text x="135" y="794" fill="#000000" font-family="Helvetica" font-size="12px" text-anchor="middle">
|
||||
Visualizer.renderContent
|
||||
</text>
|
||||
</switch>
|
||||
</g>
|
||||
<path d="M 120 690 L 130.17 757.79" fill="none" stroke="#000000" stroke-miterlimit="10" stroke-dasharray="3 3" pointer-events="stroke"/>
|
||||
<path d="M 121.98 747 L 130.33 758.89 L 134.83 745.07" fill="none" stroke="#000000" stroke-miterlimit="10" pointer-events="all"/>
|
||||
<g transform="translate(-0.5 -0.5)">
|
||||
<switch>
|
||||
<foreignObject style="overflow: visible; text-align: left;" pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility">
|
||||
<div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 1px; height: 1px; padding-top: 725px; margin-left: 125px;">
|
||||
<div style="box-sizing: border-box; font-size: 0; text-align: center; ">
|
||||
<div style="display: inline-block; font-size: 11px; font-family: Helvetica; color: #000000; line-height: 1.2; pointer-events: all; background-color: #ffffff; white-space: nowrap; ">
|
||||
Rendered In
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</foreignObject>
|
||||
<text x="125" y="728" fill="#000000" font-family="Helvetica" font-size="11px" text-anchor="middle">
|
||||
Rendered In
|
||||
</text>
|
||||
</switch>
|
||||
</g>
|
||||
<rect x="60" y="880" width="130" height="60" fill="#ffffff" stroke="#000000" pointer-events="all"/>
|
||||
<g transform="translate(-0.5 -0.5)">
|
||||
<switch>
|
||||
<foreignObject style="overflow: visible; text-align: left;" pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility">
|
||||
<div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 128px; height: 1px; padding-top: 910px; margin-left: 61px;">
|
||||
<div style="box-sizing: border-box; font-size: 0; text-align: center; ">
|
||||
<div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: #000000; line-height: 1.2; pointer-events: all; white-space: normal; word-wrap: normal; ">
|
||||
Visualization.render
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</foreignObject>
|
||||
<text x="125" y="914" fill="#000000" font-family="Helvetica" font-size="12px" text-anchor="middle">
|
||||
Visualization.render
|
||||
</text>
|
||||
</switch>
|
||||
</g>
|
||||
<path d="M 132.5 820 L 127.69 877.77" fill="none" stroke="#000000" stroke-miterlimit="10" stroke-dasharray="3 3" pointer-events="stroke"/>
|
||||
<path d="M 122.19 865.39 L 127.59 878.89 L 135.15 866.47" fill="none" stroke="#000000" stroke-miterlimit="10" pointer-events="all"/>
|
||||
<g transform="translate(-0.5 -0.5)">
|
||||
<switch>
|
||||
<foreignObject style="overflow: visible; text-align: left;" pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility">
|
||||
<div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 1px; height: 1px; padding-top: 850px; margin-left: 130px;">
|
||||
<div style="box-sizing: border-box; font-size: 0; text-align: center; ">
|
||||
<div style="display: inline-block; font-size: 11px; font-family: Helvetica; color: #000000; line-height: 1.2; pointer-events: all; background-color: #ffffff; white-space: nowrap; ">
|
||||
Rendered In
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</foreignObject>
|
||||
<text x="130" y="853" fill="#000000" font-family="Helvetica" font-size="11px" text-anchor="middle">
|
||||
Rendered In
|
||||
</text>
|
||||
</switch>
|
||||
</g>
|
||||
</g>
|
||||
<switch>
|
||||
<g requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility"/>
|
||||
<a transform="translate(0,-5)" xlink:href="https://desk.draw.io/support/solutions/articles/16000042487" target="_blank">
|
||||
<text text-anchor="middle" font-size="10px" x="50%" y="100%">
|
||||
Viewer does not support full SVG 1.1
|
||||
</text>
|
||||
</a>
|
||||
</switch>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 24 KiB |
BIN
docs/ast-demo.gif
Normal file
|
After Width: | Height: | Size: 2.2 MiB |
BIN
docs/custom-script-map.png
Normal file
|
After Width: | Height: | Size: 37 KiB |
|
Before Width: | Height: | Size: 3.5 MiB After Width: | Height: | Size: 4.2 MiB |
|
Before Width: | Height: | Size: 19 KiB After Width: | Height: | Size: 17 KiB |
@@ -3,15 +3,13 @@
|
||||
[data-extraction]
|
||||
[webview]
|
||||
[extension]
|
||||
[visualization]
|
||||
[visualization-playground]
|
||||
[hediet/visualization]
|
||||
|
||||
[webview] ..> [extension]: "Uses RPC contracts, connects using websockets"
|
||||
[extension] ..> [webview]: "Loads into a Webview in VS Code"
|
||||
|
||||
[webview] ..> [visualization]: "Uses React Components"
|
||||
[webview] ..> [hediet/visualization]: "Uses React Components"
|
||||
|
||||
[visualization-playground] ..> [visualization]: "Uses React Components"
|
||||
|
||||
[extension] ..> [data-extraction]: "Injects Runtime into JavaScript applications"
|
||||
|
||||
|
||||
BIN
docs/table-demo.gif
Normal file
|
After Width: | Height: | Size: 2.6 MiB |
BIN
docs/visualization-plotly-random-walk.gif
Normal file
|
After Width: | Height: | Size: 1.4 MiB |
@@ -1,2 +1 @@
|
||||
node_modules
|
||||
ui/node_modules
|
||||
./node_modules
|
||||
@@ -1,5 +1,29 @@
|
||||
# Change Log
|
||||
|
||||
## 2.2.0
|
||||
|
||||
- Adds setting `debugVisualizer.js.customScriptPaths` to inject custom scripts when debugging JavaScript. Such scripts are reloaded automatically when changed.
|
||||
|
||||
## 2.1.0
|
||||
|
||||
- Adds text diff and text decoration viewer.
|
||||
- Supports more js debug adapters
|
||||
|
||||
## 2.0.6
|
||||
|
||||
- Fixes grid extractor bug
|
||||
- Fixes Perspective.js bug
|
||||
- Fixes AST visualizer bug
|
||||
- Adds table data extractor
|
||||
- Improves typescript data extractor
|
||||
|
||||
## 2.0.0
|
||||
|
||||
- Support for the new JS debug adapters
|
||||
- Moved visualizations to [github.com/hediet/visualization](https://github.com/hediet/visualization).
|
||||
- New Visualizations / Improved design / Improved dark theme
|
||||
- New Logo
|
||||
|
||||
## 1.1.0
|
||||
|
||||
- Fixes a bug that occurs when the debug visualizer is opened after the debug session has started.
|
||||
|
||||
@@ -3,218 +3,77 @@
|
||||
[](https://twitter.com/intent/follow?screen_name=hediet_dev)
|
||||
|
||||
A VS Code extension for visualizing data structures while debugging.
|
||||
Like the VS Code's watch view, but with custom visualizations.
|
||||
Like the VS Code's watch view, but with rich visualizations of the watched value.
|
||||
|
||||

|
||||
|
||||
## [Visualization Playground](https://hediet.github.io/visualization/)
|
||||
|
||||
Click [here](https://hediet.github.io/visualization/) to explore all available visualizations.
|
||||
|
||||
## Supported Languages
|
||||
|
||||
See [demos](./demos/) for demos. These languages and debuggers are verified to work with this extension:
|
||||
See [demos](../demos/) for demos. These languages and debuggers are verified to work with this extension:
|
||||
|
||||
- JavaScript/TypeScript/... using `node`/`node2`/`extensionHost` or `chrome` debug adapter: [⭐ Full Support](../demos/js)
|
||||
- Go using `go` (Delve) debug adapter: [✅ Rudimentary Support](../demos/golang)
|
||||
- Python using `python` debug adapter: [✅ Rudimentary Support](../demos/python)
|
||||
- C# using `coreclr` debug adapter: [✅ Rudimentary Support](../demos/csharp)
|
||||
- PHP using `php` debug adapter: [✅ Rudimentary Support](../demos/php)
|
||||
- Java using `java` debug adapter: [✅ Rudimentary Support](../demos/java)
|
||||
- C++ using `cppdbg` debug adapter: [✅ Rudimentary Support](../demos/cpp)
|
||||
- Swift using `lldb` debug adapter: [✅ Rudimentary Support](../demos/swift)
|
||||
- Rust using `lldb` debug adapter: [✅ Rudimentary Support](../demos/rust)
|
||||
- JavaScript/TypeScript/... using `node`/`node2`/`extensionHost`/`chrome`/`pwa-chrome`/`pwa-node` debug adapter: [⭐ Full Support](../demos/js)
|
||||
- Dart/Flutter using `dart` debug adapter: [✅ Basic Support](../demos/dart)
|
||||
- Go using `go` (Delve) debug adapter: [✅ Basic Support](../demos/golang)
|
||||
- Python using `python` debug adapter: [✅ Basic Support](../demos/python)
|
||||
- C# using `coreclr` debug adapter: [✅ Basic Support](../demos/csharp) (work in progress for Full Support)
|
||||
- PHP using `php` debug adapter: [✅ Basic Support](../demos/php)
|
||||
- Java using `java` debug adapter: [✅ Basic Support](../demos/java)
|
||||
- C++ using `cppdbg` debug adapter: [✅ Basic Support](../demos/cpp)
|
||||
- Swift using `lldb` debug adapter: [✅ Basic Support](../demos/swift)
|
||||
- Rust using `lldb` debug adapter: [✅ Basic Support](../demos/rust)
|
||||
|
||||
All other languages and debuggers might work too.
|
||||
For languages with _Rudimentary Support_, only JSON strings can be visualized - you must implement some logic that builds this JSON for your data structure!
|
||||
For languages with _Basic Support_, only JSON strings can be visualized - you must implement some logic that builds this JSON for your data structure!
|
||||
Fully supported languages offer _Data Extractors_ which convert some well known data structures to json.
|
||||
|
||||
## Usage
|
||||
|
||||
After installing this extension, use the command `Debug Visualizer: New View` to open a new visualizer view.
|
||||
In this view you can enter an expression that is evaluated and visualized while stepping through your application.
|
||||
This view works the same as the watch view of VS Code, except that the resulting value is presented visually rather than textually.
|
||||
This view works the same as the watch view of VS Code, except that the resulting value is presented visually rather than textually and you can only watch one expression (but you can still open multiple windows).
|
||||
|
||||
You can refresh the evaluation and open the current visualizer view into a new browser window by using the top right buttons.
|
||||
You can also unfold the details pane to select a _Data Extractor_ and a _Visualizer_.
|
||||
|
||||
Use the command `Debug Visualizer: Use Selection as Expression` (Shift + F1) to use the currently selected text as expression
|
||||
Use the command `Debug Visualizer: Use Selection as Expression` _(Shift + F1)_ to use the currently selected text as expression
|
||||
in the most recently opened debug visualizer.
|
||||
|
||||
## Supported Values
|
||||
|
||||
Visualizers consume specific JSON data. See [Integrated Visualizers](#Integrated%20Visualizers) for the schema of supported JSON data.
|
||||
Not all values can be processed.
|
||||
Visualizers consume specific JSON data. This extension uses [hediet/visualization](https://github.com/hediet/visualization), a generic visualization framework.
|
||||
You can see in its [playground](https://hediet.github.io/visualization/) which data can be visualized and how the visualization looks like.
|
||||
|
||||
The currently watched expression should evaluate to a JSON Object string,
|
||||
matching the schema of one of the supported visualizers. This JSON string may be surrounded by single or double quotation marks (or none at all) and must not be escaped.
|
||||
matching the [schema](https://hediet.github.io/visualization/docs/visualization-data-schema.json) of one of the supported visualizers. This JSON string may be surrounded by single or double quotation marks (or none at all) and must not be escaped.
|
||||
A valid example is `"{ "kind": { "text": true }, "text": "some text\nmore text" }"`.
|
||||
Use the watch window to see what an expression evaluates to. This extension simply interprets that result.
|
||||
|
||||
For some languages (TypeScript/JavaScript), runtime code is injected to support _Data Extractors_.
|
||||
A Data Extractor lifts the requirement for the visualized value to be a JSON string
|
||||
and acts as a bridge between custom data structures and the JSON data processed by the visualizers.
|
||||
When multiple Data Extractors are applicable, a preferred one can be selected in the visualization view.
|
||||
|
||||
## Integrated Visualizers
|
||||
There is a [JSON Schema for all supported visualizations](https://hediet.github.io/visualization/docs/visualization-data-schema.json) and a [typescript declaration file](https://hediet.github.io/visualization/docs/visualization-data.ts).
|
||||
|
||||
The following visualizers are built into this extension.
|
||||
## Selected Demos
|
||||
|
||||
### Graph Visualization
|
||||
### Reversing a Doubly Linked List
|
||||
|
||||
[Demo](https://hediet.github.io/vscode-debug-visualizer/?value=%7B%0A++++%22kind%22%3A+%7B+%22graph%22%3A+true+%7D%2C%0A++++%22nodes%22%3A+%5B%0A++++++++%7B+%22id%22%3A+%221%22%2C+%22label%22%3A+%221%22+%7D%2C%0A++++++++%7B+%22id%22%3A+%222%22%2C+%22label%22%3A+%222%22%2C+%22color%22%3A+%22orange%22+%7D%2C%0A++++++++%7B+%22id%22%3A+%223%22%2C+%22label%22%3A+%223%22+%7D%0A++++%5D%2C%0A++++%22edges%22%3A+%5B%0A++++++++%7B+%22from%22%3A+%221%22%2C+%22to%22%3A+%222%22%2C+%22color%22%3A+%22red%22+%7D%2C%0A++++++++%7B+%22from%22%3A+%221%22%2C+%22to%22%3A+%223%22+%7D%0A++++%5D%0A%7D).
|
||||
The Graphviz and vis.js visualizers render data that matches the `Graph` interface.
|
||||

|
||||
|
||||
```ts
|
||||
interface Graph {
|
||||
kind: { graph: true };
|
||||
nodes: NodeGraphData[];
|
||||
edges: EdgeGraphData[];
|
||||
}
|
||||
### Random Walk
|
||||
|
||||
interface NodeGraphData {
|
||||
id: string;
|
||||
label?: string;
|
||||
color?: string;
|
||||
shape?: "ellipse" | "box";
|
||||
}
|
||||

|
||||
|
||||
interface EdgeGraphData {
|
||||
from: string;
|
||||
to: string;
|
||||
label?: string;
|
||||
id?: string;
|
||||
color?: string;
|
||||
dashes?: boolean;
|
||||
}
|
||||
```
|
||||
### Ast Visualizer
|
||||
|
||||
The graphviz visualizer uses the SVG viewer to render the SVG created by `viz.js`.
|
||||

|
||||
|
||||

|
||||

|
||||
### Table Visualizer
|
||||
|
||||
### Plotly Visualization
|
||||
|
||||
[Demo](https://hediet.github.io/vscode-debug-visualizer/?value=%7B%22kind%22%3A%7B%22plotly%22%3Atrue%7D%2C%22data%22%3A%5B%7B%22y%22%3A%5B0%2C1%2C2%2C3%2C4%2C3%2C4%2C3%2C2%2C1%2C2%5D%7D%5D%7D).
|
||||
The plotly visualizer uses plotly and can visualize JSON data matching the following interface:
|
||||
|
||||
```ts
|
||||
export interface Plotly {
|
||||
kind: { plotly: true };
|
||||
data: Partial<Plotly.Data>[];
|
||||
}
|
||||
// See plotly docs for Plotly.Data.
|
||||
```
|
||||
|
||||

|
||||
|
||||
### Tree Visualization
|
||||
|
||||
The tree visualizer renders data that matches the `Tree` interface.
|
||||
|
||||
```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;
|
||||
}
|
||||
```
|
||||
|
||||

|
||||
|
||||
### AST Visualization
|
||||
|
||||
[Demo](https://hediet.github.io/vscode-debug-visualizer/?value=%7B%22kind%22%3A%7B%22text%22%3Atrue%2C%22tree%22%3Atrue%2C%22ast%22%3Atrue%7D%2C%22root%22%3A%7B%22name%22%3A%22SourceFile%22%2C%22id%22%3A%22root%22%2C%22children%22%3A%5B%7B%22name%22%3A%22ClassDeclaration%22%2C%22id%22%3A%22statements%22%2C%22children%22%3A%5B%7B%22name%22%3A%22Identifier%22%2C%22id%22%3A%22name%22%2C%22children%22%3A%5B%5D%2C%22data%22%3A%7B%22length%22%3A6%2C%22position%22%3A6%7D%2C%22isMarked%22%3Afalse%2C%22value%22%3A%22Test1%22%7D%2C%7B%22name%22%3A%22MethodDeclaration%22%2C%22id%22%3A%22members%22%2C%22children%22%3A%5B%7B%22name%22%3A%22PublicKeyword%22%2C%22id%22%3A%22modifiers%22%2C%22children%22%3A%5B%5D%2C%22data%22%3A%7B%22length%22%3A8%2C%22position%22%3A14%7D%2C%22isMarked%22%3Afalse%7D%2C%7B%22name%22%3A%22Identifier%22%2C%22id%22%3A%22name%22%2C%22children%22%3A%5B%5D%2C%22data%22%3A%7B%22length%22%3A4%2C%22position%22%3A22%7D%2C%22isMarked%22%3Afalse%2C%22value%22%3A%22foo%22%7D%2C%7B%22name%22%3A%22Parameter%22%2C%22id%22%3A%22parameters%22%2C%22children%22%3A%5B%7B%22name%22%3A%22Identifier%22%2C%22id%22%3A%22name%22%2C%22children%22%3A%5B%5D%2C%22data%22%3A%7B%22length%22%3A1%2C%22position%22%3A27%7D%2C%22isMarked%22%3Afalse%2C%22value%22%3A%22a%22%7D%2C%7B%22name%22%3A%22NumberKeyword%22%2C%22id%22%3A%22type%22%2C%22children%22%3A%5B%5D%2C%22data%22%3A%7B%22length%22%3A7%2C%22position%22%3A29%7D%2C%22isMarked%22%3Afalse%7D%5D%2C%22data%22%3A%7B%22length%22%3A9%2C%22position%22%3A27%7D%2C%22isMarked%22%3Afalse%7D%2C%7B%22name%22%3A%22Block%22%2C%22id%22%3A%22body%22%2C%22children%22%3A%5B%7B%22name%22%3A%22VariableStatement%22%2C%22id%22%3A%22statements%22%2C%22children%22%3A%5B%7B%22name%22%3A%22VariableDeclarationList%22%2C%22id%22%3A%22declarationList%22%2C%22children%22%3A%5B%7B%22name%22%3A%22VariableDeclaration%22%2C%22id%22%3A%22declarations%22%2C%22children%22%3A%5B%7B%22name%22%3A%22Identifier%22%2C%22id%22%3A%22name%22%2C%22children%22%3A%5B%5D%2C%22data%22%3A%7B%22length%22%3A2%2C%22position%22%3A47%7D%2C%22isMarked%22%3Afalse%2C%22value%22%3A%22x%22%7D%2C%7B%22name%22%3A%22ObjectLiteralExpression%22%2C%22id%22%3A%22initializer%22%2C%22children%22%3A%5B%7B%22name%22%3A%22PropertyAssignment%22%2C%22id%22%3A%22properties%22%2C%22children%22%3A%5B%7B%22name%22%3A%22Identifier%22%2C%22id%22%3A%22name%22%2C%22children%22%3A%5B%5D%2C%22data%22%3A%7B%22length%22%3A2%2C%22position%22%3A53%7D%2C%22isMarked%22%3Afalse%2C%22value%22%3A%22a%22%7D%2C%7B%22name%22%3A%22FirstLiteralToken%22%2C%22id%22%3A%22initializer%22%2C%22children%22%3A%5B%5D%2C%22data%22%3A%7B%22length%22%3A2%2C%22position%22%3A56%7D%2C%22isMarked%22%3Afalse%2C%22value%22%3A%225%22%7D%5D%2C%22data%22%3A%7B%22length%22%3A5%2C%22position%22%3A53%7D%2C%22isMarked%22%3Afalse%7D%5D%2C%22data%22%3A%7B%22length%22%3A9%2C%22position%22%3A51%7D%2C%22isMarked%22%3Afalse%7D%5D%2C%22data%22%3A%7B%22length%22%3A13%2C%22position%22%3A47%7D%2C%22isMarked%22%3Afalse%7D%5D%2C%22data%22%3A%7B%22length%22%3A21%2C%22position%22%3A39%7D%2C%22isMarked%22%3Afalse%7D%5D%2C%22data%22%3A%7B%22length%22%3A22%2C%22position%22%3A39%7D%2C%22isMarked%22%3Afalse%7D%5D%2C%22data%22%3A%7B%22length%22%3A27%2C%22position%22%3A37%7D%2C%22isMarked%22%3Afalse%7D%5D%2C%22data%22%3A%7B%22length%22%3A50%2C%22position%22%3A14%7D%2C%22isMarked%22%3Afalse%7D%5D%2C%22data%22%3A%7B%22length%22%3A66%2C%22position%22%3A0%7D%2C%22isMarked%22%3Afalse%7D%2C%7B%22name%22%3A%22EndOfFileToken%22%2C%22id%22%3A%22endOfFileToken%22%2C%22children%22%3A%5B%5D%2C%22data%22%3A%7B%22length%22%3A1%2C%22position%22%3A66%7D%2C%22isMarked%22%3Afalse%7D%5D%2C%22data%22%3A%7B%22length%22%3A67%2C%22position%22%3A0%7D%2C%22isMarked%22%3Atrue%7D%2C%22text%22%3A%22%5Cnclass+Test1+%7B%5Cn%5Ctpublic+foo%28a%3A+number%29+%7B%5Cn%5Ct%5Ctconst+x+%3D+%7B+a%3A+5+%7D%3B%5Cn%5Ct%7D%5Cn%7D%5Cn%22%2C%22fileName%22%3A%22index.ts%22%7D).
|
||||
The AST (Abstract Syntax Tree) 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 };
|
||||
}
|
||||
```
|
||||
|
||||
Additionally to the tree view, the source code is rendered and when selecting an AST node,
|
||||
its span in the source code is highlighted.
|
||||
|
||||

|
||||
|
||||
### Grid Visualization
|
||||
|
||||
Visualizes data matching the following interface:
|
||||
|
||||
```ts
|
||||
export interface Grid {
|
||||
kind: { array: true };
|
||||
columnLabels?: { label?: string }[];
|
||||
rows: {
|
||||
label?: string;
|
||||
columns: {
|
||||
content?: string;
|
||||
tag?: string;
|
||||
color?: string;
|
||||
}[];
|
||||
}[];
|
||||
markers?: {
|
||||
id: string;
|
||||
|
||||
row: number;
|
||||
column: number;
|
||||
rows?: number;
|
||||
columns?: number;
|
||||
|
||||
label?: string;
|
||||
color?: string;
|
||||
}[];
|
||||
}
|
||||
```
|
||||
|
||||

|
||||
|
||||
### 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.
|
||||

|
||||
|
||||
## JavaScript/TypeScript Integrated Data Extractors
|
||||
|
||||
@@ -224,35 +83,22 @@ Custom data extractors can be registered too.
|
||||
See the package `@hediet/debug-visualizer-data-extraction` and its [README](../data-extraction/README.md) for the implementation and its API.
|
||||
Also, a global object of name `hedietDbgVis` with helper functions is injected.
|
||||
|
||||
### ToString
|
||||
|
||||
Just calls `.toString()` on values and treats the result as text.
|
||||
|
||||
### TypeScript AST
|
||||
|
||||
- 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.
|
||||
|
||||
### Plotly y-Values
|
||||
|
||||
Uses plotly to plot an array of numbers.
|
||||
|
||||
### Object Graph
|
||||
|
||||
Constructs a graph containing all objects reachable from object the expression evaluates to.
|
||||
Graph is constructed using a breadth search. Stops after 50 nodes.
|
||||
|
||||
### Array Grid
|
||||
|
||||
Creates Grid visualization data for an array.
|
||||
- **ToString**
|
||||
- Just calls `.toString()` on values and treats the result as text.
|
||||
- **TypeScript AST**
|
||||
- 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 'getVisualizationData'**
|
||||
- Calls `.getVisualizationData()` on values and treats the result as direct input to the visualizer.
|
||||
- **Plotly y-Values**
|
||||
- Uses plotly to plot an array of numbers.
|
||||
- **Object Graph**
|
||||
- Constructs a graph containing all objects reachable from object the expression evaluates to.
|
||||
Graph is constructed using a breadth search. Stops after 50 nodes.
|
||||
- **Array Grid**
|
||||
- Creates Grid visualization data for an array.
|
||||
|
||||
## UI Features
|
||||
|
||||
@@ -282,10 +128,53 @@ This extension provides these configuration options:
|
||||
|
||||
Configurations here overwrite the built-in support for the corresponding debug adapter type.
|
||||
|
||||
* `debugVisualizer.useChromeKioskMode`
|
||||
- `debugVisualizer.useChromeKioskMode`
|
||||
|
||||
Specifies whether to pop out Debug Visualization Views with Chrome in Kiosk Mode. Uses the default browser otherwise or if Chrome is not found. Defaults to `true`.
|
||||
|
||||
- `debugVisualizer.js.customScriptPaths`
|
||||
|
||||
Specifies a list of JavaScript files that are injected into the debugee when debugging JavaScript.
|
||||
Each script must assign `module.exports` with a function of type `import("@hediet/debug-visualizer-data-extraction").LoadDataExtractorsFn`.
|
||||
Paths must be absolute and can use the variable `${workspaceFolder}`.
|
||||
Scripts are automatically reloaded when they are changed.
|
||||
|
||||
Example:
|
||||
|
||||
```js
|
||||
// @ts-check
|
||||
/**
|
||||
* @type {import("@hediet/debug-visualizer-data-extraction").LoadDataExtractorsFn}
|
||||
*/
|
||||
module.exports = (register, helpers) => {
|
||||
register({
|
||||
id: "map",
|
||||
getExtractions(data, collector, context) {
|
||||
if (!(data instanceof Map)) {
|
||||
return;
|
||||
}
|
||||
|
||||
collector.addExtraction({
|
||||
priority: 1000,
|
||||
id: "map",
|
||||
name: "Map",
|
||||
extractData() {
|
||||
return helpers.asData({
|
||||
kind: { table: true },
|
||||
rows: [...data].map(([k, v]) => ({
|
||||
key: k,
|
||||
value: v,
|
||||
})),
|
||||
});
|
||||
},
|
||||
});
|
||||
},
|
||||
});
|
||||
};
|
||||
```
|
||||
|
||||

|
||||
|
||||
# See Also
|
||||
|
||||
This extension works very well together with my library [`@hediet/node-reload`](https://github.com/hediet/node-reload) for TypeScript/JavaScript.
|
||||
|
||||
BIN
extension/docs/logo.drawio.png
Normal file
|
After Width: | Height: | Size: 5.0 KiB |
|
Before Width: | Height: | Size: 2.8 KiB |
@@ -2,12 +2,12 @@
|
||||
"name": "debug-visualizer",
|
||||
"private": true,
|
||||
"displayName": "Debug Visualizer",
|
||||
"description": "An extension that visualizes values while debugging.",
|
||||
"icon": "docs/logo.png",
|
||||
"version": "1.1.0",
|
||||
"description": "A visual watch window that lets you visualize your data structures while debugging.",
|
||||
"icon": "docs/logo.drawio.png",
|
||||
"version": "2.2.1",
|
||||
"license": "GPL-3.0",
|
||||
"engines": {
|
||||
"vscode": "^1.35.0"
|
||||
"vscode": "^1.46.0"
|
||||
},
|
||||
"publisher": "hediet",
|
||||
"keywords": [
|
||||
@@ -17,7 +17,8 @@
|
||||
"visual",
|
||||
"javascript",
|
||||
"graph",
|
||||
"ast"
|
||||
"ast",
|
||||
"visualization"
|
||||
],
|
||||
"bugs": {
|
||||
"url": "https://github.com/hediet/vscode-debug-visualizer/issues"
|
||||
@@ -40,6 +41,11 @@
|
||||
"onDebug",
|
||||
"onWebviewPanel:debugVisualizer"
|
||||
],
|
||||
"capabilities": {
|
||||
"untrustedWorkspaces": {
|
||||
"supported": false
|
||||
}
|
||||
},
|
||||
"main": "./dist/extension.js",
|
||||
"contributes": {
|
||||
"commands": [
|
||||
@@ -62,6 +68,14 @@
|
||||
"configuration": {
|
||||
"title": "Debug Visualizer",
|
||||
"properties": {
|
||||
"debugVisualizer.js.customScriptPaths": {
|
||||
"type": "array",
|
||||
"description": "Defines JavaScript sources that are injected into the debuggee.",
|
||||
"items": {
|
||||
"type": "string",
|
||||
"description": "The file path to a custom js script. Must be absolute. You can use ${workspaceFolder}."
|
||||
}
|
||||
},
|
||||
"debugVisualizer.useChromeKioskMode": {
|
||||
"type": "boolean",
|
||||
"default": true,
|
||||
@@ -107,7 +121,8 @@
|
||||
"open": "^7.0.2",
|
||||
"serve-static": "^1.14.1",
|
||||
"ws": "^7.2.1",
|
||||
"crypto-random-string": "^3.1.0"
|
||||
"crypto-random-string": "^3.1.0",
|
||||
"mobx-utils": "^5.6.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"copy-webpack-plugin": "^5.1.1",
|
||||
@@ -115,12 +130,13 @@
|
||||
"@types/express": "^4.17.2",
|
||||
"@types/serve-static": "^1.13.3",
|
||||
"@types/node": "^13.7.4",
|
||||
"@types/vscode": "1.35.0",
|
||||
"@types/vscode": "1.46.0",
|
||||
"tslint": "^6.0.0",
|
||||
"typescript": "^3.8.2",
|
||||
"webpack": "^4.41.6",
|
||||
"webpack-cli": "^3.3.11",
|
||||
"ts-loader": "^6.2.1",
|
||||
"shx": "^0.3.2"
|
||||
"shx": "^0.3.2",
|
||||
"vsce": "^1.93.0"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,55 +1,73 @@
|
||||
import { workspace } from "vscode";
|
||||
import { workspace, window, ColorThemeKind } from "vscode";
|
||||
import { Disposable } from "@hediet/std/disposable";
|
||||
import { observable } from "mobx";
|
||||
import { DataExtractorId } from "@hediet/debug-visualizer-data-extraction";
|
||||
|
||||
const useChromeKioskModeKey = "debugVisualizer.useChromeKioskMode";
|
||||
const debugAdapterConfigsKey = "debugVisualizer.debugAdapterConfigurations";
|
||||
import { VsCodeSetting, serializerWithDefault } from "./utils/VsCodeSettings";
|
||||
|
||||
export class Config {
|
||||
public dispose = Disposable.fn();
|
||||
|
||||
@observable
|
||||
private _useChromeKioskMode!: boolean;
|
||||
|
||||
@observable
|
||||
private _debugAdapterConfigs!: DebugAdapterConfigs;
|
||||
private readonly _useChromeKioskMode = new VsCodeSetting(
|
||||
"debugVisualizer.useChromeKioskMode",
|
||||
{ serializer: serializerWithDefault<boolean>(true) }
|
||||
);
|
||||
|
||||
public get useChromeKioskMode(): boolean {
|
||||
return this._useChromeKioskMode;
|
||||
return this._useChromeKioskMode.get();
|
||||
}
|
||||
|
||||
public get debugAdapterConfigs(): DebugAdapterConfigs {
|
||||
return this._debugAdapterConfigs;
|
||||
private readonly _debugAdapterConfigs = new VsCodeSetting(
|
||||
"debugVisualizer.debugAdapterConfigurations",
|
||||
{ serializer: serializerWithDefault<DebugAdapterConfigs>({}) }
|
||||
);
|
||||
|
||||
private readonly _customScriptPaths = new VsCodeSetting(
|
||||
"debugVisualizer.js.customScriptPaths",
|
||||
{ serializer: serializerWithDefault<string[]>([]) }
|
||||
);
|
||||
|
||||
public get customScriptPaths(): string[] {
|
||||
return this._customScriptPaths.get().map(p => {
|
||||
const tpl = new SimpleTemplate(p);
|
||||
return tpl.render({
|
||||
workspaceFolder: () => {
|
||||
const workspaceFolder = (workspace.workspaceFolders ||
|
||||
[])[0];
|
||||
if (!workspaceFolder) {
|
||||
throw new Error(
|
||||
`Cannot get workspace folder - '${p}' cannot be evaluated!`
|
||||
);
|
||||
}
|
||||
return workspaceFolder.uri.fsPath;
|
||||
},
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
@observable
|
||||
private _vsCodeTheme = window.activeColorTheme;
|
||||
|
||||
public get theme(): "light" | "dark" {
|
||||
if (this._vsCodeTheme.kind === ColorThemeKind.Light) {
|
||||
return "light";
|
||||
} else if (this._vsCodeTheme.kind === ColorThemeKind.Dark) {
|
||||
return "dark";
|
||||
}
|
||||
return "light";
|
||||
}
|
||||
|
||||
constructor() {
|
||||
this.updateConfig();
|
||||
this.dispose.track(
|
||||
workspace.onDidChangeConfiguration(() => {
|
||||
this.updateConfig();
|
||||
window.onDidChangeActiveColorTheme(() => {
|
||||
this._vsCodeTheme = window.activeColorTheme;
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
private updateConfig(): void {
|
||||
const c = workspace.getConfiguration();
|
||||
|
||||
this._useChromeKioskMode = mapUndefined(
|
||||
c.get<boolean>(useChromeKioskModeKey),
|
||||
true
|
||||
);
|
||||
|
||||
this._debugAdapterConfigs = mapUndefined(
|
||||
c.get<DebugAdapterConfigs>(debugAdapterConfigsKey),
|
||||
{}
|
||||
);
|
||||
}
|
||||
|
||||
public getDebugAdapterConfig(
|
||||
debugAdapterType: string
|
||||
): DebugAdapterConfig | undefined {
|
||||
const c = this.debugAdapterConfigs[debugAdapterType];
|
||||
const c = this._debugAdapterConfigs.get()[debugAdapterType];
|
||||
if (!c) {
|
||||
return undefined;
|
||||
}
|
||||
@@ -65,13 +83,6 @@ export class Config {
|
||||
}
|
||||
}
|
||||
|
||||
function mapUndefined<T>(val: T | undefined, defaultVal: T) {
|
||||
if (val === undefined) {
|
||||
return defaultVal;
|
||||
}
|
||||
return val;
|
||||
}
|
||||
|
||||
type DebugAdapterConfigs = {
|
||||
[debugAdapter: string]: {
|
||||
context?: "watch" | "repl";
|
||||
@@ -97,3 +108,13 @@ function evaluateTemplate(
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
export class SimpleTemplate {
|
||||
constructor(private readonly str: string) {}
|
||||
|
||||
render(data: Record<string, () => string>): string {
|
||||
return this.str.replace(/\$\{([a-zA-Z0-9]+)\}/g, (substr, grp1) => {
|
||||
return data[grp1]();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,16 +0,0 @@
|
||||
import { EvaluationEngine, Evaluator } from "./EvaluationEngine";
|
||||
import { VsCodeDebugSession } from "../../VsCodeDebugger";
|
||||
|
||||
export class ComposedEvaluationEngine implements EvaluationEngine {
|
||||
constructor(public readonly engines: EvaluationEngine[]) {}
|
||||
|
||||
createEvaluator(session: VsCodeDebugSession): Evaluator | undefined {
|
||||
for (const f of this.engines) {
|
||||
const evaluator = f.createEvaluator(session);
|
||||
if (evaluator) {
|
||||
return evaluator;
|
||||
}
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
@@ -1,47 +0,0 @@
|
||||
import { DataExtractorId } from "@hediet/debug-visualizer-data-extraction";
|
||||
import { VsCodeDebugSession } from "../../VsCodeDebugger";
|
||||
import { EvaluationEngine, Evaluator } from "./EvaluationEngine";
|
||||
import { Config, DebugAdapterConfig } from "../../Config";
|
||||
import { GenericEvaluator } from "./GenericEvaluationEngine";
|
||||
import { registerUpdateReconciler, hotClass } from "@hediet/node-reload";
|
||||
|
||||
registerUpdateReconciler(module);
|
||||
|
||||
@hotClass(module)
|
||||
export class ConfiguredEvaluationEngine implements EvaluationEngine {
|
||||
constructor(private readonly config: Config) {}
|
||||
|
||||
createEvaluator(session: VsCodeDebugSession): Evaluator | undefined {
|
||||
const config = this.config.getDebugAdapterConfig(session.session.type);
|
||||
if (!config) {
|
||||
return undefined;
|
||||
}
|
||||
return new ConfiguredEvaluator(session, config);
|
||||
}
|
||||
}
|
||||
|
||||
class ConfiguredEvaluator extends GenericEvaluator {
|
||||
constructor(
|
||||
session: VsCodeDebugSession,
|
||||
private readonly config: DebugAdapterConfig
|
||||
) {
|
||||
super(session);
|
||||
}
|
||||
|
||||
protected getContext() {
|
||||
return this.config.context;
|
||||
}
|
||||
|
||||
protected getFinalExpression({
|
||||
expression,
|
||||
preferredExtractorId,
|
||||
}: {
|
||||
expression: string;
|
||||
preferredExtractorId: DataExtractorId | undefined;
|
||||
}) {
|
||||
return this.config.getFinalExpression({
|
||||
expression,
|
||||
preferredExtractorId,
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -1,30 +0,0 @@
|
||||
import {
|
||||
DataExtractionResult,
|
||||
DataExtractorId,
|
||||
} from "@hediet/debug-visualizer-data-extraction";
|
||||
import { VsCodeDebugSession } from "../../VsCodeDebugger";
|
||||
import { FormattedMessage } from "../../contract";
|
||||
|
||||
export interface EvaluationEngine {
|
||||
createEvaluator(session: VsCodeDebugSession): Evaluator | undefined;
|
||||
}
|
||||
|
||||
export interface Evaluator {
|
||||
evaluate(
|
||||
args: EvaluationArgs
|
||||
): Promise<
|
||||
| { kind: "data"; result: DataExtractionResult }
|
||||
| { kind: "error"; message: FormattedMessage }
|
||||
>;
|
||||
|
||||
/**
|
||||
* The language that expressions must be written in.
|
||||
*/
|
||||
readonly languageId: string | undefined;
|
||||
}
|
||||
|
||||
export interface EvaluationArgs {
|
||||
expression: string;
|
||||
preferredExtractorId: DataExtractorId | undefined;
|
||||
frameId: number | undefined;
|
||||
}
|
||||
@@ -1,85 +0,0 @@
|
||||
import {
|
||||
DataExtractionResult,
|
||||
DataExtractorId,
|
||||
} from "@hediet/debug-visualizer-data-extraction";
|
||||
import { VsCodeDebugSession } from "../../VsCodeDebugger";
|
||||
import {
|
||||
EvaluationEngine,
|
||||
Evaluator,
|
||||
EvaluationArgs,
|
||||
} from "./EvaluationEngine";
|
||||
import { parseEvaluationResultFromGenericDebugAdapter } from "./parseEvaluationResultFromGenericDebugAdapter";
|
||||
import { FormattedMessage } from "../../contract";
|
||||
import { hotClass, registerUpdateReconciler } from "@hediet/node-reload";
|
||||
|
||||
registerUpdateReconciler(module);
|
||||
|
||||
@hotClass(module)
|
||||
export class GenericEvaluationEngine implements EvaluationEngine {
|
||||
createEvaluator(session: VsCodeDebugSession): Evaluator | undefined {
|
||||
return new GenericEvaluator(session);
|
||||
}
|
||||
}
|
||||
|
||||
export class GenericEvaluator implements Evaluator {
|
||||
public readonly languageId = "text";
|
||||
|
||||
constructor(private readonly session: VsCodeDebugSession) {}
|
||||
|
||||
public async evaluate({
|
||||
expression,
|
||||
preferredExtractorId,
|
||||
frameId,
|
||||
}: EvaluationArgs): Promise<
|
||||
| { kind: "data"; result: DataExtractionResult }
|
||||
| { kind: "error"; message: FormattedMessage }
|
||||
> {
|
||||
const finalExpression = this.getFinalExpression({
|
||||
expression,
|
||||
preferredExtractorId,
|
||||
});
|
||||
let reply;
|
||||
try {
|
||||
reply = await this.session.evaluate({
|
||||
expression: finalExpression,
|
||||
frameId,
|
||||
context: this.getContext(),
|
||||
});
|
||||
} catch (error) {
|
||||
return {
|
||||
kind: "error",
|
||||
message: {
|
||||
kind: "list",
|
||||
items: [
|
||||
"An error occurred while evaluating the expression:",
|
||||
error.message,
|
||||
`Used debug adapter: ${this.session.session.configuration.type}`,
|
||||
{
|
||||
kind: "inlineList",
|
||||
items: [
|
||||
"Evaluated expression is",
|
||||
{ kind: "code", content: finalExpression },
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
return parseEvaluationResultFromGenericDebugAdapter(reply.result, {
|
||||
debugAdapterType: this.session.session.configuration.type,
|
||||
});
|
||||
}
|
||||
|
||||
protected getFinalExpression(args: {
|
||||
expression: string;
|
||||
preferredExtractorId: DataExtractorId | undefined;
|
||||
}): string {
|
||||
return args.expression;
|
||||
}
|
||||
|
||||
protected getContext(): "watch" | "repl" {
|
||||
// we will use "repl" as default so that results are not truncated.
|
||||
return "repl";
|
||||
}
|
||||
}
|
||||
@@ -1,128 +0,0 @@
|
||||
import {
|
||||
getExpressionForDataExtractorApi,
|
||||
DataResult,
|
||||
ApiHasNotBeenInitializedCode,
|
||||
getExpressionToInitializeDataExtractorApi,
|
||||
DataExtractionResult,
|
||||
} from "@hediet/debug-visualizer-data-extraction";
|
||||
import { VsCodeDebugSession } from "../../VsCodeDebugger";
|
||||
import {
|
||||
EvaluationEngine,
|
||||
Evaluator,
|
||||
EvaluationArgs,
|
||||
} from "./EvaluationEngine";
|
||||
import { FormattedMessage } from "../../contract";
|
||||
import { registerUpdateReconciler, hotClass } from "@hediet/node-reload";
|
||||
|
||||
registerUpdateReconciler(module);
|
||||
|
||||
@hotClass(module)
|
||||
export class JsEvaluationEngine implements EvaluationEngine {
|
||||
createEvaluator(session: VsCodeDebugSession): Evaluator | undefined {
|
||||
const supportedDebugAdapters = [
|
||||
"node",
|
||||
"node2",
|
||||
"extensionHost",
|
||||
"chrome",
|
||||
"pwa-chrome",
|
||||
];
|
||||
if (supportedDebugAdapters.indexOf(session.session.type) !== -1) {
|
||||
return new JsEvaluator(session);
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
|
||||
class JsEvaluator implements Evaluator {
|
||||
public readonly languageId = "javascript";
|
||||
|
||||
constructor(private readonly session: VsCodeDebugSession) {}
|
||||
|
||||
public async evaluate({
|
||||
expression,
|
||||
preferredExtractorId,
|
||||
frameId,
|
||||
}: EvaluationArgs): Promise<
|
||||
| { kind: "data"; result: DataExtractionResult }
|
||||
| { kind: "error"; message: FormattedMessage }
|
||||
> {
|
||||
while (true) {
|
||||
try {
|
||||
const preferredExtractorExpr = preferredExtractorId
|
||||
? `"${preferredExtractorId}"`
|
||||
: "undefined";
|
||||
|
||||
const body = `${getExpressionForDataExtractorApi()}.getData(
|
||||
e => (${expression}),
|
||||
expr => eval(expr),
|
||||
${preferredExtractorExpr}
|
||||
)`;
|
||||
|
||||
const wrappedExpr = `
|
||||
(() => {
|
||||
try {
|
||||
return ${body};
|
||||
} catch (e) {
|
||||
return JSON.stringify({
|
||||
kind: "Error",
|
||||
message: e.message,
|
||||
stack: e.stack
|
||||
});
|
||||
}
|
||||
})()
|
||||
`;
|
||||
|
||||
const reply = await this.session.evaluate({
|
||||
expression: wrappedExpr,
|
||||
frameId,
|
||||
context: "repl",
|
||||
});
|
||||
const resultStr = reply.result;
|
||||
const jsonData = resultStr.substr(1, resultStr.length - 2);
|
||||
const result = JSON.parse(jsonData) as DataResult;
|
||||
|
||||
if (result.kind === "NoExtractors") {
|
||||
throw new Error("No extractors");
|
||||
} else if (result.kind === "Error") {
|
||||
throw new Error(result.message);
|
||||
} else if (result.kind === "Data") {
|
||||
return {
|
||||
kind: "data",
|
||||
result: result.extractionResult,
|
||||
};
|
||||
} else {
|
||||
throw new Error("Invalid Data");
|
||||
}
|
||||
} catch (error) {
|
||||
const msg = error.message as string | undefined;
|
||||
if (msg && msg.includes(ApiHasNotBeenInitializedCode)) {
|
||||
if (await this.initializeApi(frameId)) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
kind: "error",
|
||||
message: error.message,
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private async initializeApi(frameId: number | undefined): Promise<boolean> {
|
||||
try {
|
||||
// prefer existing is true, so that manually registered (possibly newer) extractors are not overwritten.
|
||||
const expression = `${getExpressionToInitializeDataExtractorApi()}.registerDefaultExtractors(true);`;
|
||||
|
||||
await this.session.evaluate({
|
||||
expression,
|
||||
frameId,
|
||||
context: "repl",
|
||||
});
|
||||
|
||||
return true;
|
||||
} catch (error) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,6 +0,0 @@
|
||||
export * from "./EvaluationEngine";
|
||||
|
||||
export { ComposedEvaluationEngine } from "./ComposedEvaluationEngine";
|
||||
export { JsEvaluationEngine } from "./JsEvaluationEngine";
|
||||
export { GenericEvaluationEngine } from "./GenericEvaluationEngine";
|
||||
export { ConfiguredEvaluationEngine } from "./ConfiguredEvaluationEngine";
|
||||
@@ -1,146 +0,0 @@
|
||||
import {
|
||||
EvaluationWatchService,
|
||||
EvaluationWatcher,
|
||||
EvaluationWatcherOptions,
|
||||
} from "./EvaluationWatchService";
|
||||
import { observable, autorun, action } from "mobx";
|
||||
import { Disposable } from "@hediet/std/disposable";
|
||||
import { DataExtractorId } from "@hediet/debug-visualizer-data-extraction";
|
||||
import { DataExtractionState, CompletionItem } from "../contract";
|
||||
import { hotClass } from "@hediet/node-reload";
|
||||
import { VsCodeDebuggerView } from "../VsCodeDebugger";
|
||||
import { EvaluationEngine } from "./EvaluationEngine/EvaluationEngine";
|
||||
|
||||
@hotClass(module)
|
||||
export class EvaluationWatchServiceImpl implements EvaluationWatchService {
|
||||
public readonly dispose = Disposable.fn();
|
||||
private readonly watchers = new Set<ObservableEvaluationWatcher>();
|
||||
|
||||
constructor(
|
||||
private readonly vsCodeDebuggerView: VsCodeDebuggerView,
|
||||
private readonly evaluationEngine: EvaluationEngine
|
||||
) {
|
||||
this.dispose.track({
|
||||
dispose: autorun(() => {
|
||||
if (
|
||||
vsCodeDebuggerView.activeDebugSession &&
|
||||
vsCodeDebuggerView.activeFrameId !== undefined
|
||||
) {
|
||||
for (const w of this.watchers) {
|
||||
w.refresh();
|
||||
}
|
||||
}
|
||||
}),
|
||||
});
|
||||
}
|
||||
|
||||
public createEvaluationWatcher(
|
||||
expression: string,
|
||||
options: EvaluationWatcherOptions
|
||||
): EvaluationWatcher {
|
||||
const w = new ObservableEvaluationWatcher(expression, this, options);
|
||||
this.watchers.add(w);
|
||||
this.refresh(w);
|
||||
return w;
|
||||
}
|
||||
|
||||
public removeWatcher(w: ObservableEvaluationWatcher) {
|
||||
this.watchers.delete(w);
|
||||
}
|
||||
|
||||
private lastLanguageId: string | undefined = undefined;
|
||||
|
||||
get languageId(): string | undefined {
|
||||
const session = this.vsCodeDebuggerView.activeDebugSession;
|
||||
if (!session) {
|
||||
return this.lastLanguageId;
|
||||
}
|
||||
const evaluator = this.evaluationEngine.createEvaluator(session);
|
||||
if (!evaluator) {
|
||||
return this.lastLanguageId;
|
||||
}
|
||||
this.lastLanguageId = evaluator.languageId;
|
||||
return evaluator.languageId;
|
||||
}
|
||||
|
||||
public async refresh(w: ObservableEvaluationWatcher): Promise<void> {
|
||||
const session = this.vsCodeDebuggerView.activeDebugSession;
|
||||
if (!session) {
|
||||
w._state = { kind: "noDebugSession" };
|
||||
return;
|
||||
}
|
||||
|
||||
const frameId = this.vsCodeDebuggerView.activeFrameId;
|
||||
|
||||
w._state = { kind: "loading" };
|
||||
|
||||
const evaluator = this.evaluationEngine.createEvaluator(session);
|
||||
if (!evaluator) {
|
||||
w._state = {
|
||||
kind: "error",
|
||||
message: `The debug adapter "${session.session.type}" is not supported.`,
|
||||
};
|
||||
return;
|
||||
}
|
||||
|
||||
const result = await evaluator.evaluate({
|
||||
expression: w.expression,
|
||||
frameId,
|
||||
preferredExtractorId: w.preferredDataExtractor,
|
||||
});
|
||||
|
||||
w._state = result;
|
||||
}
|
||||
|
||||
public async getCompletions(
|
||||
text: string,
|
||||
column: number
|
||||
): Promise<CompletionItem[]> {
|
||||
const session = this.vsCodeDebuggerView.activeDebugSession;
|
||||
if (!session) {
|
||||
return [];
|
||||
}
|
||||
return await session.getCompletions({
|
||||
text,
|
||||
frameId: this.vsCodeDebuggerView.activeFrameId,
|
||||
column,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
class ObservableEvaluationWatcher implements EvaluationWatcher {
|
||||
constructor(
|
||||
public readonly expression: string,
|
||||
private readonly source: EvaluationWatchServiceImpl,
|
||||
options: EvaluationWatcherOptions
|
||||
) {
|
||||
this._preferredDataExtractor = options.preferredDataExtractor;
|
||||
}
|
||||
|
||||
@observable
|
||||
private _preferredDataExtractor: DataExtractorId | undefined = undefined;
|
||||
|
||||
public get preferredDataExtractor(): DataExtractorId | undefined {
|
||||
return this._preferredDataExtractor;
|
||||
}
|
||||
|
||||
@action
|
||||
public setPreferredDataExtractor(id: DataExtractorId | undefined): void {
|
||||
this._preferredDataExtractor = id;
|
||||
this.refresh();
|
||||
}
|
||||
|
||||
public refresh(): void {
|
||||
this.source.refresh(this);
|
||||
}
|
||||
|
||||
@observable
|
||||
public _state: DataExtractionState = { kind: "loading" };
|
||||
public get state(): DataExtractionState {
|
||||
return this._state;
|
||||
}
|
||||
|
||||
public dispose(): void {
|
||||
this.source.removeWatcher(this);
|
||||
}
|
||||
}
|
||||
@@ -1,2 +0,0 @@
|
||||
export * from "./EvaluationWatchServiceImpl";
|
||||
export * from "./EvaluationWatchService";
|
||||
@@ -0,0 +1,22 @@
|
||||
import {
|
||||
DebugSessionVisualizationSupport,
|
||||
VisualizationBackend,
|
||||
} from "./VisualizationBackend";
|
||||
import { DebugSessionProxy } from "../proxies/DebugSessionProxy";
|
||||
|
||||
export class ComposedVisualizationSupport
|
||||
implements DebugSessionVisualizationSupport {
|
||||
constructor(public readonly engines: DebugSessionVisualizationSupport[]) {}
|
||||
|
||||
createBackend(
|
||||
session: DebugSessionProxy
|
||||
): VisualizationBackend | undefined {
|
||||
for (const f of this.engines) {
|
||||
const evaluator = f.createBackend(session);
|
||||
if (evaluator) {
|
||||
return evaluator;
|
||||
}
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
import { DataExtractorId } from "@hediet/debug-visualizer-data-extraction";
|
||||
import { DebugSessionProxy } from "../proxies/DebugSessionProxy";
|
||||
import {
|
||||
DebugSessionVisualizationSupport,
|
||||
VisualizationBackend,
|
||||
} from "./VisualizationBackend";
|
||||
import { Config, DebugAdapterConfig } from "../Config";
|
||||
import { GenericVisualizationBackend } from "./GenericVisualizationSupport";
|
||||
import { registerUpdateReconciler, hotClass } from "@hediet/node-reload";
|
||||
import { DebuggerViewProxy } from "../proxies/DebuggerViewProxy";
|
||||
|
||||
registerUpdateReconciler(module);
|
||||
|
||||
@hotClass(module)
|
||||
export class ConfigurableVisualizationSupport
|
||||
implements DebugSessionVisualizationSupport {
|
||||
constructor(
|
||||
private readonly config: Config,
|
||||
private readonly debuggerView: DebuggerViewProxy
|
||||
) {}
|
||||
|
||||
createBackend(
|
||||
session: DebugSessionProxy
|
||||
): VisualizationBackend | undefined {
|
||||
const config = this.config.getDebugAdapterConfig(session.session.type);
|
||||
if (!config) {
|
||||
return undefined;
|
||||
}
|
||||
return new ConfiguredVisualizationBackend(
|
||||
session,
|
||||
this.debuggerView,
|
||||
config
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class ConfiguredVisualizationBackend extends GenericVisualizationBackend {
|
||||
constructor(
|
||||
debugSession: DebugSessionProxy,
|
||||
debuggerView: DebuggerViewProxy,
|
||||
private readonly config: DebugAdapterConfig
|
||||
) {
|
||||
super(debugSession, debuggerView);
|
||||
}
|
||||
|
||||
protected getContext() {
|
||||
return this.config.context;
|
||||
}
|
||||
|
||||
protected getFinalExpression({
|
||||
expression,
|
||||
preferredExtractorId,
|
||||
}: {
|
||||
expression: string;
|
||||
preferredExtractorId: DataExtractorId | undefined;
|
||||
}) {
|
||||
return this.config.getFinalExpression({
|
||||
expression,
|
||||
preferredExtractorId,
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,134 @@
|
||||
import { DataExtractionResult } from "@hediet/debug-visualizer-data-extraction";
|
||||
import { CompletionItem, FormattedMessage } from "../webviewContract";
|
||||
import {
|
||||
DebugSessionVisualizationSupport,
|
||||
GetVisualizationDataArgs,
|
||||
VisualizationBackend,
|
||||
} from "./VisualizationBackend";
|
||||
import { EventEmitter, EventSource } from "@hediet/std/events";
|
||||
import { debug } from "vscode";
|
||||
import { DebugSessionProxy } from "../proxies/DebugSessionProxy";
|
||||
import { Disposable } from "@hediet/std/disposable";
|
||||
import { DebuggerViewProxy } from "../proxies/DebuggerViewProxy";
|
||||
import { autorun, reaction } from "mobx";
|
||||
|
||||
export class DispatchingVisualizationBackend implements VisualizationBackend {
|
||||
public readonly dispose = Disposable.fn();
|
||||
private readonly visualizationBackends = new Map<
|
||||
DebugSessionProxy,
|
||||
VisualizationBackend
|
||||
>();
|
||||
|
||||
protected readonly onChangeEmitter = new EventEmitter();
|
||||
public readonly onChange = this.onChangeEmitter;
|
||||
|
||||
constructor(
|
||||
private readonly visualizationSupport: DebugSessionVisualizationSupport,
|
||||
private readonly debuggerView: DebuggerViewProxy
|
||||
) {
|
||||
this.dispose.track(
|
||||
debug.onDidTerminateDebugSession(session => {
|
||||
const existing = [...this.visualizationBackends].find(
|
||||
v => v[0].session === session
|
||||
);
|
||||
if (existing) {
|
||||
this.visualizationBackends.delete(existing[0]);
|
||||
existing[1].dispose();
|
||||
}
|
||||
})
|
||||
);
|
||||
|
||||
this.dispose.track({
|
||||
dispose: () => {
|
||||
for (const backend of this.visualizationBackends.values()) {
|
||||
backend.dispose();
|
||||
}
|
||||
this.visualizationBackends.clear();
|
||||
},
|
||||
});
|
||||
|
||||
this.dispose.track({
|
||||
dispose: reaction(
|
||||
() => this.debuggerView.activeDebugSession,
|
||||
debugSession => {
|
||||
if (debugSession !== undefined) {
|
||||
this.onChangeEmitter.emit();
|
||||
}
|
||||
}
|
||||
),
|
||||
});
|
||||
}
|
||||
|
||||
private getVisualizationBackend(
|
||||
debugSession: DebugSessionProxy
|
||||
): VisualizationBackend | undefined {
|
||||
const existing = this.visualizationBackends.get(debugSession);
|
||||
if (existing) {
|
||||
return existing;
|
||||
}
|
||||
|
||||
const newBackend = this.visualizationSupport.createBackend(
|
||||
debugSession
|
||||
);
|
||||
|
||||
if (!newBackend) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
newBackend.onChange.sub(() => {
|
||||
this.onChangeEmitter.emit();
|
||||
});
|
||||
|
||||
this.visualizationBackends.set(debugSession, newBackend);
|
||||
|
||||
return newBackend;
|
||||
}
|
||||
|
||||
private get activeVisualizationBackend(): VisualizationBackend | undefined {
|
||||
const activeDebugSession = this.debuggerView.activeDebugSession;
|
||||
if (!activeDebugSession) {
|
||||
return undefined;
|
||||
}
|
||||
const backend = this.getVisualizationBackend(activeDebugSession);
|
||||
return backend;
|
||||
}
|
||||
|
||||
public async getVisualizationData(
|
||||
args: GetVisualizationDataArgs
|
||||
): Promise<
|
||||
| { kind: "data"; result: DataExtractionResult }
|
||||
| { kind: "error"; message: FormattedMessage }
|
||||
> {
|
||||
const activeDebugSession = this.debuggerView.activeDebugSession;
|
||||
if (!activeDebugSession) {
|
||||
return {
|
||||
kind: "error",
|
||||
message: "No active debug session.",
|
||||
};
|
||||
}
|
||||
const backend = this.getVisualizationBackend(activeDebugSession);
|
||||
if (!backend) {
|
||||
return {
|
||||
kind: "error",
|
||||
message: `The debug adapter "${activeDebugSession.session.type}" is not supported.`,
|
||||
};
|
||||
}
|
||||
return await backend.getVisualizationData(args);
|
||||
}
|
||||
|
||||
public get expressionLanguageId(): string | undefined {
|
||||
return this.activeVisualizationBackend?.expressionLanguageId;
|
||||
}
|
||||
|
||||
public async getCompletions(
|
||||
text: string,
|
||||
column: number
|
||||
): Promise<CompletionItem[]> {
|
||||
return (
|
||||
(await this.activeVisualizationBackend?.getCompletions(
|
||||
text,
|
||||
column
|
||||
)) || []
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,229 @@
|
||||
import {
|
||||
DataExtractionResult,
|
||||
DataExtractorId,
|
||||
GraphNode,
|
||||
GraphVisualizationData,
|
||||
} from "@hediet/debug-visualizer-data-extraction";
|
||||
import { DebugSessionProxy } from "../proxies/DebugSessionProxy";
|
||||
import {
|
||||
DebugSessionVisualizationSupport,
|
||||
VisualizationBackend,
|
||||
GetVisualizationDataArgs,
|
||||
VisualizationBackendBase,
|
||||
} from "./VisualizationBackend";
|
||||
import { parseEvaluationResultFromGenericDebugAdapter } from "./parseEvaluationResultFromGenericDebugAdapter";
|
||||
import { FormattedMessage } from "../webviewContract";
|
||||
import { hotClass, registerUpdateReconciler } from "@hediet/node-reload";
|
||||
import { DebuggerViewProxy } from "../proxies/DebuggerViewProxy";
|
||||
|
||||
registerUpdateReconciler(module);
|
||||
|
||||
@hotClass(module)
|
||||
export class GenericVisualizationSupport
|
||||
implements DebugSessionVisualizationSupport {
|
||||
constructor(private readonly debuggerView: DebuggerViewProxy) {}
|
||||
|
||||
createBackend(
|
||||
session: DebugSessionProxy
|
||||
): VisualizationBackend | undefined {
|
||||
return new GenericVisualizationBackend(session, this.debuggerView);
|
||||
}
|
||||
}
|
||||
|
||||
export class GenericVisualizationBackend extends VisualizationBackendBase {
|
||||
public readonly expressionLanguageId = "text";
|
||||
|
||||
constructor(
|
||||
debugSession: DebugSessionProxy,
|
||||
debuggerView: DebuggerViewProxy
|
||||
) {
|
||||
super(debugSession, debuggerView);
|
||||
}
|
||||
|
||||
public async getVisualizationData({
|
||||
expression,
|
||||
preferredExtractorId,
|
||||
}: GetVisualizationDataArgs): Promise<
|
||||
| { kind: "data"; result: DataExtractionResult }
|
||||
| { kind: "error"; message: FormattedMessage }
|
||||
> {
|
||||
const frameId = this.debuggerView.getActiveStackFrameId(
|
||||
this.debugSession
|
||||
);
|
||||
|
||||
const finalExpression = this.getFinalExpression({
|
||||
expression,
|
||||
preferredExtractorId,
|
||||
});
|
||||
let reply: { result: string; variablesReference: number };
|
||||
try {
|
||||
reply = await this.debugSession.evaluate({
|
||||
expression: finalExpression,
|
||||
frameId,
|
||||
context: this.getContext(),
|
||||
});
|
||||
|
||||
// Use structural information about variables
|
||||
// from the evaluation response if present.
|
||||
if (reply.variablesReference) {
|
||||
const graph = await this.constructGraphFromVariablesReference(
|
||||
reply.result,
|
||||
reply.variablesReference
|
||||
);
|
||||
|
||||
return {
|
||||
kind: "data",
|
||||
result: {
|
||||
availableExtractors: [],
|
||||
usedExtractor: {
|
||||
id: "generic" as any,
|
||||
name: "Generic",
|
||||
priority: 1,
|
||||
},
|
||||
data: graph,
|
||||
},
|
||||
};
|
||||
} else {
|
||||
return parseEvaluationResultFromGenericDebugAdapter(
|
||||
reply.result,
|
||||
{
|
||||
debugAdapterType: this.debugSession.session
|
||||
.configuration.type,
|
||||
}
|
||||
);
|
||||
}
|
||||
} catch (error) {
|
||||
return {
|
||||
kind: "error",
|
||||
message: {
|
||||
kind: "list",
|
||||
items: [
|
||||
"An error occurred while evaluating the expression:",
|
||||
error.message,
|
||||
`Used debug adapter: ${this.debugSession.session.configuration.type}`,
|
||||
{
|
||||
kind: "inlineList",
|
||||
items: [
|
||||
"Evaluated expression is",
|
||||
{ kind: "code", content: finalExpression },
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs GraphVisualizationData from a DAP variables
|
||||
* reference by successively querying the debug adapter for
|
||||
* variables. Objects are considered to be equivalent if
|
||||
* they share the same variables reference (this is important
|
||||
* for representing cyclic relationships).
|
||||
*
|
||||
* @param rootLabel - The root object's label
|
||||
* @param rootVariablesReference - The root object's DAP variables reference
|
||||
* @param maxDepth - The maximum depth to search at
|
||||
* @param maxKnownNodes - The maximum number of nodes
|
||||
*/
|
||||
private async constructGraphFromVariablesReference(
|
||||
rootLabel: string,
|
||||
rootVariablesReference: number,
|
||||
maxDepth: number = 30,
|
||||
maxKnownNodes: number = 50
|
||||
): Promise<GraphVisualizationData> {
|
||||
// Perform a breadth-first search on the object to construct the graph
|
||||
|
||||
const graph: GraphVisualizationData = {
|
||||
kind: { graph: true },
|
||||
nodes: [],
|
||||
edges: [],
|
||||
};
|
||||
const knownNodeIds: { [ref: number]: string } = {};
|
||||
const bfsQueue: {
|
||||
source: { id: string; name: string } | undefined;
|
||||
label: string;
|
||||
variablesReference: number;
|
||||
depth: number;
|
||||
}[] = [
|
||||
{
|
||||
source: undefined,
|
||||
label: rootLabel,
|
||||
variablesReference: rootVariablesReference,
|
||||
depth: 0,
|
||||
},
|
||||
];
|
||||
|
||||
let knownCount: number = 0;
|
||||
|
||||
do {
|
||||
const variable = bfsQueue.shift()!;
|
||||
const hasChilds = variable.variablesReference > 0;
|
||||
|
||||
if (variable.depth > maxDepth) {
|
||||
break;
|
||||
}
|
||||
|
||||
let nodeId: string;
|
||||
|
||||
if (!hasChilds || !(variable.variablesReference in knownNodeIds)) {
|
||||
// The variable is a leaf or an unvisited object: create the node.
|
||||
|
||||
const node: GraphNode = {
|
||||
id: hasChilds
|
||||
? `${variable.variablesReference}`
|
||||
: `__${variable.label}@${knownCount}__`,
|
||||
label: variable.label,
|
||||
color: variable.depth == 0 ? "lightblue" : undefined,
|
||||
shape: "box",
|
||||
};
|
||||
|
||||
graph.nodes.push(node);
|
||||
knownCount++;
|
||||
|
||||
if (hasChilds) {
|
||||
knownNodeIds[variable.variablesReference] = node.id;
|
||||
|
||||
for (const child of await this.debugSession.getVariables({
|
||||
variablesReference: variable.variablesReference,
|
||||
})) {
|
||||
bfsQueue.push({
|
||||
source: { id: node.id, name: child.name },
|
||||
label: child.value,
|
||||
variablesReference: child.variablesReference,
|
||||
depth: variable.depth + 1,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
nodeId = node.id;
|
||||
} else {
|
||||
// The variable is a visited object (e.g. due to a cyclic reference)
|
||||
|
||||
nodeId = knownNodeIds[variable.variablesReference];
|
||||
}
|
||||
|
||||
if (variable.source) {
|
||||
graph.edges.push({
|
||||
from: variable.source.id,
|
||||
to: nodeId,
|
||||
label: variable.source.name,
|
||||
});
|
||||
}
|
||||
} while (bfsQueue.length > 0 && knownCount <= maxKnownNodes);
|
||||
|
||||
return graph;
|
||||
}
|
||||
|
||||
protected getFinalExpression(args: {
|
||||
expression: string;
|
||||
preferredExtractorId: DataExtractorId | undefined;
|
||||
}): string {
|
||||
return args.expression;
|
||||
}
|
||||
|
||||
protected getContext(): "watch" | "repl" {
|
||||
// we will use "repl" as default so that results are not truncated.
|
||||
return "repl";
|
||||
}
|
||||
}
|
||||
395
extension/src/VisualizationBackend/JsVisualizationSupport.ts
Normal file
@@ -0,0 +1,395 @@
|
||||
import {
|
||||
getExpressionForDataExtractorApi,
|
||||
DataResult,
|
||||
getExpressionToInitializeDataExtractorApi,
|
||||
DataExtractionResult,
|
||||
getExpressionToDetectDataExtractorApiPresence,
|
||||
} from "@hediet/debug-visualizer-data-extraction";
|
||||
import { DebugSessionProxy } from "../proxies/DebugSessionProxy";
|
||||
import {
|
||||
DebugSessionVisualizationSupport,
|
||||
VisualizationBackend,
|
||||
GetVisualizationDataArgs,
|
||||
VisualizationBackendBase,
|
||||
} from "./VisualizationBackend";
|
||||
import { FormattedMessage } from "../webviewContract";
|
||||
import { registerUpdateReconciler, hotClass } from "@hediet/node-reload";
|
||||
import { Config } from "../Config";
|
||||
import { DebuggerViewProxy } from "../proxies/DebuggerViewProxy";
|
||||
import { readFileSync, existsSync } from "fs";
|
||||
import { reaction, observable } from "mobx";
|
||||
import {
|
||||
workspace,
|
||||
FileSystemWatcher,
|
||||
CancellationTokenSource,
|
||||
CancellationToken,
|
||||
window,
|
||||
} from "vscode";
|
||||
import { ResettableTimeout } from "@hediet/std/timer";
|
||||
|
||||
registerUpdateReconciler(module);
|
||||
|
||||
@hotClass(module)
|
||||
export class JsEvaluationEngine implements DebugSessionVisualizationSupport {
|
||||
constructor(
|
||||
private readonly debuggerView: DebuggerViewProxy,
|
||||
private readonly config: Config
|
||||
) {}
|
||||
|
||||
createBackend(
|
||||
session: DebugSessionProxy
|
||||
): VisualizationBackend | undefined {
|
||||
const supportedDebugAdapters = [
|
||||
"node",
|
||||
"node2",
|
||||
"extensionHost",
|
||||
"chrome",
|
||||
"pwa-chrome",
|
||||
"pwa-node",
|
||||
"pwa-extensionHost",
|
||||
"node-terminal",
|
||||
"pwa-msedge",
|
||||
];
|
||||
|
||||
if (supportedDebugAdapters.indexOf(session.session.type) !== -1) {
|
||||
return new JsVisualizationBackend(
|
||||
session,
|
||||
this.debuggerView,
|
||||
this.config
|
||||
);
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
|
||||
class JsVisualizationBackend extends VisualizationBackendBase {
|
||||
public readonly expressionLanguageId = "javascript";
|
||||
private apiInitialized = false;
|
||||
private initializedPromise: Promise<void> | undefined = undefined;
|
||||
|
||||
constructor(
|
||||
debugSession: DebugSessionProxy,
|
||||
debuggerView: DebuggerViewProxy,
|
||||
private readonly config: Config
|
||||
) {
|
||||
super(debugSession, debuggerView);
|
||||
}
|
||||
|
||||
private getContext(): "copy" | "repl" {
|
||||
if (this.debugSession.session.type.startsWith("pwa-")) {
|
||||
return "copy";
|
||||
}
|
||||
return "repl";
|
||||
}
|
||||
|
||||
public async getVisualizationData({
|
||||
expression,
|
||||
preferredExtractorId,
|
||||
}: GetVisualizationDataArgs): Promise<
|
||||
| { kind: "data"; result: DataExtractionResult }
|
||||
| { kind: "error"; message: FormattedMessage }
|
||||
> {
|
||||
try {
|
||||
await this.ensureRuntimeIsInjected();
|
||||
|
||||
const frameId = this.debuggerView.getActiveStackFrameId(
|
||||
this.debugSession
|
||||
);
|
||||
|
||||
const variableNames = new Array<string>();
|
||||
if (frameId) {
|
||||
const scopes = await this.debugSession.getScopes({ frameId });
|
||||
const scopeVariables = await Promise.all(
|
||||
scopes
|
||||
.filter(s => !s.expensive)
|
||||
.map(s =>
|
||||
this.debugSession.getVariables({
|
||||
variablesReference: s.variablesReference,
|
||||
})
|
||||
)
|
||||
);
|
||||
for (const variables of scopeVariables) {
|
||||
variableNames.push(
|
||||
...variables
|
||||
.filter(v => v.value !== "undefined")
|
||||
.map(v => v.name)
|
||||
);
|
||||
}
|
||||
|
||||
if (variableNames.length > 100) {
|
||||
variableNames.length = 100;
|
||||
}
|
||||
}
|
||||
|
||||
const preferredExtractorExpr = preferredExtractorId
|
||||
? `"${preferredExtractorId}"`
|
||||
: "undefined";
|
||||
|
||||
const body = `${getExpressionForDataExtractorApi()}.getData(
|
||||
e => (${expression}),
|
||||
expr => eval(expr),
|
||||
${preferredExtractorExpr},
|
||||
{${variableNames.map(n => `${n}: ${n}`).join(",")}},
|
||||
)`;
|
||||
|
||||
const wrappedExpr = `
|
||||
(() => {
|
||||
try {
|
||||
return ${body};
|
||||
} catch (e) {
|
||||
return JSON.stringify({
|
||||
kind: "Error",
|
||||
message: e.message,
|
||||
stack: e.stack
|
||||
});
|
||||
}
|
||||
})()
|
||||
`;
|
||||
|
||||
const reply = await this.debugSession.evaluate({
|
||||
expression: wrappedExpr,
|
||||
frameId,
|
||||
context: this.getContext(),
|
||||
});
|
||||
const resultStr = reply.result;
|
||||
const jsonData =
|
||||
this.getContext() === "copy"
|
||||
? resultStr
|
||||
: resultStr.substr(1, resultStr.length - 2);
|
||||
const result = JSON.parse(jsonData) as DataResult;
|
||||
|
||||
if (result.kind === "NoExtractors") {
|
||||
throw new Error("No extractors");
|
||||
} else if (result.kind === "Error") {
|
||||
throw new Error(result.message);
|
||||
} else if (result.kind === "Data") {
|
||||
return {
|
||||
kind: "data",
|
||||
result: result.extractionResult,
|
||||
};
|
||||
} else {
|
||||
throw new Error("Invalid Data");
|
||||
}
|
||||
} catch (error) {
|
||||
return {
|
||||
kind: "error",
|
||||
message: error.message,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
private ensureRuntimeIsInjected(): Promise<void> | undefined {
|
||||
if (this.apiInitialized) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.initializedPromise) {
|
||||
return this.initializedPromise;
|
||||
} else {
|
||||
this.initializedPromise = this._ensureRuntimeIsInjected();
|
||||
}
|
||||
}
|
||||
|
||||
private async _ensureRuntimeIsInjected(): Promise<void> {
|
||||
const body = getExpressionToDetectDataExtractorApiPresence();
|
||||
|
||||
const reply = await this.debugSession.evaluate({
|
||||
expression: body,
|
||||
frameId: undefined,
|
||||
context: this.getContext(),
|
||||
});
|
||||
const isApiInitialized = reply.result === "true";
|
||||
|
||||
if (!isApiInitialized) {
|
||||
await this.initializeApi();
|
||||
}
|
||||
|
||||
await this.initializeCustomScripts();
|
||||
}
|
||||
|
||||
private async initializeApi(): Promise<boolean> {
|
||||
try {
|
||||
// prefer existing is true, so that manually registered (possibly newer) extractors are not overwritten.
|
||||
const expression = `${getExpressionToInitializeDataExtractorApi()}.registerDefaultExtractors(true);`;
|
||||
|
||||
await this.debugSession.evaluate({
|
||||
expression,
|
||||
frameId: undefined,
|
||||
context: this.getContext(),
|
||||
});
|
||||
|
||||
return true;
|
||||
} catch (error) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@observable
|
||||
private filesVersionId: number = 0;
|
||||
|
||||
private readonly fileChangeDebouncedScheduler = new DebouncedScheduler(100);
|
||||
|
||||
private async initializeCustomScripts(): Promise<void> {
|
||||
await this.debugSession.evaluate({
|
||||
expression: `global["${injectedScriptsKey}"] = {};`,
|
||||
frameId: undefined,
|
||||
context: "repl",
|
||||
});
|
||||
|
||||
const watchers = new Map<string, WatchedFile>();
|
||||
this.dispose.track({
|
||||
dispose: () => {
|
||||
for (const w of watchers.values()) {
|
||||
w.watcher.dispose();
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
reaction(
|
||||
() => ({
|
||||
customScriptPaths: [...this.config.customScriptPaths],
|
||||
version: this.filesVersionId,
|
||||
}),
|
||||
async ({ customScriptPaths }) => {
|
||||
const promises = new Array<Promise<[Result, WatchedFile]>>();
|
||||
|
||||
const activePaths = new Set<string>();
|
||||
for (const fsPath of customScriptPaths) {
|
||||
activePaths.add(fsPath);
|
||||
let entry = watchers.get(fsPath);
|
||||
if (!entry) {
|
||||
entry = new WatchedFile(fsPath, this.debugSession);
|
||||
watchers.set(fsPath, entry);
|
||||
entry.watcher.onDidChange(() => {
|
||||
this.fileChangeDebouncedScheduler.run(() => {
|
||||
this.filesVersionId++;
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
promises.push(entry.update().then(e => [e, entry!]));
|
||||
}
|
||||
|
||||
for (const [fsPath, entry] of watchers) {
|
||||
if (!activePaths.has(fsPath)) {
|
||||
promises.push(entry.remove().then(e => [e, entry]));
|
||||
entry.watcher.dispose();
|
||||
watchers.delete(fsPath);
|
||||
}
|
||||
}
|
||||
|
||||
const results = await Promise.all(promises);
|
||||
const errors = results.filter(([r]) => r.error);
|
||||
for (const [e, watcher] of errors) {
|
||||
window.showErrorMessage(
|
||||
`Error while evaluating "${watcher.fsPath}": ${
|
||||
e.error!.message
|
||||
}`
|
||||
);
|
||||
}
|
||||
|
||||
if (promises.length > 0) {
|
||||
this.onChangeEmitter.emit();
|
||||
}
|
||||
},
|
||||
{
|
||||
fireImmediately: true,
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const injectedScriptsKey = `@hediet/debug-visualizer/injectedScripts`;
|
||||
|
||||
interface Result {
|
||||
error?: { message: string };
|
||||
}
|
||||
|
||||
class WatchedFile {
|
||||
public static nextId = 0;
|
||||
public readonly watcher = workspace.createFileSystemWatcher(this.fsPath);
|
||||
public readonly id = WatchedFile.nextId++;
|
||||
public lastContent: string | undefined = undefined;
|
||||
|
||||
constructor(
|
||||
public readonly fsPath: string,
|
||||
private readonly debugSession: DebugSessionProxy
|
||||
) {}
|
||||
|
||||
public async update(): Promise<Result> {
|
||||
if (!existsSync(this.fsPath)) {
|
||||
return {
|
||||
error: { message: `File does not exist.` },
|
||||
};
|
||||
}
|
||||
|
||||
const scriptContent = readFileSync(this.fsPath, {
|
||||
encoding: "utf-8",
|
||||
});
|
||||
|
||||
if (this.lastContent === scriptContent) {
|
||||
return {};
|
||||
}
|
||||
this.lastContent = scriptContent;
|
||||
|
||||
const expression = `
|
||||
(
|
||||
load => {
|
||||
var module = {};
|
||||
load(module);
|
||||
global["${injectedScriptsKey}"]["${this.id}"] = module.exports;
|
||||
}
|
||||
)
|
||||
(
|
||||
function (module) { ${scriptContent} }
|
||||
)`;
|
||||
try {
|
||||
await this.debugSession.evaluate({
|
||||
expression,
|
||||
frameId: undefined,
|
||||
context: "repl",
|
||||
});
|
||||
} catch (e) {
|
||||
return { error: { message: e.message } };
|
||||
}
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
public async remove(): Promise<Result> {
|
||||
const expression = `(() => { delete global["${injectedScriptsKey}"]["${this.id}"]; })()`;
|
||||
try {
|
||||
await this.debugSession.evaluate({
|
||||
expression,
|
||||
frameId: undefined,
|
||||
context: "repl",
|
||||
});
|
||||
} catch (e) {
|
||||
return { error: { message: e.message } };
|
||||
}
|
||||
|
||||
return {};
|
||||
}
|
||||
}
|
||||
|
||||
class DebouncedScheduler {
|
||||
private timeout: NodeJS.Timeout | undefined;
|
||||
|
||||
constructor(private readonly debounceTimeout: number) {}
|
||||
|
||||
public run(action: () => void): void {
|
||||
this.clear();
|
||||
this.timeout = setTimeout(action, this.debounceTimeout);
|
||||
}
|
||||
|
||||
private clear() {
|
||||
if (this.timeout) {
|
||||
clearTimeout(this.timeout);
|
||||
this.timeout = undefined;
|
||||
}
|
||||
}
|
||||
|
||||
public dispose(): void {
|
||||
this.clear();
|
||||
}
|
||||
}
|
||||
80
extension/src/VisualizationBackend/VisualizationBackend.ts
Normal file
@@ -0,0 +1,80 @@
|
||||
import {
|
||||
DataExtractionResult,
|
||||
DataExtractorId,
|
||||
} from "@hediet/debug-visualizer-data-extraction";
|
||||
import { Disposable } from "@hediet/std/disposable";
|
||||
import { DebugSessionProxy } from "../proxies/DebugSessionProxy";
|
||||
import { CompletionItem, FormattedMessage } from "../webviewContract";
|
||||
import { reaction } from "mobx";
|
||||
import { DebuggerViewProxy } from "../proxies/DebuggerViewProxy";
|
||||
import { EventEmitter, EventSource } from "@hediet/std/events";
|
||||
|
||||
export interface DebugSessionVisualizationSupport {
|
||||
createBackend(session: DebugSessionProxy): VisualizationBackend | undefined;
|
||||
}
|
||||
|
||||
export interface VisualizationBackend extends Disposable {
|
||||
readonly onChange: EventSource;
|
||||
|
||||
getVisualizationData(
|
||||
args: GetVisualizationDataArgs
|
||||
): Promise<
|
||||
| { kind: "data"; result: DataExtractionResult }
|
||||
| { kind: "error"; message: FormattedMessage }
|
||||
>;
|
||||
|
||||
/**
|
||||
* The language that expressions must be written in.
|
||||
*/
|
||||
readonly expressionLanguageId: string | undefined;
|
||||
|
||||
getCompletions(text: string, column: number): Promise<CompletionItem[]>;
|
||||
}
|
||||
|
||||
export interface GetVisualizationDataArgs {
|
||||
expression: string;
|
||||
preferredExtractorId: DataExtractorId | undefined;
|
||||
}
|
||||
|
||||
export abstract class VisualizationBackendBase implements VisualizationBackend {
|
||||
public readonly dispose = Disposable.fn();
|
||||
|
||||
protected readonly onChangeEmitter = new EventEmitter();
|
||||
public readonly onChange = this.onChangeEmitter;
|
||||
|
||||
constructor(
|
||||
protected readonly debugSession: DebugSessionProxy,
|
||||
protected readonly debuggerView: DebuggerViewProxy
|
||||
) {
|
||||
this.dispose.track({
|
||||
dispose: reaction(
|
||||
() => debuggerView.getActiveStackFrameId(debugSession),
|
||||
activeStackFrameId => {
|
||||
if (activeStackFrameId !== undefined) {
|
||||
this.onChangeEmitter.emit();
|
||||
}
|
||||
}
|
||||
),
|
||||
});
|
||||
}
|
||||
|
||||
public abstract readonly expressionLanguageId: string | undefined;
|
||||
|
||||
public abstract getVisualizationData(
|
||||
args: GetVisualizationDataArgs
|
||||
): Promise<
|
||||
| { kind: "data"; result: DataExtractionResult }
|
||||
| { kind: "error"; message: FormattedMessage }
|
||||
>;
|
||||
|
||||
public async getCompletions(
|
||||
text: string,
|
||||
column: number
|
||||
): Promise<CompletionItem[]> {
|
||||
return await this.debugSession.getCompletions({
|
||||
text,
|
||||
frameId: this.debuggerView.getActiveStackFrameId(this.debugSession),
|
||||
column,
|
||||
});
|
||||
}
|
||||
}
|
||||
6
extension/src/VisualizationBackend/index.ts
Normal file
@@ -0,0 +1,6 @@
|
||||
export * from "./VisualizationBackend";
|
||||
|
||||
export { ComposedVisualizationSupport } from "./ComposedVisualizationSupport";
|
||||
export { JsEvaluationEngine } from "./JsVisualizationSupport";
|
||||
export { GenericVisualizationSupport } from "./GenericVisualizationSupport";
|
||||
export { ConfigurableVisualizationSupport } from "./ConfigurableVisualizationSupport";
|
||||
@@ -1,8 +1,8 @@
|
||||
import {
|
||||
DataExtractionResult,
|
||||
isExtractedData,
|
||||
isVisualizationData,
|
||||
} from "@hediet/debug-visualizer-data-extraction";
|
||||
import { FormattedMessage } from "../../contract";
|
||||
import { FormattedMessage } from "../webviewContract";
|
||||
|
||||
export interface ParseEvaluationResultContext {
|
||||
debugAdapterType: string;
|
||||
@@ -40,7 +40,7 @@ export function parseEvaluationResultFromGenericDebugAdapter(
|
||||
// result is now { kind: { ... }, text: "some\ntext" }
|
||||
}
|
||||
|
||||
if (!isExtractedData(resultObj)) {
|
||||
if (!isVisualizationData(resultObj)) {
|
||||
return {
|
||||
kind: "error",
|
||||
message: {
|
||||
@@ -1,11 +1,11 @@
|
||||
import { DataExtractorId } from "@hediet/debug-visualizer-data-extraction";
|
||||
import { DataExtractionState, CompletionItem } from "../contract";
|
||||
import { DataExtractionState, CompletionItem } from "../webviewContract";
|
||||
|
||||
export interface EvaluationWatchService {
|
||||
createEvaluationWatcher(
|
||||
export interface VisualizationWatchModel {
|
||||
createWatch(
|
||||
expression: string,
|
||||
options: EvaluationWatcherOptions
|
||||
): EvaluationWatcher;
|
||||
options: VisualizationWatchOptions
|
||||
): VisualizationWatch;
|
||||
|
||||
getCompletions(text: string, column: number): Promise<CompletionItem[]>;
|
||||
|
||||
@@ -17,14 +17,17 @@ export interface EvaluationWatchService {
|
||||
readonly languageId: string | undefined;
|
||||
}
|
||||
|
||||
export interface EvaluationWatcherOptions {
|
||||
export interface VisualizationWatchOptions {
|
||||
preferredDataExtractor?: DataExtractorId | undefined;
|
||||
}
|
||||
|
||||
export interface EvaluationWatcher {
|
||||
export interface VisualizationWatch {
|
||||
/** This field is constant. */
|
||||
readonly expression: string;
|
||||
|
||||
/** This field is observable */
|
||||
readonly state: DataExtractionState;
|
||||
|
||||
/** This field is observable */
|
||||
readonly preferredDataExtractor: DataExtractorId | undefined;
|
||||
|
||||
@@ -0,0 +1,162 @@
|
||||
import { DataExtractorId } from "@hediet/debug-visualizer-data-extraction";
|
||||
import { hotClass } from "@hediet/node-reload";
|
||||
import { Disposable } from "@hediet/std/disposable";
|
||||
import { EventEmitter } from "@hediet/std/events";
|
||||
import { wait } from "@hediet/std/timer";
|
||||
import { action, observable } from "mobx";
|
||||
import { CancellationToken, CancellationTokenSource } from "vscode";
|
||||
import { VisualizationBackend } from "../VisualizationBackend/VisualizationBackend";
|
||||
import { CompletionItem, DataExtractionState } from "../webviewContract";
|
||||
import {
|
||||
VisualizationWatch,
|
||||
VisualizationWatchModel,
|
||||
VisualizationWatchOptions,
|
||||
} from "./VisualizationWatchModel";
|
||||
|
||||
@hotClass(module)
|
||||
export class VisualizationWatchModelImpl implements VisualizationWatchModel {
|
||||
public readonly dispose = Disposable.fn();
|
||||
private readonly watchers = new Set<ObservableVisualizationWatch>();
|
||||
|
||||
constructor(private readonly visualizationBackend: VisualizationBackend) {
|
||||
this.dispose.track(
|
||||
visualizationBackend.onChange.sub(() => {
|
||||
if (visualizationBackend.expressionLanguageId !== undefined) {
|
||||
this.lastLanguageId =
|
||||
visualizationBackend.expressionLanguageId;
|
||||
}
|
||||
})
|
||||
);
|
||||
this.lastLanguageId = visualizationBackend.expressionLanguageId;
|
||||
}
|
||||
|
||||
public createWatch(
|
||||
expression: string,
|
||||
options: VisualizationWatchOptions
|
||||
): VisualizationWatch {
|
||||
const w = new ObservableVisualizationWatch(
|
||||
expression,
|
||||
options,
|
||||
this.visualizationBackend
|
||||
);
|
||||
w.onDispose.sub(() => {
|
||||
this.watchers.delete(w);
|
||||
});
|
||||
this.watchers.add(w);
|
||||
return w;
|
||||
}
|
||||
|
||||
@observable
|
||||
private lastLanguageId: string | undefined = undefined;
|
||||
|
||||
get languageId(): string | undefined {
|
||||
return this.lastLanguageId;
|
||||
}
|
||||
|
||||
public getCompletions(
|
||||
text: string,
|
||||
column: number
|
||||
): Promise<CompletionItem[]> {
|
||||
return this.visualizationBackend.getCompletions(text, column);
|
||||
}
|
||||
}
|
||||
|
||||
class ObservableVisualizationWatch implements VisualizationWatch {
|
||||
public readonly dispose = Disposable.fn();
|
||||
private readonly onDisposeEmitter = new EventEmitter();
|
||||
public readonly onDispose = this.onDisposeEmitter.asEvent();
|
||||
|
||||
constructor(
|
||||
public readonly expression: string,
|
||||
options: VisualizationWatchOptions,
|
||||
private readonly visualizationBackend: VisualizationBackend
|
||||
) {
|
||||
this._preferredDataExtractor = options.preferredDataExtractor;
|
||||
|
||||
this.dispose.track(
|
||||
visualizationBackend.onChange.sub(() => {
|
||||
this.refresh();
|
||||
})
|
||||
);
|
||||
|
||||
this.dispose.track({
|
||||
dispose: () => {
|
||||
this.onDisposeEmitter.emit();
|
||||
},
|
||||
});
|
||||
|
||||
this.refresh();
|
||||
}
|
||||
|
||||
private _preferredDataExtractor: DataExtractorId | undefined = undefined;
|
||||
|
||||
public get preferredDataExtractor(): DataExtractorId | undefined {
|
||||
return this._preferredDataExtractor;
|
||||
}
|
||||
|
||||
@action
|
||||
public setPreferredDataExtractor(id: DataExtractorId | undefined): void {
|
||||
this._preferredDataExtractor = id;
|
||||
this.refresh();
|
||||
}
|
||||
|
||||
private runningRefreshOperation: Disposable | undefined;
|
||||
|
||||
public refresh(): void {
|
||||
if (this.runningRefreshOperation) {
|
||||
this.runningRefreshOperation.dispose();
|
||||
}
|
||||
const tokenSource = new CancellationTokenSource();
|
||||
this.runningRefreshOperation = {
|
||||
dispose: () => {
|
||||
tokenSource.cancel();
|
||||
},
|
||||
};
|
||||
this._refresh(tokenSource.token);
|
||||
}
|
||||
|
||||
private async _refresh(token: CancellationToken): Promise<void> {
|
||||
/*const session = this.debuggerView.activeDebugSession;
|
||||
if (!session) {
|
||||
this._state = { kind: "noDebugSession" };
|
||||
return;
|
||||
}*/
|
||||
|
||||
//const frameId = this.debuggerView.activeFrameId;
|
||||
|
||||
this._state = { kind: "loading" };
|
||||
|
||||
/*
|
||||
const visualizationBackend = this.getVisualizationBackend();
|
||||
if (!visualizationBackend) {
|
||||
this._state = {
|
||||
kind: "error",
|
||||
message: `The debug adapter "${session.session.type}" is not supported.`,
|
||||
};
|
||||
return;
|
||||
}*/
|
||||
|
||||
const result = await this.visualizationBackend.getVisualizationData({
|
||||
expression: this.expression,
|
||||
//frameId,
|
||||
preferredExtractorId: this.preferredDataExtractor,
|
||||
});
|
||||
|
||||
if (result.kind === "error") {
|
||||
// give cancellation requested more time in case of errors
|
||||
await wait(330);
|
||||
}
|
||||
|
||||
if (token.isCancellationRequested) {
|
||||
return;
|
||||
}
|
||||
|
||||
this._state = result;
|
||||
}
|
||||
|
||||
@observable
|
||||
public _state: DataExtractionState = { kind: "loading" };
|
||||
public get state(): DataExtractionState {
|
||||
return this._state;
|
||||
}
|
||||
}
|
||||
2
extension/src/VisualizationWatchModel/index.ts
Normal file
@@ -0,0 +1,2 @@
|
||||
export * from "./VisualizationWatchModelImpl";
|
||||
export * from "./VisualizationWatchModel";
|
||||
@@ -1,217 +0,0 @@
|
||||
import { Disposable } from "@hediet/std/disposable";
|
||||
import { debug, DebugSession } from "vscode";
|
||||
import { EventEmitter } from "@hediet/std/events";
|
||||
import { CompletionItem } from "./contract";
|
||||
import { observable, runInAction, action } 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 {
|
||||
let result = this.sessions.get(session);
|
||||
if (!result) {
|
||||
result = new VsCodeDebugSession(session);
|
||||
this.sessions.set(session, result);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
constructor() {
|
||||
this.dispose.track([
|
||||
debug.onDidStartDebugSession(session => {
|
||||
const e = this.sessions.get(session)!;
|
||||
this._onDidStartDebugSession.emit({ session: e });
|
||||
}),
|
||||
debug.onDidTerminateDebugSession(session => {
|
||||
this.sessions.delete(session);
|
||||
}),
|
||||
debug.registerDebugAdapterTrackerFactory("*", {
|
||||
createDebugAdapterTracker: session => {
|
||||
const extendedSession = this.getDebugSession(session);
|
||||
return {
|
||||
onWillReceiveMessage: msg => {
|
||||
console.log(msg.type, msg.event, msg);
|
||||
},
|
||||
onDidSendMessage: async msg => {
|
||||
type Message =
|
||||
| StoppedEvent
|
||||
| ThreadsResponse
|
||||
| ContinueLikeResponse;
|
||||
|
||||
interface ContinueLikeResponse {
|
||||
type: "response";
|
||||
command:
|
||||
| "continue"
|
||||
| "stepIn"
|
||||
| "stepOut"
|
||||
| "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,
|
||||
startFrame: 0,
|
||||
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" ||
|
||||
m.command === "stepIn" ||
|
||||
m.command === "stepOut"
|
||||
) {
|
||||
extendedSession[
|
||||
"activeStackFrame"
|
||||
] = undefined;
|
||||
}
|
||||
}
|
||||
},
|
||||
};
|
||||
},
|
||||
}),
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
interface StackFrame {
|
||||
id: number;
|
||||
name: string;
|
||||
}
|
||||
|
||||
export class VsCodeDebugSession {
|
||||
@observable protected activeStackFrame: number | undefined;
|
||||
|
||||
constructor(public readonly session: DebugSession) {}
|
||||
|
||||
protected async getStackTrace(args: {
|
||||
threadId: number;
|
||||
startFrame?: number;
|
||||
levels?: number;
|
||||
}): Promise<{ totalFrames?: number; stackFrames: StackFrame[] }> {
|
||||
try {
|
||||
const reply = (await this.session.customRequest("stackTrace", {
|
||||
threadId: args.threadId,
|
||||
levels: args.levels,
|
||||
startFrame: args.startFrame || 0,
|
||||
})) as { totalFrames?: number; stackFrames: StackFrame[] };
|
||||
return reply;
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
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,
|
||||
});
|
||||
if (!reply) {
|
||||
return [];
|
||||
}
|
||||
return reply.targets;
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Evaluates the given expression.
|
||||
* If context is "watch", long results are usually shortened.
|
||||
*/
|
||||
public async evaluate(args: {
|
||||
expression: string;
|
||||
frameId: number | undefined;
|
||||
context: "watch" | "repl";
|
||||
}): Promise<{ result: string }> {
|
||||
const reply = await this.session.customRequest("evaluate", {
|
||||
expression: args.expression,
|
||||
frameId: args.frameId,
|
||||
|
||||
context: args.context,
|
||||
});
|
||||
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 => {
|
||||
this.updateActiveDebugSession(activeSession);
|
||||
})
|
||||
);
|
||||
this.updateActiveDebugSession(debug.activeDebugSession);
|
||||
}
|
||||
|
||||
@action
|
||||
private updateActiveDebugSession(activeSession: DebugSession | undefined) {
|
||||
if (!activeSession) {
|
||||
this._activeDebugSession = undefined;
|
||||
} else {
|
||||
const s = this.vsCodeDebugger.getDebugSession(activeSession);
|
||||
this._activeDebugSession = s;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -12,39 +12,55 @@ if (process.env.HOT_RELOAD) {
|
||||
}
|
||||
registerUpdateReconciler(module);
|
||||
|
||||
import { WebViews } from "./WebViews";
|
||||
import { Server } from "./Server";
|
||||
import { InternalWebviewManager } from "./webview/InternalWebviewManager";
|
||||
import { WebviewServer } from "./webview/WebviewServer";
|
||||
import { Config } from "./Config";
|
||||
import { VsCodeDebugger, VsCodeDebuggerView } from "./VsCodeDebugger";
|
||||
import { EvaluationWatchServiceImpl } from "./EvaluationWatchService";
|
||||
import { DebuggerProxy } from "./proxies/DebuggerProxy";
|
||||
import { DebuggerViewProxy } from "./proxies/DebuggerViewProxy";
|
||||
import { VisualizationWatchModelImpl } from "./VisualizationWatchModel";
|
||||
import {
|
||||
ComposedEvaluationEngine,
|
||||
ComposedVisualizationSupport,
|
||||
JsEvaluationEngine,
|
||||
GenericEvaluationEngine,
|
||||
ConfiguredEvaluationEngine,
|
||||
} from "./EvaluationWatchService/EvaluationEngine";
|
||||
GenericVisualizationSupport,
|
||||
ConfigurableVisualizationSupport,
|
||||
} from "./VisualizationBackend";
|
||||
import { DispatchingVisualizationBackend } from "./VisualizationBackend/DispatchingVisualizationBackend";
|
||||
|
||||
export function activate(context: ExtensionContext) {
|
||||
context.subscriptions.push(
|
||||
hotRequireExportedFn(module, Extension, Extension => new Extension())
|
||||
);
|
||||
}
|
||||
|
||||
export function deactivate() {}
|
||||
|
||||
export class Extension {
|
||||
public readonly dispose = Disposable.fn();
|
||||
|
||||
private readonly config = new Config();
|
||||
|
||||
private readonly debugger = this.dispose.track(new VsCodeDebugger());
|
||||
private readonly debugger = this.dispose.track(new DebuggerProxy());
|
||||
private readonly debuggerView = this.dispose.track(
|
||||
new VsCodeDebuggerView(this.debugger)
|
||||
new DebuggerViewProxy(this.debugger)
|
||||
);
|
||||
|
||||
public readonly dataSource = new EvaluationWatchServiceImpl(
|
||||
this.debuggerView,
|
||||
new ComposedEvaluationEngine([
|
||||
new ConfiguredEvaluationEngine(this.config),
|
||||
new JsEvaluationEngine(),
|
||||
new GenericEvaluationEngine(),
|
||||
])
|
||||
public readonly dataSource = new VisualizationWatchModelImpl(
|
||||
new DispatchingVisualizationBackend(
|
||||
new ComposedVisualizationSupport([
|
||||
new ConfigurableVisualizationSupport(
|
||||
this.config,
|
||||
this.debuggerView
|
||||
),
|
||||
new JsEvaluationEngine(this.debuggerView, this.config),
|
||||
new GenericVisualizationSupport(this.debuggerView),
|
||||
]),
|
||||
this.debuggerView
|
||||
)
|
||||
);
|
||||
|
||||
private readonly server = new Server(this.dataSource, this.config);
|
||||
private readonly views = this.dispose.track(new WebViews(this.server));
|
||||
private readonly server = new WebviewServer(this.dataSource, this.config);
|
||||
private readonly views = this.dispose.track(
|
||||
new InternalWebviewManager(this.server, this.config)
|
||||
);
|
||||
|
||||
constructor() {
|
||||
if (getReloadCount(module) > 0) {
|
||||
@@ -72,17 +88,36 @@ export class Extension {
|
||||
}
|
||||
|
||||
const selection = editor.selection;
|
||||
const selectedText = editor.document.getText(selection);
|
||||
|
||||
let selectedText;
|
||||
if (selection.isEmpty) {
|
||||
const lineText = editor.document.lineAt(selection.start)
|
||||
.text;
|
||||
const regexp = /`(.*)`/g;
|
||||
selectedText = "";
|
||||
let match;
|
||||
while ((match = regexp.exec(lineText))) {
|
||||
if (
|
||||
match.index <= selection.start.character &&
|
||||
selection.start.character <=
|
||||
match.index + match[0].length
|
||||
) {
|
||||
selectedText = match[1];
|
||||
}
|
||||
}
|
||||
} else {
|
||||
selectedText = editor.document.getText(selection);
|
||||
}
|
||||
|
||||
if (!selectedText) {
|
||||
return;
|
||||
}
|
||||
|
||||
const connections = [...this.server.connections.values()];
|
||||
if (connections.length > 0) {
|
||||
const latestConnection =
|
||||
connections[connections.length - 1];
|
||||
const latestConnection =
|
||||
connections[connections.length - 1];
|
||||
|
||||
if (latestConnection) {
|
||||
latestConnection.setExpression(selectedText);
|
||||
} else {
|
||||
this.views.createNew(selectedText);
|
||||
@@ -92,11 +127,3 @@ export class Extension {
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export function activate(context: ExtensionContext) {
|
||||
context.subscriptions.push(
|
||||
hotRequireExportedFn(module, Extension, Extension => new Extension())
|
||||
);
|
||||
}
|
||||
|
||||
export function deactivate() {}
|
||||
|
||||
118
extension/src/proxies/DebugSessionProxy.ts
Normal file
@@ -0,0 +1,118 @@
|
||||
import { DebugSession } from "vscode";
|
||||
import { CompletionItem } from "../webviewContract";
|
||||
import { observable } from "mobx";
|
||||
|
||||
export class DebugSessionProxy {
|
||||
@observable private _activeStackFrameId: number | undefined;
|
||||
|
||||
constructor(public readonly session: DebugSession) {}
|
||||
|
||||
protected async getStackTrace(args: {
|
||||
threadId: number;
|
||||
startFrame?: number;
|
||||
levels?: number;
|
||||
}): Promise<{ totalFrames?: number; stackFrames: StackFrame[] }> {
|
||||
try {
|
||||
const reply = (await this.session.customRequest("stackTrace", {
|
||||
threadId: args.threadId,
|
||||
levels: args.levels,
|
||||
startFrame: args.startFrame || 0,
|
||||
})) as { totalFrames?: number; stackFrames: StackFrame[] };
|
||||
return reply;
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
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,
|
||||
});
|
||||
if (!reply) {
|
||||
return [];
|
||||
}
|
||||
return reply.targets;
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
public async getScopes(args: { frameId: number }): Promise<Scope[]> {
|
||||
try {
|
||||
const reply = await this.session.customRequest("scopes", {
|
||||
frameId: args.frameId,
|
||||
});
|
||||
if (!reply) {
|
||||
return [];
|
||||
}
|
||||
return reply.scopes;
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
public async getVariables(args: {
|
||||
variablesReference: number;
|
||||
}): Promise<Variable[]> {
|
||||
try {
|
||||
const reply = await this.session.customRequest("variables", {
|
||||
variablesReference: args.variablesReference,
|
||||
});
|
||||
if (!reply) {
|
||||
return [];
|
||||
}
|
||||
return reply.variables;
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Evaluates the given expression.
|
||||
* If context is "watch", long results are usually shortened.
|
||||
*/
|
||||
|
||||
public async evaluate(args: {
|
||||
expression: string;
|
||||
frameId: number | undefined;
|
||||
context: "watch" | "repl" | "copy";
|
||||
}): Promise<{ result: string; variablesReference: number }> {
|
||||
const reply = await this.session.customRequest("evaluate", {
|
||||
expression: args.expression,
|
||||
frameId: args.frameId,
|
||||
context: args.context,
|
||||
});
|
||||
return {
|
||||
result: reply.result,
|
||||
variablesReference: reply.variablesReference,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
interface Scope {
|
||||
name: string;
|
||||
expensive: boolean;
|
||||
variablesReference: number;
|
||||
}
|
||||
|
||||
interface Variable {
|
||||
name: string;
|
||||
value: string;
|
||||
variablesReference: number;
|
||||
}
|
||||
|
||||
interface StackFrame {
|
||||
id: number;
|
||||
name: string;
|
||||
}
|
||||
112
extension/src/proxies/DebuggerProxy.ts
Normal file
@@ -0,0 +1,112 @@
|
||||
import { Disposable } from "@hediet/std/disposable";
|
||||
import { debug, DebugSession } from "vscode";
|
||||
import { EventEmitter } from "@hediet/std/events";
|
||||
import { runInAction } from "mobx";
|
||||
import { DebugSessionProxy } from "./DebugSessionProxy";
|
||||
|
||||
/**
|
||||
* Decorates DebugSession instances and emits an event for new debug sessions.
|
||||
*/
|
||||
export class DebuggerProxy {
|
||||
public readonly dispose = Disposable.fn();
|
||||
private readonly sessions = new Map<DebugSession, DebugSessionProxy>();
|
||||
|
||||
private readonly _onDidStartDebugSession = new EventEmitter<{
|
||||
session: DebugSessionProxy;
|
||||
}>();
|
||||
public readonly onDidStartDebugSession = this._onDidStartDebugSession.asEvent();
|
||||
|
||||
public getDebugSessionProxy(session: DebugSession): DebugSessionProxy {
|
||||
let result = this.sessions.get(session);
|
||||
if (!result) {
|
||||
result = new DebugSessionProxy(session);
|
||||
this.sessions.set(session, result);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
constructor() {
|
||||
this.dispose.track([
|
||||
debug.onDidStartDebugSession(session => {
|
||||
const e = this.sessions.get(session)!;
|
||||
this._onDidStartDebugSession.emit({ session: e });
|
||||
}),
|
||||
debug.onDidTerminateDebugSession(session => {
|
||||
this.sessions.delete(session);
|
||||
}),
|
||||
debug.registerDebugAdapterTrackerFactory("*", {
|
||||
createDebugAdapterTracker: session => {
|
||||
const extendedSession = this.getDebugSessionProxy(session);
|
||||
return {
|
||||
onDidSendMessage: async msg => {
|
||||
type Message =
|
||||
| StoppedEvent
|
||||
| ThreadsResponse
|
||||
| ContinueLikeResponse;
|
||||
|
||||
interface ContinueLikeResponse {
|
||||
type: "response";
|
||||
command:
|
||||
| "continue"
|
||||
| "stepIn"
|
||||
| "stepOut"
|
||||
| "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,
|
||||
startFrame: 0,
|
||||
levels: 1,
|
||||
});
|
||||
extendedSession["_activeStackFrameId"] =
|
||||
r.stackFrames.length > 0
|
||||
? r.stackFrames[0].id
|
||||
: undefined;
|
||||
}
|
||||
} else if (m.type === "response") {
|
||||
if (
|
||||
m.command === "continue" ||
|
||||
m.command === "next" ||
|
||||
m.command === "stepIn" ||
|
||||
m.command === "stepOut"
|
||||
) {
|
||||
extendedSession[
|
||||
"_activeStackFrameId"
|
||||
] = undefined;
|
||||
}
|
||||
}
|
||||
},
|
||||
};
|
||||
},
|
||||
}),
|
||||
]);
|
||||
}
|
||||
}
|
||||
41
extension/src/proxies/DebuggerViewProxy.ts
Normal file
@@ -0,0 +1,41 @@
|
||||
import { Disposable } from "@hediet/std/disposable";
|
||||
import { debug, DebugSession } from "vscode";
|
||||
import { observable, action } from "mobx";
|
||||
import { DebuggerProxy } from "./DebuggerProxy";
|
||||
import { DebugSessionProxy } from "./DebugSessionProxy";
|
||||
|
||||
/**
|
||||
* Tracks the currently active debug session and its currently active stack frame.
|
||||
* This is currently all faked. A proper API would be nice.
|
||||
*/
|
||||
export class DebuggerViewProxy {
|
||||
public readonly dispose = Disposable.fn();
|
||||
|
||||
@observable private _activeDebugSession: DebugSessionProxy | undefined;
|
||||
|
||||
public get activeDebugSession(): DebugSessionProxy | undefined {
|
||||
return this._activeDebugSession;
|
||||
}
|
||||
|
||||
public getActiveStackFrameId(
|
||||
session: DebugSessionProxy
|
||||
): number | undefined {
|
||||
return session["_activeStackFrameId"];
|
||||
}
|
||||
|
||||
constructor(private debuggerProxy: DebuggerProxy) {
|
||||
this.dispose.track(
|
||||
debug.onDidChangeActiveDebugSession(activeSession => {
|
||||
this.updateActiveDebugSession(activeSession);
|
||||
})
|
||||
);
|
||||
this.updateActiveDebugSession(debug.activeDebugSession);
|
||||
}
|
||||
|
||||
@action
|
||||
private updateActiveDebugSession(activeSession: DebugSession | undefined) {
|
||||
this._activeDebugSession = activeSession
|
||||
? this.debuggerProxy.getDebugSessionProxy(activeSession)
|
||||
: undefined;
|
||||
}
|
||||
}
|
||||
120
extension/src/utils/VsCodeSettings.ts
Normal file
@@ -0,0 +1,120 @@
|
||||
import { Uri, workspace, ConfigurationTarget, Disposable } from "vscode";
|
||||
import { fromResource } from "mobx-utils";
|
||||
import { computed, runInAction } from "mobx";
|
||||
import { EventEmitter } from "@hediet/std/events";
|
||||
|
||||
export interface Serializer<T> {
|
||||
deserialize: (val: any) => T;
|
||||
serializer: (val: T) => any;
|
||||
}
|
||||
|
||||
export function serializerWithDefault<T>(defaultValue: T): Serializer<T> {
|
||||
return {
|
||||
deserialize: val => (val === undefined ? defaultValue : val),
|
||||
serializer: val => val,
|
||||
};
|
||||
}
|
||||
|
||||
export class VsCodeSetting<T> {
|
||||
public get T(): T {
|
||||
throw new Error();
|
||||
}
|
||||
|
||||
public readonly serializer: Serializer<T>;
|
||||
public readonly scope: Uri | undefined;
|
||||
private readonly settingResource: VsCodeSettingResource;
|
||||
|
||||
public constructor(
|
||||
public readonly id: string,
|
||||
options: {
|
||||
serializer?: Serializer<T>;
|
||||
scope?: Uri;
|
||||
} = {}
|
||||
) {
|
||||
this.scope = options.scope;
|
||||
this.serializer = options.serializer || {
|
||||
deserialize: val => val,
|
||||
serializer: val => val,
|
||||
};
|
||||
this.settingResource = new VsCodeSettingResource(this.id, this.scope);
|
||||
}
|
||||
|
||||
public get(): T {
|
||||
const result = this.settingResource.value;
|
||||
return this.serializer.deserialize(result);
|
||||
}
|
||||
|
||||
public async set(value: T): Promise<void> {
|
||||
const value2 = this.serializer.serializer(value);
|
||||
const c = workspace.getConfiguration(undefined, this.scope);
|
||||
const result = c.inspect(this.id);
|
||||
let target: ConfigurationTarget;
|
||||
|
||||
if (
|
||||
result &&
|
||||
[result.workspaceFolderValue].some(i => i !== undefined)
|
||||
) {
|
||||
target = ConfigurationTarget.WorkspaceFolder;
|
||||
}
|
||||
if (result && [result.workspaceValue].some(i => i !== undefined)) {
|
||||
target = ConfigurationTarget.Workspace;
|
||||
} else {
|
||||
target = ConfigurationTarget.Global;
|
||||
}
|
||||
|
||||
await c.update(this.id, value2, target);
|
||||
}
|
||||
}
|
||||
|
||||
class VsCodeSettingResource {
|
||||
public static onConfigChange = new EventEmitter();
|
||||
|
||||
private subscription: Disposable | undefined;
|
||||
private readonly resource = fromResource<any>(
|
||||
update => {
|
||||
update(this.readValue());
|
||||
this.subscription = VsCodeSettingResource.onConfigChange.sub(() => {
|
||||
update(this.readValue());
|
||||
});
|
||||
},
|
||||
() => this.subscription!.dispose(),
|
||||
this.readValue()
|
||||
);
|
||||
|
||||
constructor(
|
||||
private readonly id: string,
|
||||
private readonly scope: Uri | undefined
|
||||
) {}
|
||||
|
||||
private readValue(): any {
|
||||
const val = workspace
|
||||
.getConfiguration(undefined, this.scope)
|
||||
.get(this.id);
|
||||
return val;
|
||||
}
|
||||
|
||||
/**
|
||||
* This improves change detection.
|
||||
*/
|
||||
private readonly stringifiedSettingValue = computed(
|
||||
() => JSON.stringify(this.resource.current()),
|
||||
{
|
||||
name: `VsCodeSettingResource[${this.id}].value`,
|
||||
context: this,
|
||||
}
|
||||
);
|
||||
|
||||
public get value() {
|
||||
const v = this.stringifiedSettingValue.get();
|
||||
if (v === undefined) {
|
||||
return undefined;
|
||||
}
|
||||
return JSON.parse(v);
|
||||
}
|
||||
}
|
||||
|
||||
workspace.onDidChangeConfiguration(() => {
|
||||
runInAction("Update Configuration", () => {
|
||||
VsCodeSettingResource.onConfigChange.emit();
|
||||
});
|
||||
});
|
||||
@@ -1,15 +1,23 @@
|
||||
import { window, ViewColumn, WebviewPanel } from "vscode";
|
||||
import { Server } from "./Server";
|
||||
import { WebviewServer } from "./WebviewServer";
|
||||
import { Disposable } from "@hediet/std/disposable";
|
||||
import { WindowWithWebviewData } from "../webviewContract";
|
||||
import { Config } from "../Config";
|
||||
|
||||
export const debugVisualizer = "debugVisualizer";
|
||||
|
||||
export class WebViews {
|
||||
private readonly debugVisualizations = new Map<WebviewPanel, WebView>();
|
||||
export class InternalWebviewManager {
|
||||
private readonly openedWebviews = new Map<
|
||||
WebviewPanel,
|
||||
DebugVisualizerWebview
|
||||
>();
|
||||
|
||||
public readonly dispose = Disposable.fn();
|
||||
|
||||
constructor(private readonly server: Server) {
|
||||
constructor(
|
||||
private readonly server: WebviewServer,
|
||||
private readonly config: Config
|
||||
) {
|
||||
this.dispose.track(
|
||||
window.registerWebviewPanelSerializer(debugVisualizer, {
|
||||
deserializeWebviewPanel: async (panel, state) => {
|
||||
@@ -20,7 +28,7 @@ export class WebViews {
|
||||
|
||||
this.dispose.track({
|
||||
dispose: () => {
|
||||
for (const panel of this.debugVisualizations.keys()) {
|
||||
for (const panel of this.openedWebviews.keys()) {
|
||||
panel.dispose();
|
||||
}
|
||||
},
|
||||
@@ -28,7 +36,7 @@ export class WebViews {
|
||||
}
|
||||
|
||||
public createNew(expression: string | undefined = undefined) {
|
||||
const panel = window.createWebviewPanel(
|
||||
const webviewPanel = window.createWebviewPanel(
|
||||
debugVisualizer,
|
||||
"Debug Visualizer",
|
||||
ViewColumn.Two,
|
||||
@@ -44,63 +52,71 @@ export class WebViews {
|
||||
}
|
||||
);
|
||||
|
||||
this.setupView(panel, expression);
|
||||
this.initializeView(webviewPanel, expression);
|
||||
}
|
||||
|
||||
public restore(webviewPanel: WebviewPanel) {
|
||||
this.setupView(webviewPanel);
|
||||
private restore(webviewPanel: WebviewPanel) {
|
||||
this.initializeView(webviewPanel);
|
||||
}
|
||||
|
||||
private setupView(
|
||||
private initializeView(
|
||||
webviewPanel: WebviewPanel,
|
||||
expression: string | undefined = undefined
|
||||
) {
|
||||
webviewPanel.webview.html = getHtml(this.server, expression);
|
||||
const view = new WebView(webviewPanel);
|
||||
this.debugVisualizations.set(webviewPanel, view);
|
||||
webviewPanel.webview.html = getDebugVisualizerWebviewHtml(
|
||||
this.server,
|
||||
expression,
|
||||
this.config
|
||||
);
|
||||
const view = new DebugVisualizerWebview(webviewPanel);
|
||||
this.openedWebviews.set(webviewPanel, view);
|
||||
webviewPanel.onDidDispose(() => {
|
||||
this.debugVisualizations.delete(webviewPanel);
|
||||
this.openedWebviews.delete(webviewPanel);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export class WebView {
|
||||
export class DebugVisualizerWebview {
|
||||
constructor(private readonly webviewPanel: WebviewPanel) {}
|
||||
}
|
||||
|
||||
export function getHtml(
|
||||
server: Server,
|
||||
expression: string | undefined = undefined
|
||||
function getDebugVisualizerWebviewHtml(
|
||||
server: WebviewServer,
|
||||
initialExpression: string | undefined = undefined,
|
||||
config: Config
|
||||
) {
|
||||
const isDev = !!process.env.USE_DEV_UI;
|
||||
return `
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta http-equiv="Content-Security-Policy" content="default-src * 'unsafe-inline' 'unsafe-eval'; script-src * 'unsafe-inline' 'unsafe-eval'; connect-src * 'unsafe-inline'; img-src * data: blob: 'unsafe-inline'; frame-src *; style-src * 'unsafe-inline'; worker-src * data: 'unsafe-inline' 'unsafe-eval'; font-src * 'unsafe-inline' 'unsafe-eval';">
|
||||
<meta http-equiv="Content-Security-Policy" content="default-src * 'unsafe-inline' 'unsafe-eval'; script-src * 'unsafe-inline' 'unsafe-eval'; connect-src * 'unsafe-inline'; img-src * data: blob: 'unsafe-inline'; frame-src *; style-src * 'unsafe-inline'; worker-src * data: blob: data: 'unsafe-inline' 'unsafe-eval'; font-src * 'unsafe-inline' 'unsafe-eval' 'self' data: blob:;">
|
||||
<style>
|
||||
html { height: 100%; width: 100%; padding: 0; margin: 0; }
|
||||
body { height: 100%; width: 100%; padding: 0; margin: 0; }
|
||||
iframe { height: 100%; width: 100%; padding: 0; margin: 0; border: 0; display: block; }
|
||||
iframe { height: 100%; width: 100%; padding: 0; margin: 0; border: 0; display: block; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<script>
|
||||
window.webViewData = ${JSON.stringify({
|
||||
serverSecret: server.secret,
|
||||
serverPort: server.port,
|
||||
publicPath: server.publicPath,
|
||||
expression,
|
||||
})};
|
||||
Object.assign(window, ${JSON.stringify({
|
||||
webviewData: {
|
||||
serverSecret: server.secret,
|
||||
serverPort: server.port,
|
||||
publicPath: server.publicPath,
|
||||
expression: initialExpression,
|
||||
theme: config.theme,
|
||||
},
|
||||
} as WindowWithWebviewData)});
|
||||
const api = window.VsCodeApi = acquireVsCodeApi();
|
||||
window.addEventListener('message', event => {
|
||||
if (event.source === window.frames[0]) {
|
||||
if (event.data.command === "setState") {
|
||||
console.log("setState", event.data.state);
|
||||
// console.log("setState", event.data.state);
|
||||
api.setState(event.data.state);
|
||||
}
|
||||
if (event.data.command === "getState") {
|
||||
console.log("getState, sent ", api.getState());
|
||||
// console.log("getState, sent ", api.getState());
|
||||
window.frames[0].postMessage({ command: "getStateResult", state: api.getState() }, "*");
|
||||
}
|
||||
}
|
||||
@@ -109,10 +125,13 @@ export function getHtml(
|
||||
|
||||
${
|
||||
isDev
|
||||
? `<iframe sandbox="allow-top-navigation allow-scripts allow-same-origin allow-popups allow-pointer-lock allow-forms" src="${server.getIndexUrl(
|
||||
{ mode: "webViewIFrame", expression }
|
||||
? `<iframe sandbox="allow-top-navigation allow-scripts allow-same-origin allow-popups allow-pointer-lock allow-forms" src="${server.getWebviewPageUrl(
|
||||
{
|
||||
mode: "webviewIFrame",
|
||||
expression: initialExpression,
|
||||
}
|
||||
)}"></iframe>`
|
||||
: `<script type="text/javascript" src="${server.mainBundleUrl}"></script>`
|
||||
: `<script type="text/javascript" src="${server.webviewBundleUrl}"></script>`
|
||||
}
|
||||
</body>
|
||||
</html>
|
||||
@@ -1,28 +1,28 @@
|
||||
import { Disposable } from "@hediet/std/disposable";
|
||||
import { debugVisualizerUIContract } from "./contract";
|
||||
import { webviewContract } from "../webviewContract";
|
||||
import { ConsoleRpcLogger, RequestHandlingError } from "@hediet/typed-json-rpc";
|
||||
import {
|
||||
EvaluationWatcher,
|
||||
EvaluationWatchService,
|
||||
} from "./EvaluationWatchService";
|
||||
VisualizationWatch,
|
||||
VisualizationWatchModel,
|
||||
} from "../VisualizationWatchModel";
|
||||
import { WebSocketStream } from "@hediet/typed-json-rpc-websocket";
|
||||
import { observable, autorun } from "mobx";
|
||||
import { Server } from "./Server";
|
||||
import { WebviewServer } from "./WebviewServer";
|
||||
import * as open from "open";
|
||||
import chromeLauncher = require("chrome-launcher");
|
||||
import { Config } from "./Config";
|
||||
import { Config } from "../Config";
|
||||
|
||||
export class ClientConnection {
|
||||
export class WebviewConnection {
|
||||
public readonly dispose = Disposable.fn();
|
||||
@observable
|
||||
private watcher: EvaluationWatcher | undefined = undefined;
|
||||
private watcher: VisualizationWatch | undefined = undefined;
|
||||
|
||||
private readonly client: typeof debugVisualizerUIContract["TClientInterface"];
|
||||
private readonly client: typeof webviewContract["TClientInterface"];
|
||||
|
||||
constructor(
|
||||
evaluationWatchService: EvaluationWatchService,
|
||||
evaluationWatchService: VisualizationWatchModel,
|
||||
stream: WebSocketStream,
|
||||
server: Server,
|
||||
server: WebviewServer,
|
||||
config: Config,
|
||||
serverSecret: string
|
||||
) {
|
||||
@@ -34,10 +34,7 @@ export class ClientConnection {
|
||||
}
|
||||
}
|
||||
|
||||
const {
|
||||
client,
|
||||
channel,
|
||||
} = debugVisualizerUIContract.registerServerToStream(
|
||||
const { client, channel } = webviewContract.registerServerToStream(
|
||||
stream,
|
||||
new ConsoleRpcLogger(),
|
||||
{
|
||||
@@ -58,25 +55,22 @@ export class ClientConnection {
|
||||
setExpression: async ({ newExpression }) => {
|
||||
throwIfNotAuthenticated();
|
||||
|
||||
let oldPreferredDataExtractor: EvaluationWatcher["preferredDataExtractor"];
|
||||
let oldPreferredDataExtractor: VisualizationWatch["preferredDataExtractor"];
|
||||
if (this.watcher) {
|
||||
oldPreferredDataExtractor = this.watcher
|
||||
.preferredDataExtractor;
|
||||
this.dispose.untrack(this.watcher).dispose();
|
||||
}
|
||||
this.watcher = this.dispose.track(
|
||||
evaluationWatchService.createEvaluationWatcher(
|
||||
newExpression,
|
||||
{
|
||||
preferredDataExtractor: oldPreferredDataExtractor,
|
||||
}
|
||||
)
|
||||
evaluationWatchService.createWatch(newExpression, {
|
||||
preferredDataExtractor: oldPreferredDataExtractor,
|
||||
})
|
||||
);
|
||||
},
|
||||
openInBrowser: async ({}) => {
|
||||
throwIfNotAuthenticated();
|
||||
|
||||
const url = server.getIndexUrl({
|
||||
const url = server.getWebviewPageUrl({
|
||||
mode: "standalone",
|
||||
expression: this.watcher
|
||||
? this.watcher.expression
|
||||
@@ -141,6 +135,10 @@ export class ClientConnection {
|
||||
public setExpression(expression: string) {
|
||||
this.client.setExpression({ expression });
|
||||
}
|
||||
|
||||
public setTheme(theme: "light" | "dark"): void {
|
||||
this.client.setTheme({ theme });
|
||||
}
|
||||
}
|
||||
|
||||
async function launchChrome(url: string): Promise<boolean> {
|
||||
@@ -1,38 +1,45 @@
|
||||
import { WebSocketStream } from "@hediet/typed-json-rpc-websocket";
|
||||
import { AddressInfo } from "net";
|
||||
import WebSocket = require("ws");
|
||||
import { ClientConnection } from "./ClientConnection";
|
||||
import { WebviewConnection } from "./WebviewConnection";
|
||||
import * as express from "express";
|
||||
import * as http from "http";
|
||||
import * as serveStatic from "serve-static";
|
||||
import { Config } from "./Config";
|
||||
import { Config } from "../Config";
|
||||
import cryptoRandomString = require("crypto-random-string");
|
||||
import { distPath } from "debug-visualizer-webview";
|
||||
import { EvaluationWatchService } from "./EvaluationWatchService/EvaluationWatchService";
|
||||
import { VisualizationWatchModel } from "../VisualizationWatchModel/VisualizationWatchModel";
|
||||
import { URLSearchParams } from "url";
|
||||
import { autorun, reaction } from "mobx";
|
||||
|
||||
export class Server {
|
||||
export class WebviewServer {
|
||||
private readonly server: http.Server;
|
||||
public readonly secret = cryptoRandomString({ length: 20 });
|
||||
public readonly secret = cryptoRandomString({ length: 30 });
|
||||
|
||||
public readonly connections = new Set<ClientConnection>();
|
||||
public readonly connections = new Set<WebviewConnection>();
|
||||
|
||||
constructor(dataSource: EvaluationWatchService, config: Config) {
|
||||
constructor(
|
||||
dataSource: VisualizationWatchModel,
|
||||
private readonly config: Config
|
||||
) {
|
||||
const app = express();
|
||||
|
||||
app.use(serveStatic(distPath));
|
||||
|
||||
this.server = app.listen();
|
||||
|
||||
/*
|
||||
console.log(
|
||||
`Serving "${distPath}" on port ${
|
||||
(this.server.address() as AddressInfo).port
|
||||
}`
|
||||
);
|
||||
*/
|
||||
|
||||
const wss = new WebSocket.Server({ server: this.server });
|
||||
wss.on("connection", async ws => {
|
||||
const stream = new WebSocketStream(ws);
|
||||
const c = new ClientConnection(
|
||||
const c = new WebviewConnection(
|
||||
dataSource,
|
||||
stream,
|
||||
this,
|
||||
@@ -43,27 +50,42 @@ export class Server {
|
||||
await stream.onClosed;
|
||||
this.connections.delete(c);
|
||||
});
|
||||
|
||||
reaction(
|
||||
() => config.theme,
|
||||
theme => {
|
||||
for (const c of this.connections) {
|
||||
c.setTheme(theme);
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
public getIndexUrl(args: {
|
||||
public getWebviewPageUrl(args: {
|
||||
expression?: string;
|
||||
mode: "standalone" | "webViewIFrame";
|
||||
mode: "standalone" | "webviewIFrame";
|
||||
}): string {
|
||||
const port = process.env.USE_DEV_UI ? 8080 : this.port;
|
||||
const expr =
|
||||
args.expression !== undefined
|
||||
? `&expression=${encodeURIComponent(args.expression)}`
|
||||
: "";
|
||||
const inWebView =
|
||||
args.mode === "standalone" ? "" : "&mode=webViewIFrame";
|
||||
return `http://localhost:${port}/index.html?serverPort=${this.port}&serverSecret=${this.secret}${inWebView}${expr}`;
|
||||
const params: Record<string, string> = {
|
||||
serverPort: this.port.toString(),
|
||||
serverSecret: this.secret,
|
||||
mode: args.mode,
|
||||
theme: this.config.theme,
|
||||
};
|
||||
if (args.expression !== undefined) {
|
||||
params.expression = args.expression;
|
||||
}
|
||||
|
||||
return `http://localhost:${port}/index.html?${new URLSearchParams(
|
||||
params
|
||||
).toString()}`;
|
||||
}
|
||||
|
||||
public get publicPath(): string {
|
||||
return `http://localhost:${this.port}/`;
|
||||
}
|
||||
|
||||
public get mainBundleUrl(): string {
|
||||
public get webviewBundleUrl(): string {
|
||||
return `${this.publicPath}main.js`;
|
||||
}
|
||||
|
||||
@@ -9,6 +9,83 @@ import {
|
||||
DataExtractorId,
|
||||
} from "@hediet/debug-visualizer-data-extraction";
|
||||
|
||||
interface WebviewConfig {
|
||||
serverSecret: string;
|
||||
serverPort: number;
|
||||
publicPath: string;
|
||||
expression?: string;
|
||||
theme: "light" | "dark";
|
||||
}
|
||||
|
||||
export interface WebviewUrlParams {
|
||||
serverPort: string;
|
||||
serverSecret: string;
|
||||
mode: "standalone" | "webviewIFrame";
|
||||
expression: string;
|
||||
theme: "light" | "dark";
|
||||
}
|
||||
|
||||
export interface WindowWithWebviewData {
|
||||
webviewData?: WebviewConfig;
|
||||
}
|
||||
|
||||
export const webviewContract = contract({
|
||||
client: {
|
||||
updateState: notificationContract({
|
||||
params: types.type({
|
||||
newState: unchecked<DataExtractionState>(),
|
||||
}),
|
||||
}),
|
||||
updateLanguageId: notificationContract({
|
||||
params: types.type({
|
||||
languageId: types.union([types.null, types.string]),
|
||||
}),
|
||||
}),
|
||||
setExpression: requestContract({
|
||||
params: types.type({
|
||||
expression: types.string,
|
||||
}),
|
||||
}),
|
||||
setTheme: notificationContract({
|
||||
params: types.type({
|
||||
theme: types.union([
|
||||
types.literal("light"),
|
||||
types.literal("dark"),
|
||||
]),
|
||||
}),
|
||||
}),
|
||||
},
|
||||
server: {
|
||||
authenticate: requestContract({
|
||||
params: types.type({
|
||||
secret: types.string,
|
||||
}),
|
||||
}),
|
||||
setPreferredDataExtractor: notificationContract({
|
||||
params: types.type({
|
||||
dataExtractorId: unchecked<DataExtractorId>(),
|
||||
}),
|
||||
}),
|
||||
setExpression: notificationContract({
|
||||
params: types.type({
|
||||
newExpression: types.string,
|
||||
}),
|
||||
}),
|
||||
refresh: notificationContract({}),
|
||||
openInBrowser: notificationContract({}),
|
||||
|
||||
getCompletions: requestContract({
|
||||
params: types.type({
|
||||
text: types.string,
|
||||
column: types.number,
|
||||
}),
|
||||
result: types.type({
|
||||
completions: types.array(unchecked<CompletionItem>()),
|
||||
}),
|
||||
}),
|
||||
},
|
||||
});
|
||||
|
||||
function unchecked<T>(): types.Type<T, any, unknown> {
|
||||
return new types.Type<T, T, unknown>(
|
||||
"unchecked",
|
||||
@@ -92,52 +169,3 @@ export interface CompletionItem {
|
||||
*/
|
||||
length?: number;
|
||||
}
|
||||
|
||||
export const debugVisualizerUIContract = contract({
|
||||
client: {
|
||||
updateState: notificationContract({
|
||||
params: types.type({
|
||||
newState: unchecked<DataExtractionState>(),
|
||||
}),
|
||||
}),
|
||||
updateLanguageId: notificationContract({
|
||||
params: types.type({
|
||||
languageId: types.union([types.null, types.string]),
|
||||
}),
|
||||
}),
|
||||
setExpression: requestContract({
|
||||
params: types.type({
|
||||
expression: types.string,
|
||||
}),
|
||||
}),
|
||||
},
|
||||
server: {
|
||||
authenticate: requestContract({
|
||||
params: types.type({
|
||||
secret: types.string,
|
||||
}),
|
||||
}),
|
||||
setPreferredDataExtractor: notificationContract({
|
||||
params: types.type({
|
||||
dataExtractorId: unchecked<DataExtractorId>(),
|
||||
}),
|
||||
}),
|
||||
setExpression: notificationContract({
|
||||
params: types.type({
|
||||
newExpression: types.string,
|
||||
}),
|
||||
}),
|
||||
refresh: notificationContract({}),
|
||||
openInBrowser: notificationContract({}),
|
||||
|
||||
getCompletions: requestContract({
|
||||
params: types.type({
|
||||
text: types.string,
|
||||
column: types.number,
|
||||
}),
|
||||
result: types.type({
|
||||
completions: types.array(unchecked<CompletionItem>()),
|
||||
}),
|
||||
}),
|
||||
},
|
||||
});
|
||||
@@ -1,2 +0,0 @@
|
||||
require("ts-node").register();
|
||||
module.exports = require("./webpack.config.ts");
|
||||
@@ -21,6 +21,7 @@ module.exports = {
|
||||
"@hediet/debug-visualizer-data-extraction":
|
||||
"@hediet/debug-visualizer-data-extraction",
|
||||
"debug-visualizer-webview": "debug-visualizer-webview",
|
||||
fsevents: "require('fsevents')"
|
||||
},
|
||||
resolve: {
|
||||
extensions: [".ts", ".js"],
|
||||
|
||||
3813
extension/yarn.lock
11
package.json
@@ -3,16 +3,13 @@
|
||||
"workspaces": [
|
||||
"./data-extraction",
|
||||
"./extension",
|
||||
"./webview",
|
||||
"./visualization",
|
||||
"./visualization-playground"
|
||||
"./webview"
|
||||
],
|
||||
"scripts": {
|
||||
"build": "yarn build-data-extraction && yarn build-extension && yarn build-visualization && yarn build-ui",
|
||||
"build": "yarn build-data-extraction && yarn build-ui && yarn build-extension",
|
||||
"build-data-extraction": "yarn workspace @hediet/debug-visualizer-data-extraction build",
|
||||
"build-extension": "yarn workspace debug-visualizer build",
|
||||
"build-visualization": "yarn workspace @hediet/visualization build",
|
||||
"build-ui": "yarn workspace debug-visualizer-webview build"
|
||||
"build-ui": "yarn workspace debug-visualizer-webview build",
|
||||
"build-extension": "yarn workspace debug-visualizer build"
|
||||
},
|
||||
"devDependencies": {
|
||||
"prettier": "^1.19.1"
|
||||
|
||||
@@ -1,52 +0,0 @@
|
||||
{
|
||||
"name": "visualization-playground",
|
||||
"version": "0.0.1",
|
||||
"license": "GPL-3.0",
|
||||
"scripts": {
|
||||
"dev": "webpack-dev-server --hot --port 8090",
|
||||
"dev-fast": "webpack-dev-server --hot --port 8090 --fast",
|
||||
"build": "webpack",
|
||||
"pub": "gh-pages -d dist"
|
||||
},
|
||||
"dependencies": {
|
||||
"@blueprintjs/core": "^3.23.1",
|
||||
"@hediet/std": "^0.6.0",
|
||||
"blueprintjs": "^0.0.8",
|
||||
"classnames": "^2.2.6",
|
||||
"mobx": "^5.15.4",
|
||||
"mobx-react": "^6.1.8",
|
||||
"monaco-editor": "^0.20.0",
|
||||
"popper.js": "^1.16.1",
|
||||
"react": "^16.12.0",
|
||||
"react-dom": "^16.12.0",
|
||||
"react-measure": "^2.3.0",
|
||||
"@hediet/debug-visualizer-data-extraction": "*",
|
||||
"react-autosize-textarea": "^7.0.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"gh-pages": "^2.2.0",
|
||||
"@types/html-webpack-plugin": "^3.2.2",
|
||||
"@types/react-measure": "^2.0.6",
|
||||
"@types/webpack": "^4.41.6",
|
||||
"@types/classnames": "^2.2.9",
|
||||
"@types/react": "^16.9.22",
|
||||
"@types/react-dom": "^16.9.5",
|
||||
"clean-webpack-plugin": "^3.0.0",
|
||||
"css-loader": "^3.4.2",
|
||||
"file-loader": "^5.1.0",
|
||||
"fork-ts-checker-webpack-plugin": "^4.0.4",
|
||||
"html-webpack-plugin": "^3.2.0",
|
||||
"monaco-editor-webpack-plugin": "^1.9.0",
|
||||
"node-sass": "^4.13.1",
|
||||
"raw-loader": "^4.0.0",
|
||||
"sass": "^1.25.0",
|
||||
"sass-loader": "^8.0.2",
|
||||
"style-loader": "^1.1.3",
|
||||
"ts-loader": "^6.2.1",
|
||||
"ts-node": "^8.6.2",
|
||||
"typescript": "^3.8.2",
|
||||
"webpack": "^4.41.6",
|
||||
"webpack-cli": "^3.3.11",
|
||||
"webpack-dev-server": "^3.10.3"
|
||||
}
|
||||
}
|
||||
@@ -1,45 +0,0 @@
|
||||
import * as React from "react";
|
||||
import { Button, Spinner } from "@blueprintjs/core";
|
||||
import { observable, action } from "mobx";
|
||||
import { observer } from "mobx-react";
|
||||
import { Model } from "../model/Model";
|
||||
import classnames = require("classnames");
|
||||
import { knownVisualizations } from "@hediet/visualization";
|
||||
import { CommonDataType } from "@hediet/debug-visualizer-data-extraction";
|
||||
import { LightExpressionInput } from "./LightExpressionInput";
|
||||
|
||||
@observer
|
||||
export class GUI extends React.Component<{ model: Model }> {
|
||||
render() {
|
||||
const m = this.props.model;
|
||||
|
||||
return (
|
||||
<div
|
||||
className="component-GUI"
|
||||
style={{ display: "flex", flexDirection: "column" }}
|
||||
>
|
||||
<div className="part-header">
|
||||
<LightExpressionInput model={m.jsonExpression} />
|
||||
</div>
|
||||
<div
|
||||
className="part-visualization"
|
||||
style={{ minHeight: 0, flex: 1 }}
|
||||
>
|
||||
{this.renderVisualization()}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
renderVisualization() {
|
||||
const m = this.props.model;
|
||||
|
||||
if (!m.visualizations) {
|
||||
return <div>No/Invalid Data</div>;
|
||||
}
|
||||
if (!m.visualizations.visualization) {
|
||||
return <div>Data cannot be visualized</div>;
|
||||
}
|
||||
return m.visualizations.visualization.render();
|
||||
}
|
||||
}
|
||||