77 Commits

Author SHA1 Message Date
Henning Dieterichs
9fb1e8127a v2.3.0 2022-03-09 13:48:08 +01:00
Henning Dieterichs
a2a5d1b873 Update changelog for 2.2.4 2022-03-09 13:46:05 +01:00
Henning Dieterichs
e7e76ccfb9 Update dependencies. 2022-03-09 13:45:59 +01:00
Henning Dieterichs
e01c09eac5 Merge pull request #122 from fehrlich/master
python support
2021-08-19 11:06:32 +02:00
fehrlich
86e71ae534 remove replaceA
ll
2021-08-18 13:06:08 +02:00
fehrlich
e46cdef402 minor 2021-08-18 12:52:47 +02:00
fehrlich
2af8ef9a53 some cleanup 2021-08-18 12:20:11 +02:00
fehrlich
aa3412e6ac init python support 2021-08-18 11:32:21 +02:00
Henning Dieterichs
6543180591 Version 2.2.4. 2021-06-22 10:05:03 +02:00
Henning Dieterichs
cd88168211 Fixes issues with global. 2021-06-22 09:59:50 +02:00
Henning Dieterichs
683f1e1f76 Updates readme. 2021-06-18 21:12:30 +02:00
Henning Dieterichs
f2b4f620fe Version 2.2.1. 2021-06-18 21:08:06 +02:00
Henning Dieterichs
d22e5d4111 Version 2.2.0.
* Adds support for custom injected scripts with automatic reload
* Refactors extension
2021-06-18 20:26:51 +02:00
Henning Dieterichs
6ce5d666a3 * Updates visualization library.
* Adds support for more js debug adapters.
* Minor improvements.
2021-06-12 16:02:54 +02:00
Henning Dieterichs
6da44bd722 Adds diagram. 2021-04-29 10:40:51 +02:00
Henning Dieterichs
db1e5a8086 Adds fetch demo. 2020-10-07 17:13:50 +02:00
Henning Dieterichs
28d02ca4ab Fixes marked grid. 2020-10-07 17:13:50 +02:00
Henning Dieterichs
1e65dd1990 Updates common data types. 2020-10-07 17:13:50 +02:00
Henning Dieterichs
76fa30f71a v0.13.1 2020-10-07 17:13:50 +02:00
Henning Dieterichs
3c8ccd9a53 Merge pull request #90 from DanTup/add-dart-samples
Add a Dart demo script
2020-10-03 19:59:43 +02:00
Henning Dieterichs
9c70e06ee7 Merge pull request #84 from fwcd/master
Generate object graph visualization in a fully language-agnostic way in GenericEvaluator
2020-09-29 18:52:54 +02:00
Danny Tuppeny
8f83a1d96e Add link to readme 2020-09-22 11:16:55 +01:00
Danny Tuppeny
9cd290ab57 Add a Dart demo script 2020-09-22 11:11:31 +01:00
Henning Dieterichs
061ccdca33 Update opened-issues-triage.yml
Adds repo token back again.
2020-09-19 19:52:35 +02:00
Henning Dieterichs
12099d6152 Update opened-issues-triage.yml
Removes github auth token.
2020-09-19 19:47:14 +02:00
fwcd
b47efb7176 Reduce default node limit in GenericEvaluator to 50 2020-09-03 00:37:07 +02:00
fwcd
ee9f405033 Use breadth-first-search in GenericEvaluationEngine
A BFS generates a better graph if the graph is very deep since a DFS
would get stuck in the first branch (if the limit is low).
2020-09-02 22:41:55 +02:00
fwcd
cc272acb06 Remove redundant imports in GenericEvaluationEngine 2020-09-02 22:04:48 +02:00
fwcd
4a81e9910b Apply correct labels to graph edges in generic evaluator 2020-09-02 20:36:05 +02:00
fwcd
d8840bf33b Construct graph in generic evaluator explicitly 2020-09-02 20:22:10 +02:00
fwcd
e882593cea Limit amount of scanned objects in generic evaluator 2020-09-02 19:44:13 +02:00
fwcd
7ab4e0c4b5 Improve node labelling in generic evaluator 2020-09-02 19:40:58 +02:00
fwcd
4aca50f67a Use createGraph to represent object graphs constructed from variables 2020-09-02 19:29:33 +02:00
fwcd
6ef0ecb60f Add maxRecursionDepth in GenericEvaluationEngine 2020-09-02 18:07:22 +02:00
fwcd
0ce1b02b2c Handle cyclic references in GenericEvaluationEngine 2020-09-02 17:36:17 +02:00
fwcd
abfa44b4f9 Add new object construction in GenericEvaluationEngine 2020-09-02 17:28:08 +02:00
fwcd
fa1432ffc8 Add EnhancedDebugSession.getVariables 2020-09-02 17:10:04 +02:00
fwcd
fc142926b8 Return 'variablesReference' in EnhancedDebugSession.evaluate 2020-09-02 17:05:51 +02:00
Henning Dieterichs
d68d3b4076 Merge pull request #80 from suzil/master
Add Python grid demo (insertion sort)
2020-08-31 23:47:57 +02:00
Susannah Klaneček
f8c3b73d4f Add Python grid demo (insertion sort) 2020-08-31 17:33:50 -04:00
Henning Dieterichs
a8e1a5a36c Merge pull request #75 from Phrohdoh/fix-rust-demo
Fix the Rust demo
2020-08-29 19:22:17 +02:00
Taryn Hill
d421fa1497 chore: update Rust demo to work with schema change 2020-08-29 11:19:37 -05:00
Taryn Hill
118d02e797 chore: add Visual Studio Code LLDB config for the Rust demo 2020-08-29 11:19:37 -05:00
Henning Dieterichs
c420ff7f6f Improves docs. 2020-08-24 17:06:00 +02:00
Henning Dieterichs
7aaebf0332 Selects expression enclosed in backticks when nothing is selected and shift+F1 is pressed. 2020-08-24 17:05:54 +02:00
Henning Dieterichs
2f44c2d6f8 Fixes font csp issues. 2020-08-24 17:05:31 +02:00
Henning Dieterichs
f5f6f5d56d Improves js demos. 2020-08-24 17:05:03 +02:00
Henning Dieterichs
3e64d3b05c Adds setting to js demos. 2020-08-24 17:04:55 +02:00
Henning Dieterichs
0429eac23e Adds table with type extractor. 2020-08-24 17:04:45 +02:00
Henning Dieterichs
dc9b07a4b6 v2.0.6 2020-08-23 18:38:32 +02:00
Henning Dieterichs
2b6dbf52e9 Updates version. 2020-08-23 16:54:05 +02:00
Henning Dieterichs
e4606a4ae3 Updates bundled visualizations. 2020-08-23 16:53:56 +02:00
Henning Dieterichs
508062768c v2.0.2 2020-08-23 14:41:46 +02:00
Henning Dieterichs
d7dc1f27e0 Allows to load webworkers from data/blob. 2020-08-23 14:41:38 +02:00
Henning Dieterichs
4525c226c8 Adds asData helper function. 2020-08-23 14:41:12 +02:00
Henning Dieterichs
c09dadb199 Updates readme. 2020-08-22 19:57:48 +02:00
Henning Dieterichs
c88e98cc24 Updates readme. 2020-08-22 19:54:37 +02:00
Henning Dieterichs
17be602906 Updates docs. 2020-08-22 19:47:15 +02:00
Henning Dieterichs
453e0ae5a8 Updates changelog. 2020-08-22 16:07:13 +02:00
Henning Dieterichs
08d97c2a82 Fixes logo reference. 2020-08-22 16:07:04 +02:00
Henning Dieterichs
e487dead95 Downgrades required vscode version. 2020-08-22 16:06:49 +02:00
Henning Dieterichs
8ce8e7dad8 Fixes typescript ast data extractor. 2020-08-22 16:06:36 +02:00
Henning Dieterichs
41747c5dd0 Fixes types. 2020-08-22 15:46:44 +02:00
Henning Dieterichs
1dd0349483 Adds types for bundled visualizations & improves data extractor types. 2020-08-22 15:40:24 +02:00
Henning Dieterichs
62415f1775 Fixes contract import. 2020-08-16 22:02:16 +02:00
Henning Dieterichs
b23b90c8bf Fixes dev port. 2020-08-16 11:03:05 +02:00
hediet
24aa6857e5 Code improvements. 2020-08-14 16:10:45 +02:00
hediet
7ba2428819 New logo, updates changelog, some code improvements. 2020-08-14 13:45:43 +02:00
hediet
f09e345908 Migrates to new visualization framework. 2020-08-13 21:55:02 +02:00
Henning Dieterichs
c56f21d8ab Converts backslashs to slashs. 2020-07-24 19:24:13 +02:00
Henning Dieterichs
5964c1c503 Adds support for pwa-node and adds a poll option. 2020-07-20 19:40:12 +02:00
Henning Dieterichs
e5707a0f2e fsevents fix. 2020-05-26 22:12:03 +02:00
Henning Dieterichs
d0f1a0144e Removes check script. 2020-05-26 22:05:49 +02:00
Henning Dieterichs
83b93b1369 Adds ci script. 2020-05-26 21:57:50 +02:00
Henning Dieterichs
39de71521b Adds automatic triage github action. 2020-05-26 21:57:24 +02:00
Henning Dieterichs
51a42eb497 adds check script. 2020-05-26 21:57:12 +02:00
Henning Dieterichs
ac90389aa1 Adds funding options. 2020-05-26 21:57:04 +02:00
163 changed files with 11361 additions and 18697 deletions

