diff --git a/.vscode/launch.json b/.vscode/launch.json index ed285e7..a8d4c54 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -2,7 +2,7 @@ "version": "0.2.0", "configurations": [ { - "name": "Run Extension", + "name": "Run Extension (Build UI)", "type": "extensionHost", "request": "launch", "runtimeExecutable": "${execPath}", @@ -11,7 +11,8 @@ "${workspaceFolder}\\example-project" ], "env": { - "HOT_RELOAD": "true" + "HOT_RELOAD": "true", + "USE_DEV_UI": "" }, "outFiles": ["${workspaceFolder}/extension/dist/**/*.js"], "preLaunchTask": "npm: dev - extension" diff --git a/data-extraction/package.json b/data-extraction/package.json index 1455d95..a452653 100644 --- a/data-extraction/package.json +++ b/data-extraction/package.json @@ -1,20 +1,29 @@ { "name": "@hediet/debug-visualizer-data-extraction", - "private": true, - "version": "0.2.0", + "description": "A library that helps implementing data extractors for the Debug Visualizer VS Code extension.", + "version": "0.9.0", "main": "dist/index.js", "types": "dist/index.d.ts", "author": { "name": "Henning Dieterichs", "email": "henning.dieterichs@live.de" }, + "repository": { + "type": "git", + "url": "https://github.com/hediet/vscode-debug-visualizer.git" + }, "license": "MIT", "files": [ "dist", "src" ], + "publishConfig": { + "access": "public", + "registry": "https://registry.npmjs.org/" + }, "scripts": { - "dev": "tsc --watch" + "dev": "tsc --watch", + "build": "tsc" }, "dependencies": {} } diff --git a/data-extraction/src/CommonDataTypes.ts b/data-extraction/src/CommonDataTypes.ts index 386fcb6..0ba9c52 100644 --- a/data-extraction/src/CommonDataTypes.ts +++ b/data-extraction/src/CommonDataTypes.ts @@ -19,9 +19,9 @@ export type CommonDataType = | CommonDataTypes.Svg | CommonDataTypes.Html | CommonDataTypes.DotGraph - | CommonDataTypes.TreeNodeData - | CommonDataTypes.AstData - | CommonDataTypes.GraphData; + | CommonDataTypes.Tree + | CommonDataTypes.Ast + | CommonDataTypes.Graph; export module CommonDataTypes { export interface Text { @@ -31,34 +31,31 @@ export module CommonDataTypes { fileName?: string; } - export interface GraphData { + export interface Graph { kind: { graph: true }; - nodes: { id: string; label: string }[]; - edges: { from: string; to: string; label: string }[]; + nodes: NodeGraphData[]; + edges: EdgeGraphData[]; } export interface Svg extends Text { kind: { text: true; svg: true }; - text: string; } export interface Html extends Text { kind: { text: true; html: true }; - text: string; } export interface DotGraph extends Text { kind: { text: true; dotGraph: true }; - text: string; } - export interface TreeNodeData { + export interface Tree { kind: { tree: true }; root: TreeNode; } - export interface AstData - extends TreeNodeData<{ + export interface Ast + extends Tree<{ position: number; length: number; }>, @@ -68,11 +65,26 @@ export module CommonDataTypes { } export interface TreeNode { - id: string | undefined; name: string; - value: string | undefined; - emphasizedValue: string | undefined; children: TreeNode[]; data: TExtraData; - isMarked: boolean; + id?: string; + value?: string; + emphasizedValue?: string; + isMarked?: boolean; +} + +export interface NodeGraphData { + id: string; + label: string; + color?: string; +} + +export interface EdgeGraphData { + from: string; + to: string; + label: string; + id?: string; + color?: string; + weight?: number; } diff --git a/data-extraction/src/js/DataExtractorApi.ts b/data-extraction/src/js/DataExtractorApi.ts index 83a623d..60a3054 100644 --- a/data-extraction/src/js/DataExtractorApi.ts +++ b/data-extraction/src/js/DataExtractorApi.ts @@ -37,7 +37,7 @@ declare const window: any; export function selfContainedInitDataExtractorApi(): boolean { const obj = typeof window === "object" ? (window as any) : (global as any); - const key = "@hediet/data-extractor"; + const key = "@hediet/data-extractor/v1"; const prefix = key + "::"; let api: DataExtractorApi | undefined = obj[key]; @@ -135,7 +135,7 @@ export const ApiHasNotBeenInitializedCode = "EgH0cybXij1jYUozyakO" as const; export function selfContainedGetInitializedDataExtractorApi(): DataExtractorApi { const obj = typeof window === "object" ? (window as any) : (global as any); - const key = "@hediet/data-extractor"; + const key = "@hediet/data-extractor/v1"; let api: DataExtractorApi | undefined = obj[key]; if (!api) { const code: typeof ApiHasNotBeenInitializedCode = diff --git a/data-extraction/src/js/Extractors/TypeScriptDataExtractors.ts b/data-extraction/src/js/Extractors/TypeScriptDataExtractors.ts index 4e4045d..c44bd48 100644 --- a/data-extraction/src/js/Extractors/TypeScriptDataExtractors.ts +++ b/data-extraction/src/js/Extractors/TypeScriptDataExtractors.ts @@ -4,12 +4,12 @@ import { CommonDataTypes } from "../../CommonDataTypes"; // This class is self contained and can be injected into both nodejs and browser environments. export class TypeScriptAstDataExtractor - implements DataExtractor { + implements DataExtractor { readonly id = "TypeScriptAst"; getExtractions( data: unknown, - collector: ExtractionCollector, + collector: ExtractionCollector, evalFn: (expression: string) => TEval ): void { if (!data) { @@ -63,7 +63,7 @@ export class TypeScriptAstDataExtractor memberName: string, marked: Set, emphasizedValueFn: (node: ts.Node) => string | undefined - ): CommonDataTypes.AstData["root"] { + ): CommonDataTypes.Ast["root"] { const name = tsApi.SyntaxKind[node.kind]; const children = getChildren(node) .map((childNode, idx) => { diff --git a/data-extraction/src/js/createGraph.ts b/data-extraction/src/js/createGraph.ts new file mode 100644 index 0000000..8a824fb --- /dev/null +++ b/data-extraction/src/js/createGraph.ts @@ -0,0 +1,105 @@ +import { + CommonDataTypes, + EdgeGraphData, + NodeGraphData, +} from "../CommonDataTypes"; + +export function createGraphFromPointers( + roots: Record, + infoSelector: ( + item: T + ) => { + id?: string | number; + edges: ({ to: T } & Omit)[]; + } & Omit +): CommonDataTypes.Graph { + const marker = {}; + + interface Pointer { + marker: {}; + name: string; + value: T | null | undefined; + } + + const items = Object.entries(roots).map(([name, value]) => ({ + marker, + name, + value, + })); + + const includeLabelInName = false; + + return createGraph(items, item => { + if ("marker" in item && item["marker"] === marker) { + return { + id: "label____" + item.name, + color: "orange", + label: item.name, + edges: [{ to: item.value!, color: "orange", label: "" }].filter( + t => !!t.to + ), + }; + } else { + return infoSelector(item as T); + } + }); +} + +export function createGraph( + roots: T[], + infoSelector: ( + item: T + ) => { + id?: string | number; + edges: ({ to: T } & Omit)[]; + } & Omit +): CommonDataTypes.Graph { + const r: CommonDataTypes.Graph = { + kind: { + graph: true, + }, + nodes: [], + edges: [], + }; + let idCounter = 1; + const ids = new Map(); + function getId(item: T): string { + const _id = infoSelector(item).id; + if (_id !== undefined) { + return "" + _id; + } + + let id = ids.get(item); + if (!id) { + id = (idCounter++).toString(); + ids.set(item, id); + } + return id; + } + + const queue = new Array(...roots); + const processed = new Set(); + + while (queue.length > 0) { + const item = queue.shift()!; + if (processed.has(item)) { + continue; + } + processed.add(item); + const nodeInfo = infoSelector(item); + const fromId = getId(item); + r.nodes.push({ ...nodeInfo, id: fromId, ["edges" as any]: undefined }); + for (const e of nodeInfo.edges) { + const toId = getId(e.to); + r.edges.push({ + ...e, + from: fromId, + to: toId, + }); + if (!processed.has(e.to)) { + queue.push(e.to); + } + } + } + return r; +} diff --git a/data-extraction/src/js/getClosure.ts b/data-extraction/src/js/getClosure.ts deleted file mode 100644 index c0bb322..0000000 --- a/data-extraction/src/js/getClosure.ts +++ /dev/null @@ -1,80 +0,0 @@ -import { CommonDataTypes } from "../CommonDataTypes"; - -export function getClosureObj( - roots: Record, - edgeSelector: (item: T) => { item: T; edgeLabel: string }[], - labelSelector: (item: T) => string, - idSelector?: (item: T) => string | number -): CommonDataTypes.GraphData { - const items = Object.entries(roots).map(([k, v]) => v); - return getClosure( - items.filter(i => !!i) as T[], - edgeSelector, - item => { - const names = new Array(); - for (const [key, val] of Object.entries(roots)) { - if (val === item) { - names.push(key); - } - } - const base = labelSelector(item); - return names.length === 0 ? base : `${names.join(", ")}: ${base}`; - }, - idSelector - ); -} - -export function getClosure( - roots: T[], - edgeSelector: (item: T) => { item: T; edgeLabel: string }[], - labelSelector: (item: T) => string, - idSelector?: (item: T) => string | number -): CommonDataTypes.GraphData { - const r: CommonDataTypes.GraphData = { - kind: { - graph: true, - }, - nodes: [], - edges: [], - }; - let idCounter = 1; - const ids = new Map(); - function getId(item: T): string { - if (idSelector) { - return "" + idSelector(item); - } - - let id = ids.get(item); - if (!id) { - id = (idCounter++).toString(); - ids.set(item, id); - } - return id; - } - - const queue = new Array(...roots); - const processed = new Set(); - - while (queue.length > 0) { - const item = queue.shift()!; - if (processed.has(item)) { - continue; - } - processed.add(item); - const edges = edgeSelector(item); - const fromId = getId(item); - r.nodes.push({ id: fromId, label: labelSelector(item) }); - for (const e of edges) { - const toId = getId(e.item); - r.edges.push({ - from: fromId, - to: toId, - label: e.edgeLabel, - }); - if (!processed.has(e.item)) { - queue.push(e.item); - } - } - } - return r; -} diff --git a/data-extraction/src/js/index.ts b/data-extraction/src/js/index.ts index 1d011bd..aa3cecb 100644 --- a/data-extraction/src/js/index.ts +++ b/data-extraction/src/js/index.ts @@ -1,4 +1,4 @@ export * from "./Extractors"; export * from "./DataExtractor"; export * from "./DataExtractorApi"; -export * from "./getClosure"; +export * from "./createGraph"; diff --git a/docs/ast-viewer.png b/docs/ast-viewer.png deleted file mode 100644 index 5807be8..0000000 Binary files a/docs/ast-viewer.png and /dev/null differ diff --git a/docs/demo.gif b/docs/demo.gif deleted file mode 100644 index 1b74c14..0000000 Binary files a/docs/demo.gif and /dev/null differ diff --git a/docs/doubly-linked-list-reverse-demo.gif b/docs/doubly-linked-list-reverse-demo.gif new file mode 100644 index 0000000..1e2635a Binary files /dev/null and b/docs/doubly-linked-list-reverse-demo.gif differ diff --git a/docs/graph-viewer.png b/docs/graph-viewer.png deleted file mode 100644 index b811e3a..0000000 Binary files a/docs/graph-viewer.png and /dev/null differ diff --git a/docs/linked-list-demo.gif b/docs/linked-list-demo.gif deleted file mode 100644 index da35b6e..0000000 Binary files a/docs/linked-list-demo.gif and /dev/null differ diff --git a/docs/tree-viewer.png b/docs/tree-viewer.png deleted file mode 100644 index 2c738bc..0000000 Binary files a/docs/tree-viewer.png and /dev/null differ diff --git a/docs/visualization-ast.gif b/docs/visualization-ast.gif new file mode 100644 index 0000000..6af95dc Binary files /dev/null and b/docs/visualization-ast.gif differ diff --git a/docs/visualization-ast.png b/docs/visualization-ast.png new file mode 100644 index 0000000..14e55e1 Binary files /dev/null and b/docs/visualization-ast.png differ diff --git a/docs/visualization-graphviz.png b/docs/visualization-graphviz.png new file mode 100644 index 0000000..00d7261 Binary files /dev/null and b/docs/visualization-graphviz.png differ diff --git a/docs/visualization-tree.png b/docs/visualization-tree.png new file mode 100644 index 0000000..6abdf43 Binary files /dev/null and b/docs/visualization-tree.png differ diff --git a/docs/visualization-visjs.png b/docs/visualization-visjs.png new file mode 100644 index 0000000..53520a9 Binary files /dev/null and b/docs/visualization-visjs.png differ diff --git a/example-project/.vscode/launch.json b/example-project/.vscode/launch.json index de328a3..54a555c 100644 --- a/example-project/.vscode/launch.json +++ b/example-project/.vscode/launch.json @@ -3,22 +3,5 @@ // Hover to view descriptions of existing attributes. // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 "version": "0.2.0", - "configurations": [ - { - "type": "node", - "request": "launch", - "name": "Launch Index", - "program": "${workspaceFolder}\\dist\\index.js", - "outFiles": ["${workspaceFolder}/dist/**/*.js"], - "preLaunchTask": "npm: dev" - }, - { - "type": "node", - "request": "launch", - "name": "Launch Double Linked List Demo", - "program": "${workspaceFolder}\\dist\\DoubleLinkedList.js", - "outFiles": ["${workspaceFolder}/dist/**/*.js"], - "preLaunchTask": "npm: dev" - } - ] + "configurations": [] } diff --git a/example-project/.vscode/settings.json b/example-project/.vscode/settings.json new file mode 100644 index 0000000..7f39cf5 --- /dev/null +++ b/example-project/.vscode/settings.json @@ -0,0 +1,4 @@ +{ + "debugVisualizer.useChromeKioskMode": false, + "editor.minimap.enabled": false +} diff --git a/example-project/src/DoubleLinkedList.ts b/example-project/src/DoubleLinkedList.ts deleted file mode 100644 index ce240b3..0000000 --- a/example-project/src/DoubleLinkedList.ts +++ /dev/null @@ -1,72 +0,0 @@ -import { - enableHotReload, - registerUpdateReconciler, - getReloadCount, - hotClass, -} from "@hediet/node-reload"; -enableHotReload(); -registerUpdateReconciler(module); - -import { - registerAll, - CommonDataTypes, - getClosureObj, -} from "@hediet/debug-visualizer-data-extraction"; - -registerAll(); - -setTimeout(() => { - new Main().run(); -}, 0); - -@hotClass(module) -class Main { - run() { - let myValue: DoubleLinkedList | undefined = new DoubleLinkedList("1"); - - const first = myValue; - myValue.setNext(new DoubleLinkedList("2")); - myValue.next!.setNext(new DoubleLinkedList("3")); - myValue.next!.next!.setNext(new DoubleLinkedList("4")); - - const dbgLast = myValue.next!.next!.next!; - - let last: DoubleLinkedList | undefined = undefined; - while (myValue) { - myValue.prev = myValue.next; - myValue.next = last; - last = myValue; - myValue = myValue.prev; - } - myValue = first; - } - - getDebugVisualization( - items: Record - ): CommonDataTypes.GraphData { - return getClosureObj( - items, - r => - [ - { item: r.next!, edgeLabel: "next" }, - { item: r.prev!, edgeLabel: "prev" }, - ].filter(r => !!r.item), - item => item.name, - item => item.id - ); - } -} - -let id = 0; -class DoubleLinkedList { - public readonly id = (id++).toString(); - constructor(public name: string) {} - - next: DoubleLinkedList | undefined; - prev: DoubleLinkedList | undefined; - - public setNext(val: DoubleLinkedList): void { - val.prev = this; - this.next = val; - } -} diff --git a/example-project/src/demo_doubly-linked-list.ts b/example-project/src/demo_doubly-linked-list.ts new file mode 100644 index 0000000..13145d7 --- /dev/null +++ b/example-project/src/demo_doubly-linked-list.ts @@ -0,0 +1,59 @@ +import { + registerAll, + CommonDataTypes, + createGraphFromPointers, +} from "@hediet/debug-visualizer-data-extraction"; + +registerAll(); + +setTimeout(() => { + new Main().run(); +}, 0); + +class Main { + run() { + const list = new DoublyLinkedList("1"); + list.setNext(new DoublyLinkedList("2")); + list.next!.setNext(new DoublyLinkedList("3")); + list.next!.next!.setNext(new DoublyLinkedList("4")); + + const visualize = () => + createGraphFromPointers({ list, last, cur }, i => ({ + id: i.id, + label: i.name, + color: finished.has(i) ? "lime" : undefined, + edges: [ + { to: i.next!, label: "next" }, + { to: i.prev!, label: "prev", color: "lightgray" }, + ].filter(r => !!r.to), + })); + + const finished = new Set(); + var cur: DoublyLinkedList | undefined = list; + // Reverses `list`. Finished nodes have correct pointers, + // their next node is also finished. + var last: DoublyLinkedList | undefined = undefined; + while (cur) { + cur.prev = cur.next; + cur.next = last; + finished.add(cur); + last = cur; + cur = cur.prev; + } + console.log("finished"); + } +} + +let id = 0; +class DoublyLinkedList { + public readonly id = (id++).toString(); + constructor(public name: string) {} + + next: DoublyLinkedList | undefined; + prev: DoublyLinkedList | undefined; + + public setNext(val: DoublyLinkedList): void { + val.prev = this; + this.next = val; + } +} diff --git a/example-project/src/index.ts b/example-project/src/demo_typescript.ts similarity index 76% rename from example-project/src/index.ts rename to example-project/src/demo_typescript.ts index 8524f9c..e7c98ea 100644 --- a/example-project/src/index.ts +++ b/example-project/src/demo_typescript.ts @@ -1,24 +1,13 @@ import * as ts from "typescript"; -import { - enableHotReload, - registerUpdateReconciler, - getReloadCount, - hotClass, -} from "@hediet/node-reload"; -enableHotReload(); - -import { - registerAll, - CommonDataTypes, - getClosure, -} from "@hediet/debug-visualizer-data-extraction"; +import { registerAll } from "@hediet/debug-visualizer-data-extraction"; import { MockLanguageServiceHost } from "./MockLanguageServiceHost"; registerAll(); -registerUpdateReconciler(module); +setTimeout(() => { + new Main().run(); +}, 0); -@hotClass(module) class Main { run() { const mainFile = { @@ -49,10 +38,12 @@ class Test1 { const c = prog.getTypeChecker(); let myValue = undefined; - const sf = prog.getSourceFiles()[0]; - myValue = sf.getText(); + const sourceFileAst = prog.getSourceFiles()[0]; + myValue = sourceFileAst.getText(); + console.log("myValue is the source code of the AST"); + myValue = { - sf, + sf: sourceFileAst, fn: (n: ts.Node) => { try { const t = c.getTypeAtLocation(n); @@ -62,8 +53,10 @@ class Test1 { } }, }; + console.log("myValue is AST, annotated with type information"); + myValue = { - sf, + sf: sourceFileAst, fn: (n: ts.Node) => { try { const t = c.getSymbolAtLocation(n); @@ -73,12 +66,16 @@ class Test1 { } }, }; + console.log("myValue is AST, annotated with symbol information"); for (const ident of identifiers) { - const s = c.getSymbolAtLocation(ident); myValue = ident; + console.log("myValue is an identifier"); } + } +} +/* myValue = { kind: { text: true, svg: true }, text: ` @@ -89,10 +86,4 @@ class Test1 { /> `, - }; - } -} - -if (getReloadCount(module) === 0) { - new Main().run(); -} + };*/ diff --git a/extension/CHANGELOG.md b/extension/CHANGELOG.md new file mode 100644 index 0000000..e9a4642 --- /dev/null +++ b/extension/CHANGELOG.md @@ -0,0 +1,5 @@ +# Change Log + +## 0.9.0 + +- Initial release diff --git a/extension/README.md b/extension/README.md index 02fb4da..5cf6664 100644 --- a/extension/README.md +++ b/extension/README.md @@ -1,62 +1,171 @@ -# Debug Visualizer (Preview) +# Debug Visualizer [![](https://img.shields.io/twitter/follow/hediet_dev.svg?style=social)](https://twitter.com/intent/follow?screen_name=hediet_dev) -Provides infrastructure to visualize values while debugging. +A VS Code extension for visualizing data structures during debugging. -This extension is still in development. - -![](docs/demo.gif) +![](../docs/doubly-linked-list-reverse-demo.gif) ## Usage After installing this extension, use the command `Open a new Debug Visualizer View` to open a new visualizer view. -There you can enter an expression that is visualized when debugging your application. +In this view you can enter an expression that is visualized when debugging your application, e.g. + +```ts +{ kind: { graph: true }, nodes: [ { id: "1", label: "1" }, { id: "2", label: "2" } ], edges: [{ from: "1", to: "2", label: "edge" }]} +``` ## Integrated Visualizers -Visualizers present preprocessed data. -They are mostly React-Components and live in the webview. +Visualizers present data extracted by a _Data Extractor_. +Visualizers are (mostly) React components and live in the webview of the extension. -These visualizers are integrated: +### Tree Visualization -- Tree Viewer - ![](./docs/tree-viewer.png) +The tree visualizer renders data that matches the `Tree` interface. -- Svg Viewer -- Dot Graph Viewer -- VisJs Graph Viewer - ![](./docs/linked-list-demo.png) +```ts +interface Tree { + kind: { tree: true }; + root: TreeNode; +} +interface TreeNode { + id: string | undefined; + name: string; + value: string | undefined; + emphasizedValue: string | undefined; + children: TreeNode[]; + data: TExtraData; + isMarked: boolean; +} +``` -- Graph Viewer - ![](./docs/graph-viewer.png) +![](../docs/visualization-tree.png) -- Text Viewer - - Plain Text - - Monaco +### AST Visualization + +The AST visualizer renders data that matches the `Ast` interface. + +```ts +interface Ast + extends Tree<{ + position: number; + length: number; + }>, + Text { + kind: { text: true; tree: true; ast: true }; +} +``` + +In addition to the tree view, the text representation is shown. + +![](../docs/visualization-ast.png) + +### Text Visualization + +The text visualizer renders data that matches the `Text` interface. + +```ts +interface Text { + kind: { text: true }; + text: string; + mimeType?: string; + fileName?: string; +} +``` + +The `mimeType` and the file extension of `fileName` are used for syntax highlighting. + +### SVG Visualization + +The SVG visualizer renders data that matches the `Svg` interface. +The actual SVG data must be stored in `text`. + +```ts +interface Svg extends Text { + kind: { text: true; svg: true }; +} +``` + +### Dot Graph Visualization + +The Graphviz Dot visualizer renders data that matches the `DotGraph` interface. + +```ts +interface DotGraph extends Text { + kind: { text: true; dotGraph: true }; +} +``` + +`Viz.js` (Graphviz) is used for rendering. + +### Graph Visualization + +The Graphviz and vis.js visualizers render data that matches the `Graph` interface. + +```ts +interface Graph { + kind: { graph: true }; + nodes: NodeGraphData[]; + edges: EdgeGraphData[]; +} + +interface NodeGraphData { + id: string; + label: string; + color?: string; +} + +interface EdgeGraphData { + from: string; + to: string; + label: string; + id?: string; + color?: string; + weight?: number; +} +``` + +The graphviz visualizer uses the SVG viewer to render the SVG created by `viz.js`. + +![](../docs/visualization-graphviz.png) +![](../docs/visualization-visjs.png) ## Integrated Data Extractors Data extractors convert arbitrary values into visualizable data. -They live in the debugee. +They live in the debugee. The following data extractors are injected automatically by this extension. +Custom data extractors can be registered too. -These visualizers are integrated: +### ToString -- ToString Text Extractor -- TypeScript Ast to Tree Data - - Direct Visualization of `ts.Node`s - - Visualization of `Record` and `[ts.Node]`. If the record contains a key `fn`, its value is displayed for each node. +Just calls `.toString()` on values and treats the result as text. -![](./docs/ast-viewer.png) +### TypeScript AST -- As Is Data Extractor. Allows to return data directly. +- Direct Visualization of `ts.Node`s +- Visualization of `Record` and `[ts.Node]`. If the record contains a key `fn`, its value is displayed for each node. + +### As Is Data Extractor + +Treats the data as direct input to the visualizer. + +### Use Method 'getDebugVisualization' + +Calls `.getDebugVisualization()` on values and treats the result as direct input to the visualizer. ## Limitations Currently, only JavaScript (and thus TypeScript) values can be visualized and only a few visualizations are supported. +The architecture is solid enough to support other languages in the future. # See Also -This extension works very well together with my library [`@hediet/node-reload`](https://github.com/hediet/node-reload): +This extension works very well together with my library [`@hediet/node-reload`](https://github.com/hediet/node-reload). +Together, they provide an interactive typescript playground. -![](./docs/demo-hot.gif) +![](../docs/demo-hot.gif) + +``` + +``` diff --git a/extension/docs/logo.png b/extension/docs/logo.png new file mode 100644 index 0000000..2d9f7d9 Binary files /dev/null and b/extension/docs/logo.png differ diff --git a/extension/package.json b/extension/package.json index dde1064..6a886c7 100644 --- a/extension/package.json +++ b/extension/package.json @@ -2,7 +2,8 @@ "name": "debug-visualizer", "displayName": "Debug Visualizer", "description": "An extension that visualizes values while debugging.", - "version": "0.2.1", + "icon": "docs/logo.png", + "version": "0.9.0", "license": "MIT", "engines": { "vscode": "^1.35.0" @@ -20,7 +21,6 @@ "categories": [ "Other" ], - "preview": true, "activationEvents": [ "onCommand:vscode-debug-visualizer.new-visualizer", "onDebug", @@ -46,6 +46,7 @@ } }, "scripts": { + "pub": "vsce publish --baseImagesUrl https://github.com/hediet/vscode-debug-visualizer/raw/master/extension", "vscode:prepublish": "yarn build", "build": "webpack --mode production", "dev": "tsc -watch -p ./" @@ -59,7 +60,8 @@ "express": "^4.17.1", "open": "^6.4.0", "serve-static": "^1.14.1", - "ws": "^7.1.1" + "ws": "^7.1.1", + "crypto-random-string": "^3.1.0" }, "devDependencies": { "@types/express": "^4.17.0", diff --git a/extension/src/ConnectionHandler.ts b/extension/src/ConnectionHandler.ts index 732a28e..3ea81e6 100644 --- a/extension/src/ConnectionHandler.ts +++ b/extension/src/ConnectionHandler.ts @@ -1,6 +1,6 @@ import { Disposable } from "@hediet/std/disposable"; import { debugVisualizerUIContract } from "@hediet/debug-visualizer-vscode-shared"; -import { ConsoleRpcLogger } from "@hediet/typed-json-rpc"; +import { ConsoleRpcLogger, RequestHandlingError } from "@hediet/typed-json-rpc"; import { EvaluationWatcher } from "./DataSource/DataSource"; import { WebSocketStream } from "@hediet/typed-json-rpc-websocket"; import { observable, autorun } from "mobx"; @@ -19,8 +19,17 @@ export class ConnectionHandler { sources: Sources, stream: WebSocketStream, server: Server, - config: Config + config: Config, + serverSecret: string ) { + let authenticated = false; + + function throwIfNotAuthenticated() { + if (!authenticated) { + throw new RequestHandlingError("Not authenticated"); + } + } + const { client, channel, @@ -28,12 +37,23 @@ export class ConnectionHandler { stream, new ConsoleRpcLogger(), { + authenticate: async ({ secret }, { newErr }) => { + if (secret !== serverSecret) { + return newErr({ errorMessage: "Invalid Secret" }); + } else { + authenticated = true; + } + }, refresh: async () => { + throwIfNotAuthenticated(); + if (this.watcher) { this.watcher.refresh(); } }, setExpression: async ({ newExpression }) => { + throwIfNotAuthenticated(); + let oldPreferredDataExtractor: EvaluationWatcher["preferredDataExtractor"]; if (this.watcher) { oldPreferredDataExtractor = this.watcher @@ -50,6 +70,8 @@ export class ConnectionHandler { ); }, openInBrowser: async ({}) => { + throwIfNotAuthenticated(); + let opened = false; if (config.useChromeKioskMode()) { opened = await launchChrome(server.indexUrl); @@ -59,11 +81,15 @@ export class ConnectionHandler { } }, setPreferredDataExtractor: async ({ dataExtractorId }) => { + throwIfNotAuthenticated(); + if (this.watcher) { this.watcher.setPreferredDataExtractor(dataExtractorId); } }, getCompletions: async ({ text, column }) => { + throwIfNotAuthenticated(); + const completions = await sources.jsSource.getCompletions( text, column diff --git a/extension/src/Server.ts b/extension/src/Server.ts index de9421f..bab4b26 100644 --- a/extension/src/Server.ts +++ b/extension/src/Server.ts @@ -8,9 +8,11 @@ import * as express from "express"; import * as http from "http"; import * as serveStatic from "serve-static"; import { Config } from "./Config"; +import cryptoRandomString = require("crypto-random-string"); export class Server { private server: http.Server; + public readonly secret = cryptoRandomString({ length: 20 }); constructor(sources: Sources, config: Config) { const app = express(); @@ -22,13 +24,13 @@ export class Server { const wss = new WebSocket.Server({ server: this.server }); wss.on("connection", ws => { const stream = new WebSocketStream(ws); - new ConnectionHandler(sources, stream, this, config); + new ConnectionHandler(sources, stream, this, config, this.secret); }); } public get indexUrl(): string { const port = process.env.USE_DEV_UI ? 8080 : this.port; - return `http://localhost:${port}/index.html?serverPort=${this.port}`; + return `http://localhost:${port}/index.html?serverPort=${this.port}&serverSecret=${this.secret}`; } public get mainBundleUrl(): string { diff --git a/extension/src/WebViews.ts b/extension/src/WebViews.ts index 89f9592..a1138e5 100644 --- a/extension/src/WebViews.ts +++ b/extension/src/WebViews.ts @@ -72,6 +72,7 @@ export function getHtml(server: Server) {