1
.github/FUNDING.yml vendored Normal file
View File

@@ -0,0 +1 @@
github: hediet

23
.github/workflows/ci.yml vendored Normal file
View 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

View 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 }}

7
.gitignore vendored
View File

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

16
.vscode/launch.json vendored
View File

@@ -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"
},
]
}

View File

@@ -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"
}

View File

@@ -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

View File

@@ -1,5 +1,7 @@
# VS Code Debug Visualizer
[![](https://img.shields.io/static/v1?style=social&label=Sponsor&message=%E2%9D%A4&logo=GitHub&color&link=%3Curl%3E)](https://github.com/sponsors/hediet)
[![](https://img.shields.io/static/v1?style=social&label=Donate&message=%E2%9D%A4&logo=Paypal&color&link=%3Curl%3E)](https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=ZP5F38L4C88UY&source=url)
[![](https://img.shields.io/twitter/follow/hediet_dev.svg?style=social)](https://twitter.com/intent/follow?screen_name=hediet_dev)
See [README.md](./extension/README.md) for the readme of the extension.

View File

@@ -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"
}
}

View File

@@ -1,122 +1,312 @@
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;
cells?: {
values?: string[][];
};
header?: {
values?: string[];
};
domain?: {
x?: number[],
y?: number[],
};
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"
| "table";
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;
};

View File

@@ -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;
}

View File

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

View File

@@ -1,20 +1,19 @@
import {
ExtractedData,
VisualizationData,
DataExtractionResult,
} from "../../DataExtractionResult";
import { LoadDataExtractorsFn } from "./LoadDataExtractorsFn";
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 +23,8 @@ export interface DataExtractorApi {
getData(
valueFn: () => unknown,
evalFn: <T>(expression: string) => T,
preferredDataExtractorId: string | undefined
preferredDataExtractorId: string | undefined,
variablesInScope: Record<string, unknown>
): JSONString<DataResult>;
/**
@@ -32,6 +32,9 @@ export interface DataExtractorApi {
* @preferExisting if `true`, existing extractors with the same id are not overwritten.
*/
registerDefaultExtractors(preferExisting?: boolean): void;
registerDataExtractorsSource(id: string, fn: LoadDataExtractorsFn): void;
unregisterDataExtractorsSource(id: string): void;
}
export type DataResult =
@@ -46,23 +49,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 +73,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 +92,5 @@ export interface DataExtraction<TData extends ExtractedData> {
* A user friendly name of this extraction.
*/
name: string;
extractData(): TData;
extractData(): VisualizationData;
}

View File

@@ -5,34 +5,31 @@ import {
DataExtractor,
DataExtraction,
ExtractionCollector,
DataExtractorContext,
} from "./DataExtractorApi";
import { ExtractedData, DataExtractorInfo } from "../../DataExtractionResult";
import { DataExtractorInfo } from "../../DataExtractionResult";
import { registerDefaultExtractors } from "./default-extractors";
import { LoadDataExtractorsFn } from "./LoadDataExtractorsFn";
import * as helpers from "../helpers";
/**
* @internal
*/
export class DataExtractorApiImpl implements DataExtractorApi {
public static lastEvalFn: (<T>(expression: string) => T) | undefined;
public static lastContext: DataExtractorContext | undefined = undefined;
private readonly extractors = new Map<
string,
DataExtractor<ExtractedData>
>();
private readonly extractors = new Map<string, DataExtractor>();
private readonly extractorSources = new Map<string, LoadDataExtractorsFn>();
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 +38,38 @@ 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.lastEvalFn = evalFn;
const value = valueFn();
DataExtractorApiImpl.lastEvalFn = undefined;
const context: DataExtractorContext = {
evalFn,
variablesInScope,
};
for (const e of this.extractors.values()) {
e.getExtractions(value, extractionCollector, { evalFn });
DataExtractorApiImpl.lastContext = context;
const value = valueFn();
const extractors = new Array<DataExtractor>();
for (const fn of this.extractorSources.values()) {
fn((extractor) => {
extractors.push(extractor);
}, helpers);
}
for (const e of [...this.extractors.values(), ...extractors]) {
e.getExtractions(value, extractionCollector, context);
}
DataExtractorApiImpl.lastContext = undefined;
extractions.sort((a, b) => b.priority - a.priority);
let usedExtraction = extractions[0];
if (!usedExtraction) {
@@ -65,16 +78,14 @@ export class DataExtractorApiImpl implements DataExtractorApi {
if (preferredDataExtractorId) {
const preferred = extractions.find(
e => e.id === preferredDataExtractorId
(e) => e.id === preferredDataExtractorId
);
if (preferred) {
usedExtraction = preferred;
}
}
function mapExtractor(
e: DataExtraction<ExtractedData>
): DataExtractorInfo {
function mapExtractor(e: DataExtraction): DataExtractorInfo {
return {
id: e.id as any,
name: e.name,
@@ -97,4 +108,15 @@ export class DataExtractorApiImpl implements DataExtractorApi {
// TODO consider preferExisting
registerDefaultExtractors(this);
}
public registerDataExtractorsSource(
id: string,
fn: LoadDataExtractorsFn
): void {
this.extractorSources.set(id, fn);
}
public unregisterDataExtractorsSource(id: string): void {
this.extractorSources.delete(id);
}
}

View File

@@ -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;

View File

@@ -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;
}

View File

@@ -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);
},
});
}

View File

@@ -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 })) }],
}),
});
}
}

View File

@@ -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 {

View File

@@ -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;
},
}),
});
}
}

View File

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

View File

@@ -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 })),
});
},
});
}
}

View File

@@ -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,
};
});
},
});
}

View File

@@ -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();
}
}

View File

@@ -1,12 +1,14 @@
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";
/**
* The default data extractors should be registered by VS Code automatically.
@@ -18,11 +20,13 @@ 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(),
]) {
api.registerExtractor(item);
}

View File

@@ -1,3 +1,4 @@
export * from "./DataExtractorApi";
export * from "./injection";
export { DataExtractorApiImpl } from "./DataExtractorApiImpl";
export * from "./LoadDataExtractorsFn";

View File

@@ -3,6 +3,7 @@ import { DataExtractorApi } from "./DataExtractorApi";
import { DataExtractorApiImpl } from "./DataExtractorApiImpl";
import * as helpers from "../helpers";
import * as globalHelpers from "../global-helpers";
import { getGlobal } from "../../getGlobal";
/**
* Returns standalone JS code representing an expression that initializes the data extraction API.
@@ -20,22 +21,19 @@ 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()})())`;
}
const apiKey = "@hediet/data-extractor-api/v2";
const apiKey = "@hediet/data-extractor-api/v3";
export function getDataExtractorApi(): DataExtractorApi {
installHelpers();
const globalObj =
typeof window === "object" ? (window as any) : (global as any);
const globalObj = getGlobal();
if (!globalObj[apiKey]) {
globalObj[apiKey] = new DataExtractorApiImpl();
}
@@ -46,29 +44,28 @@ 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;
export const ApiHasNotBeenInitializedCode = "EgH0cybXij1jYUozyakO" as const;
/**
* @internal
*/
function selfContainedGetInitializedDataExtractorApi(): DataExtractorApi {
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];
if (!api) {
const code: typeof ApiHasNotBeenInitializedCode =
"EgH0cybXij1jYUozyakO";
throw new Error(
`Data Extractor API has not been initialized. Code: ${code}`
);
}
return api;
}
/**
* @internal
*/
function selfContainedGetInitializedDataExtractorApi(): DataExtractorApi {
const globalObj =
typeof window === "object" ? (window as any) : (global as any);
const key: typeof apiKey = "@hediet/data-extractor-api/v3";
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}`
);
}
return api;
}
export function installHelpers(): void {
const globalObj =
typeof window === "object" ? (window as any) : (global as any);
const globalObj = getGlobal();
// `hediet` as prefix to avoid name collision (I own `hediet.de`).
globalObj["hedietDbgVis"] = { ...helpers, ...globalHelpers };
}

View File

@@ -0,0 +1,6 @@
import { KnownVisualizationData } from "../../CommonDataTypes";
import { VisualizationData } from "../../DataExtractionResult";
export function asData(data: KnownVisualizationData): VisualizationData {
return data;
}

View File

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

View File

@@ -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 });
}
}
}

View File

@@ -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 {

View File

@@ -0,0 +1,44 @@
import { DataExtractorApiImpl } from "../api";
export function find(predicate: (obj: unknown) => boolean): unknown {
const processed = new Set();
if (!DataExtractorApiImpl.lastContext) {
throw new Error("No data extractor context!");
}
const values = Object.values(
DataExtractorApiImpl.lastContext.variablesInScope
);
const queue = [
...values.map((v) => {
try {
return v();
} catch (e) {
return undefined;
}
}),
];
let i = 10000;
while (i > 0) {
i--;
const top = queue.shift();
processed.add(top);
if (predicate(top)) {
return top;
}
if (typeof top === "object" && top) {
for (const val of Object.values(top)) {
if (!processed.has(val)) {
processed.add(val);
queue.push(val);
}
}
}
}
return undefined;
}

View File

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

View File

@@ -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),
};
}

View File

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

View File

@@ -0,0 +1,3 @@
export function expect<T>(data: T): T {
return data;
}

View 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
View 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;
}
}

View File

@@ -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>&#xD;
</projects>
<buildSpec>
<buildCommand>
<name>org.eclipse.jdt.core.javabuilder</name>
<arguments>&#xD;
</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
View File

@@ -0,0 +1,8 @@
{
"editor.minimap.enabled": false,
"git.enabled": false,
"typescript.tsc.autoDetect": "off",
"debugVisualizer.js.customScriptPaths": [
"${workspaceFolder}/custom-visualizer.js",
]
}

View 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
);
}

View File

@@ -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": {

View 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;

View File

@@ -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;
}

View File

@@ -0,0 +1,7 @@
const fetch = require("node-fetch");
fetch("https://jsonplaceholder.typicode.com/users")
.then((response) => response.json())
.then((json) => {
debugger;
});

View File

@@ -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;
}

View File

@@ -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]];

View File

@@ -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);
}
}

View File

@@ -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"

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

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

View File

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

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

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

View File

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

View File

@@ -0,0 +1,38 @@
"""Python demo for sorting using VS Code Debug Visualizer."""
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 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)

View File

@@ -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
View File

@@ -0,0 +1,8 @@
{
"debugVisualizer.debugAdapterConfigurations": {
"lldb": {
"expressionTemplate": "${expr}",
"context": "watch"
}
}
}

View File

@@ -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");
}

View 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="&lt;mxfile host=&quot;localhost&quot; modified=&quot;2020-05-18T21:20:25.100Z&quot; agent=&quot;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&quot; etag=&quot;wIym9edGWidWU1rSQKSs&quot; version=&quot;13.0.9&quot;&gt;&lt;diagram id=&quot;6hGFLwfOUW9BJ-s0fimq&quot; name=&quot;Page-1&quot;&gt;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/&lt;/diagram&gt;&lt;/mxfile&gt;">
<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

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 MiB

BIN
docs/custom-script-map.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 37 KiB

BIN
docs/custom-scripts.gif Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.5 MiB

After

Width:  |  Height:  |  Size: 4.2 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 19 KiB

After

Width:  |  Height:  |  Size: 17 KiB

View File

@@ -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

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.6 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 MiB

View File

@@ -1,2 +1 @@
node_modules
ui/node_modules
./node_modules

View File

@@ -1,5 +1,33 @@
# Change Log
## 2.3.0
- [Improves support for python](https://github.com/hediet/vscode-debug-visualizer/pull/122)
## 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.

View File

@@ -3,218 +3,77 @@
[![](https://img.shields.io/twitter/follow/hediet_dev.svg?style=social)](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.
![](../docs/demo.gif)
## [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.
![](../docs/doubly-linked-list-reverse-demo.gif)
```ts
interface Graph {
kind: { graph: true };
nodes: NodeGraphData[];
edges: EdgeGraphData[];
}
### Random Walk
interface NodeGraphData {
id: string;
label?: string;
color?: string;
shape?: "ellipse" | "box";
}
![](../docs/visualization-plotly-random-walk.gif)
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`.
![](../docs/ast-demo.gif)
![](../docs/visualization-graphviz.png)
![](../docs/visualization-visjs.png)
### 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.
```
![](../docs/visualization-plotly-random-walk.png)
### 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;
}
```
![](../docs/visualization-tree.png)
### 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.
![](../docs/visualization-ast.png)
### 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;
}[];
}
```
![](../docs/visualization-grid.gif)
### 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.
![](../docs/table-demo.gif)
## 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
@@ -266,25 +112,71 @@ Creates Grid visualization data for an array.
This extension provides these configuration options:
- `debugVisualizer.debugAdapterConfigurations`
### **debugVisualizer.debugAdapterConfigurations**
Allows to set expression templates for specific debug adapter types.
Example:
Allows to set expression templates for specific debug adapter types.
Example:
```json
"debugVisualizer.debugAdapterConfigurations": {
"lldb": {
"expressionTemplate": "script to_json(\"${expr}\")",
"context": "repl"
}
```json
"debugVisualizer.debugAdapterConfigurations": {
"lldb": {
"expressionTemplate": "script to_json(\"${expr}\")",
"context": "repl"
}
```
}
```
Configurations here overwrite the built-in support for the corresponding debug adapter type.
Configurations here overwrite the built-in support for the corresponding debug adapter type.
* `debugVisualizer.useChromeKioskMode`
### **debugVisualizer.useChromeKioskMode**
Specifies whether to pop out Debug Visualization Views with Chrome in Kiosk Mode. Uses the default browser otherwise or if Chrome is not found. Defaults to `true`.
Specifies whether to pop out Debug Visualization Views with Chrome in Kiosk Mode. Uses the default browser otherwise or if Chrome is not found. Defaults to `true`.
### **debugVisualizer.js.customScriptPaths**
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,
})),
});
},
});
},
});
};
```
![](../docs/custom-script-map.png)
Use the `helpers` object to access some magic functions.
`helpers.find(val => ...)` can be used to find an object that is reachable by any variable in scope.
# See Also

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.8 KiB

View File

@@ -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.3.0",
"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"
}
}

View File

@@ -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]();
});
}
}

View File

@@ -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;
}
}

View File

@@ -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,
});
}
}

View File

@@ -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;
}

View File

@@ -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";
}
}

View File

@@ -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;
}
}
}

View File

@@ -1,6 +0,0 @@
export * from "./EvaluationEngine";
export { ComposedEvaluationEngine } from "./ComposedEvaluationEngine";
export { JsEvaluationEngine } from "./JsEvaluationEngine";
export { GenericEvaluationEngine } from "./GenericEvaluationEngine";
export { ConfiguredEvaluationEngine } from "./ConfiguredEvaluationEngine";

View File

@@ -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);
}
}

View File

@@ -1,2 +0,0 @@
export * from "./EvaluationWatchServiceImpl";
export * from "./EvaluationWatchService";

View File

@@ -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;
}
}

View File

@@ -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,
});
}
}

View File

@@ -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
)) || []
);
}
}

View File

@@ -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";
}
}

View File

@@ -0,0 +1,430 @@
import {
DataExtractionResult,
DataResult,
getExpressionForDataExtractorApi,
getExpressionToInitializeDataExtractorApi,
ApiHasNotBeenInitializedCode,
} from "@hediet/debug-visualizer-data-extraction";
import { hotClass, registerUpdateReconciler } from "@hediet/node-reload";
import { existsSync, readFileSync, watch } from "fs";
import { observable, reaction } from "mobx";
import { window, workspace } from "vscode";
import { Config } from "../Config";
import { DebuggerViewProxy } from "../proxies/DebuggerViewProxy";
import { DebugSessionProxy } from "../proxies/DebugSessionProxy";
import { FormattedMessage } from "../webviewContract";
import {
DebugSessionVisualizationSupport,
GetVisualizationDataArgs,
VisualizationBackend,
VisualizationBackendBase,
} from "./VisualizationBackend";
import { Disposable } from "@hediet/std/disposable";
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 initializePromise: 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(
args: GetVisualizationDataArgs
): Promise<
| { kind: "data"; result: DataExtractionResult }
| { kind: "error"; message: FormattedMessage }
> {
const result = await this._getVisualizationData(args);
if (result.kind === "not-initialized") {
await this.initializeApiSafe();
const result2 = await this._getVisualizationData(args);
if (result2.kind !== "not-initialized") {
return result2;
} else {
return {
kind: "error",
message: "Could not initialize API",
};
}
}
return result;
}
private async _getVisualizationData({
expression,
preferredExtractorId,
}: GetVisualizationDataArgs): Promise<
| { kind: "data"; result: DataExtractionResult }
| { kind: "error"; message: FormattedMessage }
| { kind: "not-initialized" }
> {
try {
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 && s.name !== "Global")
.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 > 50) {
variableNames.length = 50;
}
}
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) {
if (
typeof error.message === "string" &&
error.message.indexOf(ApiHasNotBeenInitializedCode) !== -1
) {
return {
kind: "not-initialized",
};
}
return {
kind: "error",
message: error.message,
};
}
}
private initializeApiSafe(): Promise<void> {
if (!this.initializePromise) {
this.initializePromise = this.initializeApi().finally(
() => (this.initializePromise = undefined)
);
}
return this.initializePromise;
}
private async initializeApi(): Promise<void> {
// 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(),
});
await this.initializeCustomScripts();
}
private customScripts: undefined | CustomScripts;
private async initializeCustomScripts(): Promise<void> {
this.dispose.untrack(this.customScripts);
this.customScripts?.dispose();
this.customScripts = this.dispose.track(new CustomScripts(this.debugSession, this.config, () => this.onChangeEmitter.emit()));
}
}
class CustomScripts {
public readonly dispose = Disposable.fn();
@observable
private filesVersionId: number = 0;
private readonly fileChangeDebouncedScheduler = new DebouncedScheduler(100);
constructor(private readonly debugSession: DebugSessionProxy, private readonly config: Config, private readonly changeHandler: () => void) {
this.init();
}
private init(): void {
const watchers = new Map<string, WatchedFile>();
this.dispose.track({
dispose: () => {
for (const w of watchers.values()) {
w.dispose();
}
},
});
this.dispose.track({
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,
() => {
this.fileChangeDebouncedScheduler.run(
() => {
this.filesVersionId++;
}
);
}
);
watchers.set(fsPath, entry);
}
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.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.changeHandler();
}
},
{
fireImmediately: true,
}
),
});
}
}
interface Result {
error?: { message: string };
}
class WatchedFile {
public static nextId = 0;
private readonly watcher = watch(
this.fsPath,
{ encoding: "utf-8" },
this.changeHandler
);
public readonly id = WatchedFile.nextId++;
public lastContent: string | undefined = undefined;
constructor(
public readonly fsPath: string,
private readonly debugSession: DebugSessionProxy,
private readonly changeHandler: () => void
) {}
public dispose(): void {
this.watcher.close();
}
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);
${getExpressionForDataExtractorApi()}.registerDataExtractorsSource("${
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 = `${getExpressionForDataExtractorApi()}.unregisterDataExtractorsSource("${
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();
}
}

View File

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

View File

@@ -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,
});
}
}

View File

@@ -0,0 +1,7 @@
export * from "./VisualizationBackend";
export { ComposedVisualizationSupport } from "./ComposedVisualizationSupport";
export { JsEvaluationEngine } from "./JsVisualizationSupport";
export { PyEvaluationEngine } from "./PyVisualizationSupport";
export { GenericVisualizationSupport } from "./GenericVisualizationSupport";
export { ConfigurableVisualizationSupport } from "./ConfigurableVisualizationSupport";

View File

@@ -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: {

View File

@@ -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;

View File

@@ -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;
}
}

View File

@@ -0,0 +1,2 @@
export * from "./VisualizationWatchModelImpl";
export * from "./VisualizationWatchModel";

View File

@@ -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;
}
}
}

View File

@@ -12,39 +12,57 @@ 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";
PyEvaluationEngine,
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 PyEvaluationEngine(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 +90,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 +129,3 @@ export class Extension {
);
}
}
export function activate(context: ExtensionContext) {
context.subscriptions.push(
hotRequireExportedFn(module, Extension, Extension => new Extension())
);
}
export function deactivate() {}

View 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;
}

View 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;
}
}
},
};
},
}),
]);
}
}

View 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;
}
}

View 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();
});
});

View File

@@ -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>

Some files were not shown because too many files have changed in this diff Show More