├── .github ├── FUNDING.yml └── workflows │ ├── ci.yml │ └── opened-issues-triage.yml ├── .gitignore ├── .prettierrc.json ├── .vscode ├── launch.json ├── settings.json └── tasks.json ├── CONTRIBUTING.md ├── LICENSE.md ├── README.md ├── data-extraction ├── CHANGELOG.md ├── LICENSE.md ├── README.md ├── package.json ├── src │ ├── CommonDataTypes.ts │ ├── DataExtractionResult.ts │ ├── getGlobal.ts │ ├── index.ts │ ├── js │ │ ├── api │ │ │ ├── DataExtractorApi.ts │ │ │ ├── DataExtractorApiImpl.ts │ │ │ ├── LoadDataExtractorsFn.ts │ │ │ ├── default-extractors │ │ │ │ ├── AsIsDataExtractor.ts │ │ │ │ ├── GetDebugVisualizationDataExtractor.ts │ │ │ │ ├── GridExtractor.ts │ │ │ │ ├── MarkedGridExtractor.ts │ │ │ │ ├── ObjectGraphExtractor.ts │ │ │ │ ├── PlotlyDataExtractor.ts │ │ │ │ ├── StringDiffExtractor.ts │ │ │ │ ├── StringRangeExtractor.ts │ │ │ │ ├── TableExtractor.ts │ │ │ │ ├── ToStringExtractor.ts │ │ │ │ ├── TypeScriptDataExtractors.ts │ │ │ │ ├── index.ts │ │ │ │ └── registerDefaultDataExtractors.ts │ │ │ ├── index.ts │ │ │ └── injection.ts │ │ ├── global-helpers.ts │ │ ├── helpers │ │ │ ├── asData.ts │ │ │ ├── cache.ts │ │ │ ├── createGraph.ts │ │ │ ├── createGraphFromPointers.ts │ │ │ ├── find.ts │ │ │ ├── index.ts │ │ │ ├── markedGrid.ts │ │ │ └── tryEval.ts │ │ └── index.ts │ └── util.ts ├── test │ ├── main.test.ts │ └── tsconfig.json ├── tsconfig.json ├── webpack.config.ts └── yarn.lock ├── demos ├── cpp │ ├── .gitignore │ ├── .vscode │ │ ├── launch.json │ │ └── tasks.json │ └── main.cpp ├── csharp │ ├── .gitignore │ ├── .vscode │ │ ├── launch.json │ │ ├── settings.json │ │ └── tasks.json │ ├── ExtractedData.cs │ ├── Program.cs │ └── demo.csproj ├── dart │ ├── debug_visualizers.dart │ └── demo.dart ├── golang │ ├── .vscode │ │ └── launch.json │ ├── demo.go │ └── go.mod ├── java │ ├── .classpath │ ├── .gitignore │ ├── .project │ ├── .settings │ │ └── org.eclipse.jdt.core.prefs │ ├── .vscode │ │ ├── launch.json │ │ └── settings.json │ ├── lib │ │ ├── jackson-annotations-2.9.8.jar │ │ ├── jackson-core-2.9.8.jar │ │ └── jackson-databind-2.9.8.jar │ └── src │ │ └── app │ │ ├── App.java │ │ ├── ExtractedData.java │ │ ├── GraphData.java │ │ └── TextData.java ├── js │ ├── .vscode │ │ ├── settings.json │ │ └── tasks.json │ ├── README.md │ ├── custom-visualizer.js │ ├── package.json │ ├── src │ │ ├── MockLanguageServiceHost.ts │ │ ├── demo_address_book.ts │ │ ├── demo_custom-data-extractor.ts │ │ ├── demo_doubly-linked-list.ts │ │ ├── demo_fetch.js │ │ ├── demo_random-walks.ts │ │ ├── demo_singly-linked-list.js │ │ ├── demo_sorting.ts │ │ ├── demo_stack-frames.js │ │ ├── demo_typescript-asts.ts │ │ └── playground.ts │ ├── tsconfig.json │ └── yarn.lock ├── nim │ ├── .vscode │ │ ├── launch.json │ │ └── tasks.json │ ├── LICENSE │ ├── main.nim │ └── nim.cfg ├── php │ ├── .vscode │ │ ├── launch.json │ │ └── settings.json │ └── demo.php ├── python │ ├── .vscode │ │ └── launch.json │ ├── Person.py │ ├── debugvisualizer.py │ ├── demo.py │ ├── graph.py │ └── insertion_sort.py ├── ruby │ ├── README.md │ └── src │ │ ├── demo_custom_visualizer.rb │ │ └── demo_random_walks.rb ├── rust │ ├── .gitignore │ ├── .vscode │ │ ├── launch.json │ │ └── settings.json │ ├── Cargo.toml │ └── src │ │ └── main.rs └── swift │ ├── .gitignore │ ├── .vscode │ ├── launch.json │ ├── settings.json │ └── tasks.json │ ├── Package.swift │ ├── Sources │ └── swiftDemo │ │ └── main.swift │ ├── Tests │ ├── LinuxMain.swift │ └── swiftDemoTests │ │ ├── XCTestManifests.swift │ │ └── swiftDemoTests.swift │ └── main.swift ├── docs ├── architecture.drawio.svg ├── ast-demo.gif ├── custom-script-map.png ├── custom-scripts.gif ├── demo-hot.gif ├── demo.gif ├── doubly-linked-list-reverse-demo.gif ├── exported │ └── main │ │ └── Main.png ├── input-autocompletion.png ├── main.plantuml ├── multiline-expression.png ├── singly-linked-list.gif ├── table-demo.gif ├── visualization-ast.gif ├── visualization-ast.png ├── visualization-graphviz.png ├── visualization-grid.gif ├── visualization-plotly-random-walk.gif ├── visualization-plotly-random-walk.png ├── visualization-tree.png └── visualization-visjs.png ├── extension ├── .vscodeignore ├── CHANGELOG.md ├── LICENSE.md ├── README.md ├── docs │ └── logo.drawio.png ├── package.json ├── src │ ├── Config.ts │ ├── VisualizationBackend │ │ ├── ComposedVisualizationSupport.ts │ │ ├── ConfigurableVisualizationSupport.ts │ │ ├── DispatchingVisualizationBackend.ts │ │ ├── GenericVisualizationSupport.ts │ │ ├── JsVisualizationSupport.ts │ │ ├── PyVisualizationSupport.ts │ │ ├── RbVisualizationSupport.ts │ │ ├── VisualizationBackend.ts │ │ ├── index.ts │ │ └── parseEvaluationResultFromGenericDebugAdapter.ts │ ├── VisualizationWatchModel │ │ ├── VisualizationWatchModel.ts │ │ ├── VisualizationWatchModelImpl.ts │ │ └── index.ts │ ├── extension.ts │ ├── proxies │ │ ├── DebugSessionProxy.ts │ │ ├── DebuggerProxy.ts │ │ └── DebuggerViewProxy.ts │ ├── types.d.ts │ ├── utils │ │ ├── DebouncedRunner.ts │ │ ├── IncrementalMap.ts │ │ └── VsCodeSettings.ts │ ├── webview │ │ ├── InternalWebviewManager.ts │ │ ├── WebviewConnection.ts │ │ └── WebviewServer.ts │ └── webviewContract.ts ├── tsconfig.json └── webpack.config.ts ├── package.json ├── tslint.json ├── webview ├── index.js ├── package.json ├── src │ ├── components │ │ ├── App.tsx │ │ ├── ExpressionInput.tsx │ │ ├── GUI.tsx │ │ ├── NoData.tsx │ │ ├── Visualizer.tsx │ │ └── VisualizerHeaderDetails.tsx │ ├── hotComponent.tsx │ ├── index.tsx │ ├── model │ │ ├── Model.ts │ │ ├── MonacoBridge.ts │ │ ├── VsCodeApi.ts │ │ └── lib.es5.d.ts.txt │ ├── style.scss │ ├── vscode-dark.scss │ └── vscode-light.scss ├── tsconfig.json ├── webpack.config.ts └── yarn.lock └── yarn.lock /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | github: hediet 2 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | on: 2 | push: 3 | branches: 4 | - master 5 | pull_request: 6 | 7 | jobs: 8 | build: 9 | strategy: 10 | matrix: 11 | os: [macos-latest, ubuntu-latest, windows-latest] 12 | runs-on: ${{ matrix.os }} 13 | steps: 14 | - name: Checkout 15 | uses: actions/checkout@v2 16 | with: 17 | submodules: true 18 | - name: Install Node.js 19 | uses: actions/setup-node@v1 20 | with: 21 | node-version: 20.x 22 | - run: yarn install 23 | - run: yarn build 24 | -------------------------------------------------------------------------------- /.github/workflows/opened-issues-triage.yml: -------------------------------------------------------------------------------- 1 | name: Move new issues into Triage 2 | 3 | on: 4 | issues: 5 | types: [opened] 6 | 7 | jobs: 8 | automate-project-columns: 9 | runs-on: ubuntu-latest 10 | steps: 11 | - uses: alex-page/github-project-automation-plus@v0.2.3 12 | with: 13 | project: Backlog 14 | column: Triage 15 | repo-token: ${{ secrets.GH_TOKEN }} 16 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | 8 | # Runtime data 9 | pids 10 | *.pid 11 | *.seed 12 | *.pid.lock 13 | 14 | # Directory for instrumented libs generated by jscoverage/JSCover 15 | lib-cov 16 | 17 | # Coverage directory used by tools like istanbul 18 | coverage 19 | 20 | # nyc test coverage 21 | .nyc_output 22 | 23 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) 24 | .grunt 25 | 26 | # Bower dependency directory (https://bower.io/) 27 | bower_components 28 | 29 | # node-waf configuration 30 | .lock-wscript 31 | 32 | # Compiled binary addons (https://nodejs.org/api/addons.html) 33 | build/Release 34 | 35 | # Dependency directories 36 | node_modules/ 37 | jspm_packages/ 38 | 39 | # TypeScript v1 declaration files 40 | typings/ 41 | 42 | # Optional npm cache directory 43 | .npm 44 | 45 | # Optional eslint cache 46 | .eslintcache 47 | 48 | # Optional REPL history 49 | .node_repl_history 50 | 51 | # Output of 'npm pack' 52 | *.tgz 53 | 54 | # Yarn Integrity file 55 | .yarn-integrity 56 | 57 | # dotenv environment variables file 58 | .env 59 | .env.test 60 | 61 | # parcel-bundler cache (https://parceljs.org/) 62 | .cache 63 | 64 | # next.js build output 65 | .next 66 | 67 | # nuxt.js build output 68 | .nuxt 69 | 70 | # vuepress build output 71 | .vuepress/dist 72 | 73 | # Serverless directories 74 | .serverless/ 75 | 76 | # FuseBox cache 77 | .fusebox/ 78 | 79 | # DynamoDB Local files 80 | .dynamodb/ 81 | 82 | dist/ 83 | out/ 84 | 85 | # Python byte-compiled / optimized / DLL files 86 | __pycache__/ 87 | *.py[cod] 88 | *$py.class 89 | 90 | *.vsix -------------------------------------------------------------------------------- /.prettierrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "trailingComma": "es5", 3 | "tabWidth": 4, 4 | "semi": true, 5 | "useTabs": true, 6 | "printWidth": 120 7 | } 8 | -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "0.2.0", 3 | "configurations": [ 4 | { 5 | "type": "chrome", 6 | "request": "launch", 7 | "name": "Launch Chrome", 8 | "url": "http://localhost:8090", 9 | "webRoot": "${workspaceFolder}/visualization-playground" 10 | }, 11 | { 12 | "name": "Run Extension (Hot Reload)", 13 | "type": "extensionHost", 14 | "request": "launch", 15 | "runtimeExecutable": "${execPath}", 16 | "args": [ 17 | "--extensionDevelopmentPath=${workspaceFolder}/extension", 18 | "${workspaceFolder}/demos/php" 19 | ], 20 | "env": { 21 | "HOT_RELOAD": "true", 22 | "USE_DEV_UI": "" 23 | }, 24 | "outFiles": ["${workspaceFolder}/extension/dist/**/*.js"], 25 | "preLaunchTask": "npm: dev - extension" 26 | }, 27 | { 28 | "name": "Run Extension (Hot Reload + Dev UI)", 29 | "type": "extensionHost", 30 | "request": "launch", 31 | "runtimeExecutable": "${execPath}", 32 | "args": [ 33 | "--extensionDevelopmentPath=${workspaceFolder}/extension", 34 | "${workspaceFolder}/demos/php" 35 | ], 36 | "env": { 37 | "HOT_RELOAD": "true", 38 | "USE_DEV_UI": "true" 39 | }, 40 | "outFiles": ["${workspaceFolder}/extension/dist/**/*.js"], 41 | "preLaunchTask": "npm: dev - extension" 42 | }, 43 | { 44 | "name": "Run Extension", 45 | "type": "extensionHost", 46 | "request": "launch", 47 | "runtimeExecutable": "${execPath}", 48 | "args": [ 49 | "--extensionDevelopmentPath=${workspaceFolder}/extension", 50 | "${workspaceFolder}/demos/js" 51 | ], 52 | "env": { 53 | "HOT_RELOAD": "", 54 | "USE_DEV_UI": "" 55 | }, 56 | "outFiles": ["${workspaceFolder}/extension/dist/**/*.js"] 57 | }, 58 | { 59 | "name": "Run Extension (Dev UI)", 60 | "type": "extensionHost", 61 | "request": "launch", 62 | "runtimeExecutable": "${execPath}", 63 | "args": [ 64 | "--extensionDevelopmentPath=${workspaceFolder}/extension", 65 | "${workspaceFolder}/demos/js" 66 | ], 67 | "env": { 68 | "HOT_RELOAD": "", 69 | "USE_DEV_UI": "true" 70 | }, 71 | "outFiles": ["${workspaceFolder}/extension/dist/**/*.js"], 72 | "preLaunchTask": "npm: dev - extension" 73 | }, 74 | { 75 | "type": "dart", 76 | "request": "launch", 77 | "name": "Run Dart samples", 78 | "program": "demos/dart/demo.dart" 79 | } 80 | ] 81 | } 82 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "search.exclude": { 3 | "extension/out": true 4 | }, 5 | "plantuml.diagramsRoot": "docs", 6 | "plantuml.exportFormat": "png", 7 | "plantuml.exportOutDir": "docs/exported", 8 | "typescript.updateImportsOnFileMove.enabled": "prompt", 9 | "mochaExplorer.files": "./data-extraction/test/**/*.test.ts", 10 | "mochaExplorer.require": ["ts-node/register", "source-map-support/register"], 11 | "tasksStatusbar.taskLabelFilter": "dev", 12 | "editor.formatOnSave": true, 13 | "typescript.tsdk": "node_modules\\typescript\\lib", 14 | 15 | "editor.defaultFormatter": "esbenp.prettier-vscode", 16 | "prettier.prettierPath": "node_modules/prettier", 17 | "prettier.printWidth": 120, 18 | "[javascript]": { 19 | "editor.defaultFormatter": "esbenp.prettier-vscode" 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /.vscode/tasks.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "2.0.0", 3 | "tasks": [ 4 | { 5 | "type": "npm", 6 | "label": "npm: dev - extension", 7 | "script": "dev", 8 | "problemMatcher": "$tsc-watch", 9 | "isBackground": true, 10 | "presentation": { 11 | "reveal": "never" 12 | }, 13 | "path": "extension/", 14 | "group": { 15 | "kind": "build", 16 | "isDefault": true 17 | } 18 | } 19 | ] 20 | } 21 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing 2 | 3 | This document extends the [readme](./extension/README.md) of the extension with implementation details. 4 | 5 | ## Build Instructions 6 | 7 | - Clone the repository 8 | - Run `yarn` in the repository root 9 | - Run `yarn build` 10 | 11 | ## Dev Instructions 12 | 13 | This project uses yarn workspaces and consists of the sub-projects _data-extraction_, _extension_ and _webview_. 14 | To setup a dev environment, follow these steps: 15 | 16 | - Clone the repository 17 | - Run `yarn` in the repository root 18 | - Run `yarn build` initially (or `yarn dev` for every sub-project) 19 | - Run `yarn dev` for the sub-project (i.e. in its folder) you are working on. 20 | 21 | For the _webview_ project, `yarn dev` will serve the react application on port 8080. 22 | Certain query parameters need to be set, so that the UI can connect to the debug visualizer extension. 23 | 24 | You can use VS Code to launch and debug the extension. 25 | Choose the preconfigured `Run Extension (Dev UI)` as debug configuration 26 | so that the extension loads the UI from the webpack server. 27 | Otherwise, the extension will start a webserver on its own, hosting the `dist` folder of the _webview_ project. 28 | 29 | ## Publish Instructions 30 | 31 | - Follow the Build Instructions 32 | - `cd extension` 33 | - `yarn pub` 34 | 35 | ## Architecture 36 | 37 | ![](./docs/exported/main/Main.png) 38 | 39 | ### webview 40 | 41 | Implements the UI and is hosted inside a webview in VS Code. 42 | Can be opened in a browser window. 43 | Uses websockets and JSON RPC to communicate with the extension. 44 | 45 | ### hediet/visualization 46 | 47 | Contains all the visualizers and visualization infrastructure. 48 | This external project can be found [here](https://github.com/hediet/visualization). 49 | 50 | ### extension 51 | 52 | Creates the webview in VS Code, hosts a webserver and a websocket server. 53 | The webserver serves the _webview_ project that is loaded by the webview. 54 | If started with the `Run Extension (Dev UI)` debug configuration, it will load 55 | the page from `http://localhost:8080` rather than from its own http server. 56 | 57 | The webview is served from an http server rather than the file system to work around some security mechanisms, 58 | which would prevent lazy chunk loading or websockets. 59 | 60 | After the webview is loaded, it connects to the websocket server. 61 | The websocket server is used to evaluate expressions and is secured by a random token. 62 | 63 | ### data-extraction 64 | 65 | Provides types and a JS runtime for data extraction. 66 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # VS Code Debug Visualizer 2 | 3 | [![](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) 4 | [![](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) 5 | [![](https://img.shields.io/twitter/follow/hediet_dev.svg?style=social)](https://twitter.com/intent/follow?screen_name=hediet_dev) 6 | 7 | See [README.md](./extension/README.md) for the readme of the extension. 8 | 9 | You can get the extension in the [marketplace](https://marketplace.visualstudio.com/items?itemName=hediet.debug-visualizer). 10 | 11 | See [CONTRIBUTING.md](./CONTRIBUTING.md) for build instructions and implementation details. 12 | 13 | ![](./docs/doubly-linked-list-reverse-demo.gif) 14 | -------------------------------------------------------------------------------- /data-extraction/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Change Log 2 | 3 | ## 0.11.0 4 | 5 | - Object Graph Data Extractor 6 | - Plotly Data Extractor 7 | - Some Bugfixes 8 | -------------------------------------------------------------------------------- /data-extraction/LICENSE.md: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Henning Dieterichs 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /data-extraction/README.md: -------------------------------------------------------------------------------- 1 | # @hediet/debug-visualizer-data-extraction 2 | 3 | [![](https://img.shields.io/twitter/follow/hediet_dev.svg?style=social)](https://twitter.com/intent/follow?screen_name=hediet_dev) 4 | 5 | A library that helps implementing data extractors for the Debug Visualizer VS Code extension. 6 | It will automatically be injected by the extension when the debugger attaches. 7 | Compatible with NodeJS and browsers. 8 | 9 | # Installation 10 | 11 | Use the following command to install the library using yarn: 12 | 13 | ``` 14 | yarn add @hediet/debug-visualizer-data-extraction 15 | ``` 16 | 17 | # Usage 18 | 19 | ## `createGraphFromPointers` Helper 20 | 21 | ```ts 22 | import { createGraphFromPointers } from "@hediet/debug-visualizer-data-extraction"; 23 | 24 | setTimeout(() => { 25 | new Main().run(); 26 | }, 0); 27 | 28 | class Main { 29 | run() { 30 | const list = new DoublyLinkedList("1"); 31 | list.setNext(new DoublyLinkedList("2")); 32 | list.next!.setNext(new DoublyLinkedList("3")); 33 | list.next!.next!.setNext(new DoublyLinkedList("4")); 34 | 35 | // Watch `visualize()` with the Debug Visualizer Extension for VS Code! 36 | const visualize = () => 37 | // Returns `CommonDataTypes.Graph` data which can be visualized by 38 | // either the vis.js or the graphviz visualizer. 39 | createGraphFromPointers({ list, last, cur }, i => ({ 40 | id: i.id, 41 | label: i.name, 42 | color: finished.has(i) ? "lime" : undefined, 43 | edges: [ 44 | { to: i.next!, label: "next" }, 45 | { to: i.prev!, label: "prev", color: "lightgray" }, 46 | ].filter(r => !!r.to), 47 | })); 48 | 49 | const finished = new Set(); 50 | var cur: DoublyLinkedList | undefined = list; 51 | // Reverses `list`. Finished nodes have correct pointers, 52 | // their next node is also finished. 53 | var last: DoublyLinkedList | undefined = undefined; 54 | while (cur) { 55 | cur.prev = cur.next; 56 | cur.next = last; 57 | finished.add(cur); 58 | last = cur; 59 | cur = cur.prev; 60 | } 61 | console.log("finished"); 62 | } 63 | } 64 | 65 | let id = 0; 66 | class DoublyLinkedList { 67 | public readonly id = (id++).toString(); 68 | constructor(public name: string) {} 69 | 70 | next: DoublyLinkedList | undefined; 71 | prev: DoublyLinkedList | undefined; 72 | 73 | public setNext(val: DoublyLinkedList): void { 74 | val.prev = this; 75 | this.next = val; 76 | } 77 | } 78 | ``` 79 | 80 | ![](../docs/doubly-linked-list-reverse-demo.gif) 81 | 82 | ## Registering Custom Data Extractors 83 | 84 | ```ts 85 | import { getDataExtractorApi } from "@hediet/debug-visualizer-data-extraction"; 86 | 87 | getDataExtractorApi().registerExtractor({ 88 | id: "my-foo-extractor", 89 | getExtractions: (data, collector) => { 90 | if (data instanceof Foo) { 91 | collector.addExtraction({ 92 | id: "my-foo-extraction", 93 | name: "My Foo Extraction", 94 | priority: 2000, 95 | extractData: () => ({ kind: { text: true }, text: "Foo" }), 96 | }); 97 | } 98 | }, 99 | }); 100 | 101 | setTimeout(() => { 102 | new Main().run(); 103 | }, 0); 104 | 105 | class Foo {} 106 | 107 | class Main { 108 | run() { 109 | const f = new Foo(); 110 | // if `f` is watched by the Debug Visualizer, 111 | // `my-foo-extractor` will provide the data for the visualizers. 112 | // See `CommonDataTypes` for data types that have built in visualizers. 113 | debugger; 114 | } 115 | } 116 | ``` 117 | -------------------------------------------------------------------------------- /data-extraction/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@hediet/debug-visualizer-data-extraction", 3 | "description": "A library that helps implementing data extractors for the Debug Visualizer VS Code extension.", 4 | "version": "0.14.0", 5 | "main": "dist/index.js", 6 | "types": "dist/index.d.ts", 7 | "author": { 8 | "name": "Henning Dieterichs", 9 | "email": "henning.dieterichs@live.de" 10 | }, 11 | "repository": { 12 | "type": "git", 13 | "url": "https://github.com/hediet/vscode-debug-visualizer.git" 14 | }, 15 | "license": "MIT", 16 | "files": [ 17 | "dist", 18 | "src" 19 | ], 20 | "publishConfig": { 21 | "access": "public", 22 | "registry": "https://registry.npmjs.org/" 23 | }, 24 | "scripts": { 25 | "dev": "webpack --watch --mode development", 26 | "build": "webpack --mode production", 27 | "test": "mocha --require source-map-support/register --require ts-node/register ./test/**/*.test.ts" 28 | }, 29 | "dependencies": {}, 30 | "devDependencies": { 31 | "copy-webpack-plugin": "^11.0.0", 32 | "@types/copy-webpack-plugin": "^10.1.0", 33 | "@types/mocha": "^7.0.2", 34 | "@types/node": "^13.7.4", 35 | "@types/plotly.js": "1.44.28", 36 | "coveralls": "^3.0.11", 37 | "mocha": "^7.1.1", 38 | "mocha-lcov-reporter": "^1.3.0", 39 | "nyc": "^15.0.0", 40 | "source-map-support": "^0.5.16", 41 | "tslint": "^6.1.3", 42 | "typescript": "^5.1.6", 43 | "webpack": "^5.88.1", 44 | "webpack-cli": "^5.1.4", 45 | "ts-loader": "^9.4.4", 46 | "ts-node": "^10.9.1" 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /data-extraction/src/DataExtractionResult.ts: -------------------------------------------------------------------------------- 1 | export interface DataExtractionResult { 2 | data: VisualizationData; 3 | usedExtractor: DataExtractorInfo; 4 | availableExtractors: DataExtractorInfo[]; 5 | } 6 | 7 | /** 8 | * Instances must be valid json values. 9 | */ 10 | export interface VisualizationData { 11 | kind: Record; 12 | } 13 | 14 | export interface DataExtractorInfo { 15 | id: DataExtractorId; 16 | name: string; 17 | priority: number; 18 | } 19 | 20 | export type DataExtractorId = { 21 | __brand: "DataExtractorId"; 22 | } & string; 23 | 24 | export function isVisualizationData(val: unknown): val is VisualizationData { 25 | if (typeof val !== "object" || !val || !("kind" in val)) { 26 | return false; 27 | } 28 | 29 | const obj = val as any; 30 | if (typeof obj.kind !== "object" || !obj.kind) { 31 | return false; 32 | } 33 | 34 | return Object.values(obj.kind).every(val => val === true); 35 | } 36 | -------------------------------------------------------------------------------- /data-extraction/src/getGlobal.ts: -------------------------------------------------------------------------------- 1 | export function getGlobal(): any { 2 | if (typeof globalThis === "object") { 3 | return globalThis; 4 | } else if (typeof global === "object") { 5 | return global; 6 | } else if (typeof window === "object") { 7 | return window; 8 | } 9 | throw new Error("No global available"); 10 | } 11 | -------------------------------------------------------------------------------- /data-extraction/src/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./js"; 2 | export * from "./CommonDataTypes"; 3 | export * from "./DataExtractionResult"; 4 | -------------------------------------------------------------------------------- /data-extraction/src/js/api/DataExtractorApi.ts: -------------------------------------------------------------------------------- 1 | import { DataExtractionResult, VisualizationData } from "../../DataExtractionResult"; 2 | import { LoadDataExtractorsFn } from "./LoadDataExtractorsFn"; 3 | 4 | export interface DataExtractorApi { 5 | /** 6 | * Registers a single extractor. 7 | */ 8 | registerExtractor(extractor: DataExtractor): void; 9 | 10 | /** 11 | * Registers multiple extractors. 12 | */ 13 | registerExtractors(extractors: DataExtractor[]): void; 14 | 15 | /** 16 | * Extracts data from the result of `valueFn`. 17 | * @valueFn a function returning the value to extract the data from. 18 | * Is a function so that it's evaluation can depend on `evalFn`. 19 | */ 20 | getData( 21 | valueFn: () => unknown, 22 | evalFn: (expression: string) => T, 23 | preferredDataExtractorId: string | undefined, 24 | variablesInScope: Record, 25 | callFramesSnapshot: CallFramesSnapshot | null 26 | ): JSONString; 27 | 28 | /** 29 | * Registers all default (built-in) extractors. 30 | * @preferExisting if `true`, existing extractors with the same id are not overwritten. 31 | */ 32 | registerDefaultExtractors(preferExisting?: boolean): void; 33 | 34 | setDataExtractorFn(id: string, fn: LoadDataExtractorsFn | undefined): void; 35 | } 36 | 37 | export type DataResult = 38 | | { 39 | kind: "Data"; 40 | extractionResult: DataExtractionResult; 41 | } 42 | | { kind: "NoExtractors" } 43 | | { kind: "Error"; message: string } 44 | | { 45 | kind: "OutdatedCallFrameSnapshot"; 46 | callFramesRequest: CallFramesRequest; 47 | }; 48 | 49 | export interface JSONString extends String { 50 | __brand: { json: T }; 51 | } 52 | 53 | export interface CallFramesRequest { 54 | requestId: string; 55 | requestedCallFrames: CallFrameRequest[]; 56 | } 57 | 58 | export interface CallFrameRequest { 59 | methodName: string; 60 | pathRegExp: string | undefined; 61 | } 62 | 63 | export interface CallFramesSnapshot { 64 | requestId: string; 65 | frames: (CallFrameInfo | SkippedCallFrames)[]; 66 | } 67 | 68 | export interface SkippedCallFrames { 69 | skippedFrames: number; 70 | } 71 | 72 | export interface CallFrameInfo { 73 | methodName: string; 74 | source: { name: string; path: string }; 75 | vars: Record; 76 | } 77 | 78 | export interface DataExtractor { 79 | /** 80 | * Must be unique among all data extractors. 81 | */ 82 | id: string; 83 | 84 | /** 85 | * Filters the data to be extracted. 86 | */ 87 | dataCtor?: string; 88 | getExtractions(data: unknown, extractionCollector: ExtractionCollector, context: DataExtractorContext): void; 89 | } 90 | 91 | export interface ExtractionCollector { 92 | /** 93 | * Suggests a possible extraction. 94 | */ 95 | addExtraction(extraction: DataExtraction): void; 96 | } 97 | 98 | export interface DataExtractorContext { 99 | /** 100 | * Evaluates an expression in the context of the active stack frame. 101 | */ 102 | evalFn: (expression: string) => TEval; 103 | 104 | readonly expression: string | undefined; 105 | 106 | readonly variablesInScope: Record unknown>; 107 | 108 | extract(value: unknown): VisualizationData | undefined; 109 | 110 | readonly callFrameInfos: readonly (CallFrameInfo | SkippedCallFrames)[]; 111 | 112 | addCallFrameRequest(request: CallFrameRequest): void; 113 | } 114 | 115 | export interface DataExtraction { 116 | /** 117 | * Higher priorities are preferred. 118 | */ 119 | priority: number; 120 | 121 | /** 122 | * A unique id identifying this extraction among all extractions. 123 | * Required to express extraction preferences. 124 | */ 125 | id?: string; 126 | 127 | /** 128 | * A user friendly name of this extraction. 129 | */ 130 | name?: string; 131 | 132 | extractData(): VisualizationData; 133 | } 134 | -------------------------------------------------------------------------------- /data-extraction/src/js/api/LoadDataExtractorsFn.ts: -------------------------------------------------------------------------------- 1 | import { DataExtractor } from "./DataExtractorApi"; 2 | import type * as helpersTypes from "../helpers" 3 | 4 | export type LoadDataExtractorsFn = ( 5 | register: (extractor: DataExtractor) => void, 6 | helpers: typeof helpersTypes 7 | ) => void; 8 | -------------------------------------------------------------------------------- /data-extraction/src/js/api/default-extractors/AsIsDataExtractor.ts: -------------------------------------------------------------------------------- 1 | import { isVisualizationData } from "../../../DataExtractionResult"; 2 | import { 3 | DataExtractor, 4 | ExtractionCollector, 5 | DataExtractorContext, 6 | } from "../.."; 7 | 8 | export class AsIsDataExtractor implements DataExtractor { 9 | readonly id = "as-is"; 10 | getExtractions( 11 | data: unknown, 12 | extractionCollector: ExtractionCollector, 13 | context: DataExtractorContext 14 | ): void { 15 | if (!isVisualizationData(data)) { 16 | return; 17 | } 18 | 19 | extractionCollector.addExtraction({ 20 | id: this.id, 21 | name: "As Is", 22 | priority: 500, 23 | extractData() { 24 | return data; 25 | }, 26 | }); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /data-extraction/src/js/api/default-extractors/GetDebugVisualizationDataExtractor.ts: -------------------------------------------------------------------------------- 1 | import { VisualizationData } from "../../../DataExtractionResult"; 2 | import { 3 | DataExtractor, 4 | ExtractionCollector, 5 | DataExtractorContext, 6 | } from "../DataExtractorApi"; 7 | 8 | export class GetVisualizationDataExtractor implements DataExtractor { 9 | readonly id = "get-visualization-data"; 10 | getExtractions( 11 | data: unknown, 12 | collector: ExtractionCollector, 13 | context: DataExtractorContext 14 | ): void { 15 | if (typeof data !== "object" || !data) { 16 | return; 17 | } 18 | 19 | const getVisualizationData = (data as any) 20 | .getVisualizationData as Function; 21 | if (typeof getVisualizationData !== "function") { 22 | return; 23 | } 24 | 25 | collector.addExtraction({ 26 | id: this.id, 27 | name: "Use Method 'getVisualizationData'", 28 | priority: 600, 29 | extractData() { 30 | return getVisualizationData.apply(data); 31 | }, 32 | }); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /data-extraction/src/js/api/default-extractors/GridExtractor.ts: -------------------------------------------------------------------------------- 1 | import { 2 | DataExtractor, 3 | ExtractionCollector, 4 | DataExtractorContext, 5 | } from "../.."; 6 | import { GridVisualizationData } from "../../../CommonDataTypes"; 7 | import { expect } from "../../../util"; 8 | 9 | export class GridExtractor implements DataExtractor { 10 | readonly id = "grid"; 11 | getExtractions( 12 | data: unknown, 13 | extractionCollector: ExtractionCollector, 14 | context: DataExtractorContext 15 | ): void { 16 | if (!Array.isArray(data)) { 17 | return; 18 | } 19 | 20 | extractionCollector.addExtraction({ 21 | id: this.id, 22 | name: "Array As Grid", 23 | priority: 500, 24 | extractData: () => 25 | expect({ 26 | kind: { grid: true }, 27 | rows: [{ columns: data.map(d => ({ tag: "" + d })) }], 28 | }), 29 | }); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /data-extraction/src/js/api/default-extractors/MarkedGridExtractor.ts: -------------------------------------------------------------------------------- 1 | import { DataExtractor, ExtractionCollector, DataExtractorContext } from ".."; 2 | import { LineColumnRange } from "../../../CommonDataTypes"; 3 | import { asData, markedGrid, tryEval } from "../../helpers"; 4 | 5 | export class MarkedGridFromArrayExtractor implements DataExtractor { 6 | readonly id = "markedGridFromArray"; 7 | getExtractions( 8 | data: unknown, 9 | collector: ExtractionCollector, 10 | context: DataExtractorContext 11 | ): void { 12 | if (!Array.isArray(data)) { 13 | return; 14 | } 15 | if (!Array.isArray(data[0])) { 16 | return; 17 | } 18 | 19 | let markers: any; 20 | if (data.length === 2 && typeof data[1] === "object") { 21 | markers = data[1]; 22 | } else { 23 | for (let i = 1; i < data.length; i++) { 24 | if (typeof data[i] !== "string") { 25 | return; 26 | } 27 | } 28 | markers = tryEval(data.slice(1)); 29 | } 30 | 31 | collector.addExtraction({ 32 | id: "markedGridFromArray", 33 | name: "Marked Grid from Array", 34 | priority: 1000, 35 | extractData() { 36 | return markedGrid(data[0], markers as any); 37 | }, 38 | }); 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /data-extraction/src/js/api/default-extractors/ObjectGraphExtractor.ts: -------------------------------------------------------------------------------- 1 | import { VisualizationData } from "../../../DataExtractionResult"; 2 | import { 3 | DataExtractor, 4 | ExtractionCollector, 5 | DataExtractorContext, 6 | } from "../DataExtractorApi"; 7 | import { 8 | createGraph, 9 | CreateGraphEdge, 10 | createGraphFromPointers, 11 | } from "../../helpers"; 12 | import { GraphNode } from "../../.."; 13 | 14 | export class ObjectGraphExtractor implements DataExtractor { 15 | readonly id = "object-graph"; 16 | 17 | getExtractions( 18 | data: unknown, 19 | collector: ExtractionCollector, 20 | context: DataExtractorContext 21 | ): void { 22 | function isObject(val: unknown): val is object { 23 | if (typeof val !== "object") { 24 | return false; 25 | } 26 | if (!val) { 27 | return false; 28 | } 29 | return true; 30 | } 31 | if (!isObject(data)) { 32 | return; 33 | } 34 | 35 | const infoSelector: (item: object) => { 36 | id?: string | number; 37 | edges: CreateGraphEdge[]; 38 | } & Omit = (item) => { 39 | let label = ""; 40 | const edges = new Array>(); 41 | if (item instanceof Set) { 42 | label = "Set"; 43 | for (const value of item.values()) { 44 | if (isObject(value)) { 45 | edges.push({ label: "item", to: value }); 46 | } 47 | } 48 | } else if (item instanceof Map) { 49 | label = "Map"; 50 | for (const [key, value] of item.entries()) { 51 | if (isObject(value)) { 52 | edges.push({ label: key, to: value }); 53 | } 54 | } 55 | } else { 56 | for (const [key, val] of Object.entries(item)) { 57 | if (isObject(val)) { 58 | edges.push({ label: key, to: val }); 59 | } 60 | } 61 | 62 | const className = item.constructor 63 | ? item.constructor.name 64 | : "?"; 65 | label = className; 66 | if (!Array.isArray(item)) { 67 | try { 68 | let toStrVal = item.toString(); 69 | if (toStrVal !== "[object Object]") { 70 | if (toStrVal.length > 15) { 71 | toStrVal += ` "${toStrVal.substr(0, 15)}..."`; 72 | } 73 | label += `${className}: ${toStrVal}`; 74 | } 75 | } catch (e) {} 76 | } 77 | } 78 | 79 | return { 80 | shape: "box", 81 | edges, 82 | color: item === data ? "lightblue" : undefined, 83 | label, 84 | }; 85 | }; 86 | 87 | collector.addExtraction({ 88 | id: "object-graph", 89 | name: "Object Graph", 90 | priority: 98, 91 | extractData() { 92 | return createGraph([data], infoSelector, { maxSize: 50 }); 93 | }, 94 | }); 95 | 96 | if ( 97 | data.constructor === Object && 98 | Object.values(data).every( 99 | (v) => v === undefined || v === null || typeof v === "object" 100 | ) 101 | ) { 102 | collector.addExtraction({ 103 | id: "object-graph-pointers", 104 | name: "Object Graph With Pointers", 105 | priority: 99, 106 | extractData() { 107 | return createGraphFromPointers(data as any, infoSelector, { 108 | maxSize: 50, 109 | }); 110 | }, 111 | }); 112 | } 113 | } 114 | } 115 | -------------------------------------------------------------------------------- /data-extraction/src/js/api/default-extractors/PlotlyDataExtractor.ts: -------------------------------------------------------------------------------- 1 | import { PlotlyVisualizationData } from "../../../CommonDataTypes"; 2 | import { expect } from "../../../util"; 3 | import { 4 | DataExtractor, 5 | ExtractionCollector, 6 | DataExtractorContext, 7 | } from "../DataExtractorApi"; 8 | 9 | export class PlotlyDataExtractor implements DataExtractor { 10 | readonly id = "plot"; 11 | 12 | getExtractions( 13 | data: unknown, 14 | collector: ExtractionCollector, 15 | context: DataExtractorContext 16 | ): void { 17 | if (!Array.isArray(data)) { 18 | return; 19 | } 20 | if (data.some(x => typeof x !== "number")) { 21 | return; 22 | } 23 | 24 | collector.addExtraction({ 25 | id: "plot-y", 26 | name: "Plot as y-Values", 27 | priority: 1001, 28 | extractData: () => 29 | expect({ 30 | kind: { 31 | plotly: true, 32 | }, 33 | data: [{ y: data }], 34 | }), 35 | }); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /data-extraction/src/js/api/default-extractors/StringDiffExtractor.ts: -------------------------------------------------------------------------------- 1 | import { 2 | DataExtractor, 3 | DataExtractorContext, 4 | ExtractionCollector, 5 | } from "../.."; 6 | 7 | export class StringDiffExtractor implements DataExtractor { 8 | public readonly id = "string-diff"; 9 | 10 | public getExtractions( 11 | data: unknown, 12 | collector: ExtractionCollector, 13 | context: DataExtractorContext 14 | ) { 15 | if ( 16 | Array.isArray(data) && 17 | data.length === 2 && 18 | typeof data[0] === "string" && 19 | typeof data[1] === "string" 20 | ) { 21 | collector.addExtraction({ 22 | id: "string-diff", 23 | name: "String Diff", 24 | priority: 1300, 25 | extractData: () => ({ 26 | kind: { text: true }, 27 | text: data[0], 28 | otherText: data[1], 29 | }), 30 | }); 31 | } 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /data-extraction/src/js/api/default-extractors/StringRangeExtractor.ts: -------------------------------------------------------------------------------- 1 | import { DataExtractor, ExtractionCollector, DataExtractorContext } from ".."; 2 | import { LineColumnRange } from "../../../CommonDataTypes"; 3 | import { asData } from "../../helpers"; 4 | 5 | export class StringRangeExtractor implements DataExtractor { 6 | readonly id = "stringRange"; 7 | getExtractions( 8 | data: unknown, 9 | collector: ExtractionCollector, 10 | context: DataExtractorContext 11 | ): void { 12 | if (!Array.isArray(data)) { 13 | return; 14 | } 15 | if (typeof data[0] !== "string") { 16 | return; 17 | } 18 | 19 | const text = data[0]; 20 | 21 | const decorations1: { range: LineColumnRange; label?: string }[] = []; 22 | const decorations2: { range: LineColumnRange; label?: string }[] = []; 23 | 24 | function offsetToLineColumn(offset: number) { 25 | let line = 0; 26 | let column = 0; 27 | for (let idx = 0; idx < text.length; idx++) { 28 | if (idx === offset) { 29 | return { line, column }; 30 | } 31 | if (text[idx] === "\n") { 32 | line++; 33 | column = 0; 34 | } else { 35 | column++; 36 | } 37 | } 38 | return { line, column }; // TODO 39 | } 40 | 41 | if ( 42 | data.length === 2 && 43 | typeof data[1] === "object" && 44 | !Array.isArray(data[1]) 45 | ) { 46 | data = [data[0], ...Object.values(data[1])] as [ 47 | number | [number, number] 48 | ][]; 49 | } 50 | 51 | for (let item of (data as (number | [number, number])[]).slice(1)) { 52 | if (typeof item === "string") { 53 | try { 54 | item = context.evalFn(item); 55 | } catch (e) { 56 | return; 57 | } 58 | if (item === undefined) { 59 | continue; 60 | } 61 | } 62 | 63 | if (typeof item === "number") { 64 | const pos = offsetToLineColumn(item); 65 | decorations1.push({ range: { start: pos, end: pos } }); 66 | decorations2.push({ 67 | range: { 68 | start: pos, 69 | end: { line: pos.line, column: pos.column + 1 }, 70 | }, 71 | }); 72 | } else if ( 73 | Array.isArray(item) && 74 | item.length === 2 && 75 | typeof item[0] === "number" && 76 | typeof item[1] === "number" 77 | ) { 78 | decorations1.push({ 79 | range: { 80 | start: offsetToLineColumn(item[0]), 81 | end: offsetToLineColumn(item[1]), 82 | }, 83 | }); 84 | decorations2.push(decorations1[decorations1.length - 1]); 85 | } else { 86 | return; 87 | } 88 | } 89 | 90 | collector.addExtraction({ 91 | priority: 1200, 92 | id: "stringRange", 93 | name: "String Range", 94 | extractData() { 95 | return asData({ 96 | kind: { text: true }, 97 | text, 98 | decorations: decorations1, 99 | }); 100 | }, 101 | }); 102 | 103 | collector.addExtraction({ 104 | priority: 1000, 105 | id: "stringRangeFullCharacters", 106 | name: "String Range (Full Character)", 107 | extractData() { 108 | return asData({ 109 | kind: { text: true }, 110 | text, 111 | decorations: decorations2, 112 | }); 113 | }, 114 | }); 115 | } 116 | } 117 | -------------------------------------------------------------------------------- /data-extraction/src/js/api/default-extractors/TableExtractor.ts: -------------------------------------------------------------------------------- 1 | import { isAssertionExpression } from "typescript"; 2 | import { DataExtractor, ExtractionCollector, DataExtractorContext } from ".."; 3 | import { TableVisualizationData } from "../../../CommonDataTypes"; 4 | import { expect } from "../../../util"; 5 | 6 | function assert(value: unknown): asserts value {} 7 | 8 | export class TableDataExtractor implements DataExtractor { 9 | readonly id = "table"; 10 | 11 | getExtractions( 12 | data: unknown, 13 | collector: ExtractionCollector, 14 | context: DataExtractorContext 15 | ): void { 16 | if (!Array.isArray(data)) { 17 | return; 18 | } 19 | if ( 20 | !data.every((d) => typeof d === "object" && d && !Array.isArray(d)) 21 | ) { 22 | return; 23 | } 24 | assert(data); 25 | 26 | collector.addExtraction({ 27 | id: "table", 28 | name: "Table", 29 | priority: 1000, 30 | extractData() { 31 | return expect({ 32 | kind: { 33 | table: true, 34 | }, 35 | rows: data, 36 | }); 37 | }, 38 | }); 39 | 40 | collector.addExtraction({ 41 | id: "table-with-type-name", 42 | name: "Table (With Type Name)", 43 | priority: 950, 44 | extractData() { 45 | return expect({ 46 | kind: { 47 | table: true, 48 | }, 49 | rows: data.map((d) => ({ type: d.constructor.name, ...d })), 50 | }); 51 | }, 52 | }); 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /data-extraction/src/js/api/default-extractors/ToStringExtractor.ts: -------------------------------------------------------------------------------- 1 | import { MonacoTextVisualizationData } from "../../../CommonDataTypes"; 2 | import { expect } from "../../../util"; 3 | import { 4 | DataExtractor, 5 | ExtractionCollector, 6 | DataExtractorContext, 7 | } from "../DataExtractorApi"; 8 | 9 | export class ToStringDataExtractor implements DataExtractor { 10 | readonly id = "to-string"; 11 | 12 | getExtractions( 13 | data: unknown, 14 | collector: ExtractionCollector, 15 | context: DataExtractorContext 16 | ): void { 17 | collector.addExtraction({ 18 | id: "to-string", 19 | name: "To String", 20 | priority: 100, 21 | extractData() { 22 | return expect({ 23 | kind: { 24 | text: true, 25 | }, 26 | text: "" + data, 27 | }); 28 | }, 29 | }); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /data-extraction/src/js/api/default-extractors/index.ts: -------------------------------------------------------------------------------- 1 | export { registerDefaultExtractors } from "./registerDefaultDataExtractors"; 2 | -------------------------------------------------------------------------------- /data-extraction/src/js/api/default-extractors/registerDefaultDataExtractors.ts: -------------------------------------------------------------------------------- 1 | import { DataExtractorApi } from "../DataExtractorApi"; 2 | import { TypeScriptAstDataExtractor } from "./TypeScriptDataExtractors"; 3 | import { AsIsDataExtractor } from "./AsIsDataExtractor"; 4 | import { GetVisualizationDataExtractor } from "./GetDebugVisualizationDataExtractor"; 5 | import { ToStringDataExtractor } from "./ToStringExtractor"; 6 | import { PlotlyDataExtractor } from "./PlotlyDataExtractor"; 7 | import { ObjectGraphExtractor } from "./ObjectGraphExtractor"; 8 | import { getDataExtractorApi } from "../injection"; 9 | import { GridExtractor } from "./GridExtractor"; 10 | import { TableDataExtractor } from "./TableExtractor"; 11 | import { StringDiffExtractor } from "./StringDiffExtractor"; 12 | import { StringRangeExtractor } from "./StringRangeExtractor"; 13 | import { MarkedGridFromArrayExtractor } from "./MarkedGridExtractor"; 14 | 15 | /** 16 | * The default data extractors should be registered by VS Code automatically. 17 | * Registering them manually ensures that they are up to date. 18 | */ 19 | export function registerDefaultExtractors( 20 | api: DataExtractorApi = getDataExtractorApi() 21 | ) { 22 | for (const item of [ 23 | new TypeScriptAstDataExtractor(), 24 | new AsIsDataExtractor(), 25 | new GetVisualizationDataExtractor(), 26 | new ToStringDataExtractor(), 27 | new PlotlyDataExtractor(), 28 | new ObjectGraphExtractor(), 29 | new GridExtractor(), 30 | new TableDataExtractor(), 31 | new StringDiffExtractor(), 32 | new StringRangeExtractor(), 33 | new MarkedGridFromArrayExtractor(), 34 | ]) { 35 | api.registerExtractor(item); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /data-extraction/src/js/api/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./DataExtractorApi"; 2 | export * from "./injection"; 3 | export { DataExtractorApiImpl } from "./DataExtractorApiImpl"; 4 | export * from "./LoadDataExtractorsFn"; 5 | -------------------------------------------------------------------------------- /data-extraction/src/js/api/injection.ts: -------------------------------------------------------------------------------- 1 | import * as fs from "fs"; 2 | import { getGlobal } from "../../getGlobal"; 3 | import * as globalHelpers from "../global-helpers"; 4 | import * as helpers from "../helpers"; 5 | import { DataExtractorApi } from "./DataExtractorApi"; 6 | import { DataExtractorApiImpl } from "./DataExtractorApiImpl"; 7 | 8 | /** 9 | * Returns standalone JS code representing an expression that initializes the data extraction API. 10 | * This expression returns nothing. 11 | * This function is called in the VS Code extension, the expression is evaluated in the debugee. 12 | */ 13 | export function getExpressionToInitializeDataExtractorApi(): string { 14 | const _fs = require("fs") as typeof fs; 15 | const moduleSrc = _fs.readFileSync(__filename, { encoding: "utf8" }); 16 | return `((function () { 17 | let module = {}; 18 | ${moduleSrc} 19 | return module.exports.getDataExtractorApi(); 20 | })())`; 21 | } 22 | 23 | /** 24 | * Returns standalone JS code representing an expression that returns the data extraction API. 25 | * This expression returns an object of type `DataExtractorApi`. 26 | * This function is called in the VS Code extension, the expression is evaluated in the debugee. 27 | */ 28 | export function getExpressionForDataExtractorApi(): string { 29 | return `((${selfContainedGetInitializedDataExtractorApi.toString()})())`; 30 | } 31 | 32 | const apiKey = "@hediet/data-extractor-api/v3"; 33 | 34 | export function getDataExtractorApi(): DataExtractorApi { 35 | installHelpers(); 36 | const globalObj = getGlobal(); 37 | if (!globalObj[apiKey]) { 38 | globalObj[apiKey] = new DataExtractorApiImpl(); 39 | } 40 | return globalObj[apiKey]; 41 | } 42 | 43 | /** 44 | * This code is used to detect if the API has not been initialized yet. 45 | * @internal 46 | */ 47 | export const ApiHasNotBeenInitializedCode = "EgH0cybXij1jYUozyakO" as const; 48 | 49 | /** 50 | * @internal 51 | */ 52 | function selfContainedGetInitializedDataExtractorApi(): DataExtractorApi { 53 | function getGlobal(): any { 54 | if (typeof globalThis === "object") { 55 | return globalThis; 56 | } else if (typeof global === "object") { 57 | return global; 58 | } else if (typeof window === "object") { 59 | return window; 60 | } 61 | throw new Error("No global available"); 62 | } 63 | 64 | const globalObj = getGlobal(); 65 | const key: typeof apiKey = "@hediet/data-extractor-api/v3"; 66 | let api: DataExtractorApi | undefined = globalObj[key]; 67 | if (!api) { 68 | const code: typeof ApiHasNotBeenInitializedCode = 69 | "EgH0cybXij1jYUozyakO"; 70 | throw new Error( 71 | `Data Extractor API has not been initialized. Code: ${code}` 72 | ); 73 | } 74 | return api; 75 | } 76 | 77 | export function installHelpers(): void { 78 | const globalObj = getGlobal(); 79 | // `hediet` as prefix to avoid name collision (I own `hediet.de`). 80 | globalObj["hedietDbgVis"] = { ...helpers, ...globalHelpers }; 81 | } 82 | -------------------------------------------------------------------------------- /data-extraction/src/js/global-helpers.ts: -------------------------------------------------------------------------------- 1 | export { getDataExtractorApi as getApi } from "./api/injection"; 2 | -------------------------------------------------------------------------------- /data-extraction/src/js/helpers/asData.ts: -------------------------------------------------------------------------------- 1 | import { KnownVisualizationData } from "../../CommonDataTypes"; 2 | import { VisualizationData } from "../../DataExtractionResult"; 3 | 4 | export function asData(data: KnownVisualizationData): VisualizationData { 5 | return data; 6 | } 7 | -------------------------------------------------------------------------------- /data-extraction/src/js/helpers/cache.ts: -------------------------------------------------------------------------------- 1 | import { DataExtractorApiImpl } from "../api"; 2 | 3 | const cached = new Map(); 4 | 5 | /** 6 | * Evaluates an expression 7 | */ 8 | export function cache( 9 | expression: string | (() => T), 10 | id: string | number | undefined = undefined 11 | ): T { 12 | let resultFn: () => any; 13 | let key: string; 14 | if (typeof expression === "string") { 15 | const context = DataExtractorApiImpl.lastContext!; 16 | resultFn = () => context.evalFn(expression); 17 | key = JSON.stringify({ expr: expression, id }); 18 | } else { 19 | resultFn = () => expression(); 20 | key = JSON.stringify({ expr: expression.toString(), id }); 21 | } 22 | 23 | if (cached.has(key)) { 24 | return cached.get(key); 25 | } 26 | 27 | const result = resultFn(); 28 | cached.set(key, result); 29 | 30 | return result; 31 | } 32 | -------------------------------------------------------------------------------- /data-extraction/src/js/helpers/createGraph.ts: -------------------------------------------------------------------------------- 1 | import { 2 | GraphEdge, 3 | GraphNode, 4 | GraphVisualizationData, 5 | } from "../../CommonDataTypes"; 6 | 7 | export type CreateGraphEdge = ({ to: T } | { from: T } | { include: T }) & 8 | Omit; 9 | 10 | /** 11 | * Given a list of roots, it creates a graph by following their edges recursively. 12 | * Tracks cycles. 13 | */ 14 | export function createGraph( 15 | roots: T[], 16 | infoSelector: ( 17 | item: T 18 | ) => { 19 | id?: string | number; 20 | edges: CreateGraphEdge[]; 21 | } & Omit, 22 | options: { maxSize?: number } = {} 23 | ): GraphVisualizationData { 24 | const r: GraphVisualizationData = { 25 | kind: { 26 | graph: true, 27 | }, 28 | nodes: [], 29 | edges: [], 30 | }; 31 | let idCounter = 1; 32 | const ids = new Map(); 33 | function getId(item: T): string { 34 | const _id = infoSelector(item).id; 35 | if (_id !== undefined) { 36 | return "" + _id; 37 | } 38 | 39 | let id = ids.get(item); 40 | if (!id) { 41 | id = `hediet.de/id-${idCounter++}`; 42 | ids.set(item, id); 43 | } 44 | return id; 45 | } 46 | 47 | const queue = new Array<{ item: T; dist: number }>( 48 | ...roots.map(r => ({ item: r, dist: 0 })) 49 | ); 50 | const processed = new Set(); 51 | 52 | while (queue.length > 0) { 53 | const { item, dist } = queue.shift()!; 54 | if (processed.has(item)) { 55 | continue; 56 | } 57 | processed.add(item); 58 | const nodeInfo = infoSelector(item); 59 | const fromId = getId(item); 60 | r.nodes.push({ ...nodeInfo, id: fromId, ["edges" as any]: undefined }); 61 | for (const e of nodeInfo.edges) { 62 | let other: T; 63 | let toId: string | undefined; 64 | let fromId_: string | undefined; 65 | if ("to" in e) { 66 | other = e.to; 67 | toId = getId(e.to); 68 | fromId_ = fromId; 69 | } else if ("from" in e) { 70 | other = e.from; 71 | toId = getId(e.from); 72 | fromId_ = fromId; 73 | } else if ("include" in e) { 74 | other = e.include; 75 | } else { 76 | throw new Error("Every edge must either have 'to' or 'from'"); 77 | } 78 | 79 | if (fromId_ !== undefined && toId !== undefined) { 80 | r.edges.push({ 81 | ...e, 82 | from: fromId_, 83 | to: toId, 84 | }); 85 | } 86 | let shouldPush = !processed.has(other); 87 | if ( 88 | options.maxSize && 89 | processed.size + queue.length > options.maxSize 90 | ) { 91 | shouldPush = false; 92 | } 93 | if (shouldPush) { 94 | queue.push({ item: other, dist: dist + 1 }); 95 | } 96 | } 97 | } 98 | return r; 99 | } 100 | -------------------------------------------------------------------------------- /data-extraction/src/js/helpers/createGraphFromPointers.ts: -------------------------------------------------------------------------------- 1 | import { GraphNode, GraphVisualizationData } from "../../CommonDataTypes"; 2 | import { CreateGraphEdge, createGraph } from "./createGraph"; 3 | 4 | /** 5 | * Given a labeled list of roots, it creates a graph by following their edges recursively. 6 | * Tracks cycles. 7 | */ 8 | export function createGraphFromPointers( 9 | roots: Record, 10 | infoSelector: (item: T) => { 11 | id?: string | number; 12 | edges: CreateGraphEdge[]; 13 | } & Omit, 14 | options: { maxSize?: number } = {} 15 | ): GraphVisualizationData { 16 | const marker = {}; 17 | 18 | interface Pointer { 19 | marker: {}; 20 | name: string; 21 | value: T | null | undefined; 22 | } 23 | 24 | const items = Object.entries(roots).map(([name, value]) => ({ 25 | marker, 26 | name, 27 | value, 28 | })); 29 | 30 | return createGraph( 31 | items, 32 | (item) => { 33 | if ( 34 | typeof item === "object" && 35 | item && 36 | "marker" in item && 37 | item["marker"] === marker 38 | ) { 39 | return { 40 | id: "label____" + item.name, 41 | color: "orange", 42 | label: item.name, 43 | edges: [ 44 | { to: item.value!, color: "orange", label: "" }, 45 | ].filter((t) => !!t.to), 46 | }; 47 | } else { 48 | return infoSelector(item as T); 49 | } 50 | }, 51 | options 52 | ); 53 | } 54 | -------------------------------------------------------------------------------- /data-extraction/src/js/helpers/find.ts: -------------------------------------------------------------------------------- 1 | import { DataExtractorApiImpl } from "../api"; 2 | 3 | export function find(predicate: (obj: unknown) => boolean): unknown { 4 | const processed = new Set(); 5 | if (!DataExtractorApiImpl.lastContext) { 6 | throw new Error("No data extractor context!"); 7 | } 8 | 9 | const values = Object.values( 10 | DataExtractorApiImpl.lastContext.variablesInScope 11 | ); 12 | const queue = [ 13 | ...values.map((v) => { 14 | try { 15 | return v(); 16 | } catch (e) { 17 | return undefined; 18 | } 19 | }), 20 | ]; 21 | 22 | let i = 10000; 23 | while (i > 0) { 24 | i--; 25 | 26 | const top = queue.shift(); 27 | processed.add(top); 28 | 29 | if (predicate(top)) { 30 | return top; 31 | } 32 | 33 | if (typeof top === "object" && top) { 34 | for (const val of Object.values(top)) { 35 | if (!processed.has(val)) { 36 | processed.add(val); 37 | queue.push(val); 38 | } 39 | } 40 | } 41 | } 42 | 43 | return undefined; 44 | } 45 | 46 | export function findVar( 47 | options: { nameSimilarTo?: string; ctor?: string }, 48 | predicate?: (value: any) => boolean 49 | ): unknown | undefined { 50 | if (!DataExtractorApiImpl.lastContext) { 51 | throw new Error("No data extractor context!"); 52 | } 53 | 54 | let bestValue = undefined; 55 | let bestValueScore = undefined; // minimized 56 | 57 | for (const [name, value] of Object.entries( 58 | DataExtractorApiImpl.lastContext.variablesInScope 59 | )) { 60 | const v = value(); 61 | if (options.ctor !== undefined) { 62 | if ( 63 | typeof v !== "object" || 64 | !v || 65 | v.constructor.name !== options.ctor 66 | ) { 67 | continue; 68 | } 69 | } 70 | if (predicate) { 71 | if (!predicate(v)) { 72 | continue; 73 | } 74 | } 75 | let score = 0; 76 | if (options.nameSimilarTo !== undefined) { 77 | score += similarityScore(name, options.nameSimilarTo); 78 | } else { 79 | return v; 80 | } 81 | if (bestValueScore === undefined || score < bestValueScore) { 82 | bestValue = v; 83 | bestValueScore = score; 84 | } 85 | } 86 | 87 | return bestValue; 88 | } 89 | 90 | function similarityScore(a: string, b: string): number { 91 | const distance = levenshteinDistance(a, b); 92 | 93 | const aSorted = a.split("").sort().join(""); 94 | const bSorted = b.split("").sort().join(""); 95 | const distance2 = levenshteinDistance(aSorted, bSorted); 96 | 97 | return distance * 10 + distance2; 98 | } 99 | 100 | function levenshteinDistance(a: string, b: string): number { 101 | if (a.length === 0) return b.length; 102 | if (b.length === 0) return a.length; 103 | 104 | const matrix = []; 105 | 106 | // increment along the first column of each row 107 | let i; 108 | for (i = 0; i <= b.length; i++) { 109 | matrix[i] = [i]; 110 | } 111 | 112 | // increment each column in the first row 113 | let j; 114 | for (j = 0; j <= a.length; j++) { 115 | matrix[0][j] = j; 116 | } 117 | 118 | // Fill in the rest of the matrix 119 | for (i = 1; i <= b.length; i++) { 120 | for (j = 1; j <= a.length; j++) { 121 | if (b.charAt(i - 1) == a.charAt(j - 1)) { 122 | matrix[i][j] = matrix[i - 1][j - 1]; 123 | } else { 124 | matrix[i][j] = Math.min( 125 | matrix[i - 1][j - 1] + 1, // substitution 126 | Math.min( 127 | matrix[i][j - 1] + 1, // insertion 128 | matrix[i - 1][j] + 1 129 | ) 130 | ); // deletion 131 | } 132 | } 133 | } 134 | 135 | return matrix[b.length][a.length]; 136 | } 137 | -------------------------------------------------------------------------------- /data-extraction/src/js/helpers/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./createGraph"; 2 | export * from "./createGraphFromPointers"; 3 | export * from "./tryEval"; 4 | export * from "./markedGrid"; 5 | export * from "./cache"; 6 | export * from "./asData"; 7 | export * from "./find"; 8 | -------------------------------------------------------------------------------- /data-extraction/src/js/helpers/markedGrid.ts: -------------------------------------------------------------------------------- 1 | import { GridVisualizationData } from "../../CommonDataTypes"; 2 | 3 | export function markedGrid( 4 | arr: unknown[], 5 | marked: Record 6 | ): GridVisualizationData { 7 | if (!Array.isArray(arr)) { 8 | arr = [...(arr as any)]; 9 | } 10 | 11 | return { 12 | kind: { grid: true }, 13 | rows: [ 14 | { 15 | columns: arr.map((d) => ({ 16 | content: "" + d, 17 | tag: "" + d, 18 | })), 19 | }, 20 | ], 21 | markers: marked 22 | ? Object.entries(marked) 23 | .map(([key, val]) => ({ 24 | id: "" + key, 25 | row: 0, 26 | column: val!, 27 | })) 28 | .filter((c) => c.column !== undefined) 29 | : undefined, 30 | }; 31 | } 32 | -------------------------------------------------------------------------------- /data-extraction/src/js/helpers/tryEval.ts: -------------------------------------------------------------------------------- 1 | import { DataExtractorApiImpl } from "../api/DataExtractorApiImpl"; 2 | 3 | /** 4 | * Takes an object and eval's its values. 5 | * Each successfully evaluated value is added to the result object, 6 | * its original key is used as key in the result object. 7 | * 8 | * # Example 9 | * ``` 10 | * const x = 1; 11 | * tryEval({ val1: "x", val2: "x y" }) 12 | * // -> { val1: 1 } 13 | * ``` 14 | */ 15 | export function tryEval(obj: Record): Record; 16 | /** 17 | * Takes an array of strings and eval's its items. 18 | * Each successfully evaluated value is added to the result object, 19 | * its original value is used as key. 20 | * 21 | * # Example 22 | * ``` 23 | * const x = 1; 24 | * tryEval(["x", "y", "a a", "x + 2"]) 25 | * // -> { x: 1, "x + 2": 3 } 26 | * ``` 27 | */ 28 | export function tryEval(arr: string[]): Record; 29 | export function tryEval( 30 | obj: Record | string[] | string 31 | ): Record | unknown { 32 | const result: Record = {}; 33 | const context = DataExtractorApiImpl.lastContext!; 34 | if (Array.isArray(obj)) { 35 | for (const val of obj) { 36 | try { 37 | result[val] = context.evalFn(val); 38 | } catch (e) {} 39 | } 40 | } else { 41 | for (const [key, val] of Object.entries(obj)) { 42 | try { 43 | result[key] = context.evalFn(val); 44 | } catch (e) {} 45 | } 46 | } 47 | return result; 48 | } 49 | -------------------------------------------------------------------------------- /data-extraction/src/js/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./api"; 2 | export * from "./helpers"; 3 | export { registerDefaultExtractors } from "./api/default-extractors"; 4 | -------------------------------------------------------------------------------- /data-extraction/src/util.ts: -------------------------------------------------------------------------------- 1 | export function expect(data: T): T { 2 | return data; 3 | } 4 | -------------------------------------------------------------------------------- /data-extraction/test/main.test.ts: -------------------------------------------------------------------------------- 1 | import { DataExtractorApiImpl } from "../"; 2 | 3 | describe("extractor", () => { 4 | it("should not crash", () => { 5 | const dataExtractor = new DataExtractorApiImpl(); 6 | dataExtractor.registerDefaultExtractors(); 7 | const result = dataExtractor.getData( 8 | () => [1, 2, 3], 9 | () => null!, 10 | undefined 11 | ); 12 | }); 13 | }); 14 | -------------------------------------------------------------------------------- /data-extraction/test/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "esnext", 4 | "module": "commonjs", 5 | "strict": true, 6 | "skipLibCheck": true, 7 | "rootDir": "./", 8 | "resolveJsonModule": true, 9 | "declaration": true, 10 | "declarationMap": true, 11 | "newLine": "LF", 12 | "sourceMap": true, 13 | "noEmit": true 14 | }, 15 | "include": ["./**/*"] 16 | } 17 | -------------------------------------------------------------------------------- /data-extraction/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ES2018", 4 | "module": "commonjs", 5 | "strict": true, 6 | "outDir": "./dist", 7 | "skipLibCheck": true, 8 | "rootDir": "./src", 9 | "resolveJsonModule": true, 10 | "declaration": true, 11 | "declarationMap": true, 12 | "newLine": "LF", 13 | "sourceMap": true 14 | }, 15 | "include": ["./src/**/*"] 16 | } 17 | -------------------------------------------------------------------------------- /data-extraction/webpack.config.ts: -------------------------------------------------------------------------------- 1 | import { CleanWebpackPlugin } from "clean-webpack-plugin"; 2 | import * as path from "path"; 3 | import * as webpack from "webpack"; 4 | 5 | const r = (file: string) => path.resolve(__dirname, file); 6 | 7 | module.exports = { 8 | target: "node", 9 | entry: r("./src/index"), 10 | output: { 11 | path: r("./dist"), 12 | filename: "index.js", 13 | libraryTarget: "commonjs2", 14 | devtoolModuleFilenameTemplate: "../[resource-path]", 15 | }, 16 | devtool: "source-map", 17 | resolve: { 18 | extensions: [".ts", ".js"], 19 | }, 20 | module: { 21 | rules: [ 22 | { 23 | test: /\.ts$/, 24 | exclude: /node_modules/, 25 | use: [ 26 | { 27 | loader: "ts-loader", 28 | }, 29 | ], 30 | }, 31 | ], 32 | }, 33 | node: { 34 | __dirname: false, 35 | __filename: false, 36 | }, 37 | plugins: [ 38 | new CleanWebpackPlugin({ 39 | //protectWebpackAssets: true, 40 | cleanAfterEveryBuildPatterns: ["!**/*.d.ts", "!**/*.d.ts.map"], 41 | }), 42 | ], 43 | } as webpack.Configuration; 44 | -------------------------------------------------------------------------------- /demos/cpp/.gitignore: -------------------------------------------------------------------------------- 1 | main -------------------------------------------------------------------------------- /demos/cpp/.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | // Use IntelliSense to learn about possible attributes. 3 | // Hover to view descriptions of existing attributes. 4 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 5 | "version": "0.2.0", 6 | "configurations": [ 7 | { 8 | "name": "g++ build and debug active file", 9 | "type": "cppdbg", 10 | "request": "launch", 11 | "program": "${fileDirname}/${fileBasenameNoExtension}", 12 | "args": [], 13 | "stopAtEntry": false, 14 | "cwd": "${workspaceFolder}", 15 | "environment": [], 16 | "externalConsole": false, 17 | "MIMode": "gdb", 18 | "setupCommands": [ 19 | { 20 | "description": "Enable pretty-printing for gdb", 21 | "text": "-enable-pretty-printing", 22 | "ignoreFailures": true 23 | } 24 | ], 25 | "preLaunchTask": "g++ build active file", 26 | "miDebuggerPath": "/usr/bin/gdb" 27 | } 28 | ] 29 | } -------------------------------------------------------------------------------- /demos/cpp/.vscode/tasks.json: -------------------------------------------------------------------------------- 1 | { 2 | // See https://go.microsoft.com/fwlink/?LinkId=733558 3 | // for the documentation about the tasks.json format 4 | "version": "2.0.0", 5 | "tasks": [ 6 | { 7 | "type": "shell", 8 | "label": "g++ build active file", 9 | "command": "/usr/bin/g++", 10 | "args": [ 11 | "-g", 12 | "${file}", 13 | "-o", 14 | "${fileDirname}/${fileBasenameNoExtension}" 15 | ], 16 | "options": { 17 | "cwd": "/usr/bin" 18 | }, 19 | "problemMatcher": [ 20 | "$gcc" 21 | ], 22 | "group": "build" 23 | } 24 | ] 25 | } -------------------------------------------------------------------------------- /demos/cpp/main.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | using namespace std; 6 | 7 | int main() 8 | { 9 | // visualize `myGraphJson`! 10 | string myGraphJson = "{\"kind\":{\"graph\":true}," 11 | "\"nodes\":[{\"id\":\"1\"},{\"id\":\"2\"}]," 12 | "\"edges\":[{\"from\":\"1\",\"to\":\"2\"}]}"; 13 | cout << myGraphJson; 14 | } -------------------------------------------------------------------------------- /demos/csharp/.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | // Use IntelliSense to find out which attributes exist for C# debugging 3 | // Use hover for the description of the existing attributes 4 | // For further information visit https://github.com/OmniSharp/omnisharp-vscode/blob/master/debugger-launchjson.md 5 | "version": "0.2.0", 6 | "configurations": [ 7 | { 8 | "name": "C# - Demo: Linked List", 9 | "type": "coreclr", 10 | "request": "launch", 11 | "preLaunchTask": "csharp-build", 12 | "program": "${workspaceFolder}/bin/Debug/netcoreapp3.1/demo.dll", 13 | "args": [], 14 | "cwd": "${workspaceFolder}", 15 | "console": "internalConsole", 16 | "stopAtEntry": false 17 | } 18 | ] 19 | } 20 | -------------------------------------------------------------------------------- /demos/csharp/.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "debugVisualizer.debugAdapterConfigurations": {} 3 | } 4 | -------------------------------------------------------------------------------- /demos/csharp/.vscode/tasks.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "2.0.0", 3 | "tasks": [ 4 | { 5 | "label": "csharp-build", 6 | "command": "dotnet", 7 | "type": "process", 8 | "args": [ 9 | "build", 10 | "${workspaceFolder}/csharp/demo.csproj", 11 | "/property:GenerateFullPaths=true", 12 | "/consoleloggerparameters:NoSummary" 13 | ], 14 | "problemMatcher": "$msCompile" 15 | } 16 | ] 17 | } 18 | -------------------------------------------------------------------------------- /demos/csharp/Program.cs: -------------------------------------------------------------------------------- 1 |  2 | // Install dotnet core clr see VS Code docs on how to setup VS Code so that you can debug C# applications. 3 | // The Debug Visualizer does not support C# data extractors yet. 4 | // If you want to visualize a value, it's `ToString` method must return supported json data. 5 | // See the readme of the extension for supported json schemas. 6 | 7 | #nullable enable 8 | 9 | using System.Linq; 10 | using Hediet.DebugVisualizer.ExtractedData; 11 | 12 | namespace Demo 13 | { 14 | class Program 15 | { 16 | static void Main(string[] args) 17 | { 18 | var list = new LinkedList(); 19 | // visualize `list.Visualize()` here! 20 | list.Append(1); 21 | list.Append(2); 22 | list.Append(3); 23 | list.Append(4); 24 | } 25 | } 26 | 27 | class LinkedList 28 | { 29 | class Node 30 | { 31 | public Node(T value) { Value = value; } 32 | public T Value { get; set; } 33 | public Node? Next { get; set; } 34 | } 35 | 36 | private Node? Head { get; set; } 37 | 38 | public void Append(T item) 39 | { 40 | if (this.Head == null) 41 | { 42 | this.Head = new Node(item); 43 | } 44 | else 45 | { 46 | var cur = this.Head; 47 | while (cur.Next != null) 48 | { 49 | cur = cur.Next; 50 | } 51 | cur.Next = new Node(item); 52 | } 53 | } 54 | 55 | public string Visualize() 56 | { 57 | var list = new Node(default(T)!) { Next = this.Head }; 58 | return GraphData.From(new[] { list }, (item, info) => 59 | { 60 | info.Id = item == list ? "List" : string.Format("{0}", item.Value); 61 | if (item == list) 62 | { 63 | info.Color = "orange"; 64 | } 65 | if (item.Next != null) 66 | info.AddEdge(item.Next!, label: item == list ? "head" : "next"); 67 | return info; 68 | }).ToString(); 69 | } 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /demos/csharp/demo.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Exe 5 | netcoreapp3.1 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /demos/dart/debug_visualizers.dart: -------------------------------------------------------------------------------- 1 | import 'dart:convert'; 2 | 3 | String graph(List nodes, List edges) { 4 | return jsonEncode({ 5 | 'kind': {'graph': true}, 6 | 'nodes': nodes, 7 | 'edges': edges, 8 | }); 9 | } 10 | 11 | String plotly(List values) { 12 | return jsonEncode({ 13 | 'kind': {'plotly': true}, 14 | 'data': [ 15 | {'y': values} 16 | ], 17 | }); 18 | } 19 | 20 | String tree(TreeNode root) { 21 | return jsonEncode({ 22 | 'kind': {'tree': true}, 23 | 'root': root, 24 | }); 25 | } 26 | 27 | class Graph { 28 | List nodes; 29 | List edges; 30 | } 31 | 32 | class GraphEdge { 33 | final String from; 34 | final String to; 35 | final String label; 36 | 37 | GraphEdge(this.from, this.to, this.label); 38 | 39 | Map toJson() => { 40 | 'from': from, 41 | 'to': to, 42 | 'label': label, 43 | }; 44 | } 45 | 46 | class GraphNode { 47 | final String id; 48 | final String label; 49 | 50 | GraphNode( 51 | this.id, 52 | this.label, 53 | ); 54 | 55 | Map toJson() => { 56 | 'id': id, 57 | 'label': label, 58 | }; 59 | } 60 | 61 | class TreeNode { 62 | final List children; 63 | final List items; 64 | final String segment; 65 | final bool isMarked; 66 | 67 | TreeNode(this.children, this.items, this.segment, [this.isMarked = false]); 68 | 69 | Map toJson() => { 70 | 'children': children ?? [], 71 | 'items': items, 72 | 'segment': segment, 73 | 'isMarked': isMarked, 74 | }; 75 | } 76 | 77 | class TreeNodeItem { 78 | final String text; 79 | 80 | TreeNodeItem(this.text); 81 | 82 | Map toJson() => { 83 | 'text': text, 84 | }; 85 | } 86 | -------------------------------------------------------------------------------- /demos/dart/demo.dart: -------------------------------------------------------------------------------- 1 | import 'dart:collection'; 2 | import 'dart:developer'; 3 | import 'dart:math'; 4 | 5 | // ignore: unused_import 6 | import 'debug_visualizers.dart'; 7 | 8 | final _rnd = Random(); 9 | 10 | String Function() visualize; 11 | 12 | void main() { 13 | debugger(); 14 | // Open a Debug Visualizer window and evalute the expression "visualize()" 15 | // and then press Continue to step through the samples. 16 | plotly_demo(); 17 | graph_demo(); 18 | tree_demo(); 19 | } 20 | 21 | void plotly_demo() { 22 | final values = []; 23 | visualize = () => plotly(values); 24 | for (var i = 0; i < 20; i++) { 25 | debugger(); 26 | values.add(i + _rnd.nextInt(5) - 2); 27 | } 28 | } 29 | 30 | void graph_demo() { 31 | graphFromDoubleLinked( 32 | DoubleLinkedQueue values, String Function(T) toString) { 33 | node(T s) => GraphNode(toString(s), toString(s)); 34 | edge(DoubleLinkedQueueEntry e) => [ 35 | if (e.nextEntry() != null) 36 | GraphEdge( 37 | toString(e.element), 38 | toString(e.nextEntry().element), 39 | "next", 40 | ), 41 | if (e.previousEntry() != null) 42 | GraphEdge( 43 | toString(e.element), 44 | toString(e.previousEntry().element), 45 | "prev", 46 | ), 47 | ]; 48 | 49 | final nodes = values.map(node).toList(); 50 | final edges = []; 51 | values.forEachEntry((e) => edges.addAll(edge(e))); 52 | return graph(nodes, edges); 53 | } 54 | 55 | final values = DoubleLinkedQueue(); 56 | visualize = () => graphFromDoubleLinked(values, (s) => s); 57 | 58 | for (var i = 0; i < 10; i++) { 59 | debugger(); 60 | values.addLast('Node $i'); 61 | } 62 | } 63 | 64 | void tree_demo() { 65 | var nodeNum = 1; 66 | TreeNode createNode(int levels) { 67 | final name = 'Node ${nodeNum++}'; 68 | final children = 69 | levels > 0 ? List.generate(2, (_) => createNode(levels - 1)) : null; 70 | return TreeNode(children, [TreeNodeItem(name)], name); 71 | } 72 | 73 | var levels = 0; 74 | visualize = () { 75 | nodeNum = 1; 76 | return tree(createNode(levels)); 77 | }; 78 | 79 | for (var i = 1; i < 5; i++) { 80 | debugger(); 81 | levels = i; 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /demos/golang/.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "0.2.0", 3 | "configurations": [ 4 | { 5 | "name": "Launch", 6 | "type": "go", 7 | "request": "launch", 8 | "mode": "auto", 9 | "program": "${fileDirname}", 10 | "env": {}, 11 | "args": [], 12 | "dlvLoadConfig": { 13 | "followPointers": true, 14 | "maxVariableRecurse": 1, 15 | "maxStringLen": 5000, 16 | "maxArrayValues": 64, 17 | "maxStructFields": -1 18 | } 19 | } 20 | ] 21 | } -------------------------------------------------------------------------------- /demos/golang/demo.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "encoding/json" 5 | "math/rand" 6 | "strconv" 7 | "time" 8 | ) 9 | 10 | // If you want to visualize large data structures, 11 | // you need to increase Delve's maxStringLen. 12 | // (See here https://github.com/microsoft/vscode-go/issues/868 for more info) 13 | // You can do this by adding the following configuration to your launch.json: 14 | // "dlvLoadConfig": { 15 | // "followPointers": true, 16 | // "maxVariableRecurse": 1, 17 | // "maxStringLen": 5000, 18 | // "maxArrayValues": 64, 19 | // "maxStructFields": -1 20 | // } 21 | // For debugging tests, you can set the maxStringLen in settings.json like this: 22 | // "go.delveConfig": { 23 | // "dlvLoadConfig": { 24 | // "followPointers": true, 25 | // "maxVariableRecurse": 1, 26 | // "maxStringLen": 5000, 27 | // "maxArrayValues": 64, 28 | // "maxStructFields": -1 29 | // }, 30 | // "apiVersion": 2, 31 | // "showGlobalVariables": true 32 | // } 33 | 34 | // Open a new Debug Visualizer and visualize "s" 35 | func main() { 36 | rand.Seed(time.Now().UnixNano()) 37 | graph := NewGraph() 38 | var s string 39 | for i := 0; i < 100; i++ { 40 | id := strconv.Itoa(i) 41 | graph.Nodes = append(graph.Nodes, NodeGraphData{ 42 | ID: id, 43 | Label: id, 44 | }) 45 | if i > 0 { 46 | targetId := rand.Intn(i) 47 | graph.Edges = append(graph.Edges, EdgeGraphData{ 48 | From: id, 49 | To: strconv.Itoa(targetId), 50 | }) 51 | } 52 | s = graph.toString() 53 | _ = s 54 | //fmt.Printf("%s", s) 55 | } 56 | } 57 | 58 | type Graph struct { 59 | Kind map[string]bool `json:"kind"` 60 | Nodes []NodeGraphData `json:"nodes"` 61 | Edges []EdgeGraphData `json:"edges"` 62 | } 63 | 64 | type NodeGraphData struct { 65 | ID string `json:"id"` 66 | Label string `json:"label,omitempty"` 67 | Color string `json:"color,omitempty"` 68 | Shape string `json:"shape,omitempty"` 69 | } 70 | 71 | type EdgeGraphData struct { 72 | From string `json:"from"` 73 | To string `json:"to"` 74 | Label string `json:"label,omitempty"` 75 | ID string `json:"id"` 76 | Color string `json:"color,omitempty"` 77 | Dashes bool `json:"dashes,omitempty"` 78 | } 79 | 80 | func NewGraph() *Graph { 81 | return &Graph{ 82 | Kind: map[string]bool{"graph": true}, 83 | Nodes: []NodeGraphData{}, 84 | Edges: []EdgeGraphData{}, 85 | } 86 | } 87 | 88 | func (this *Graph) toString() string { 89 | rs, _ := json.Marshal(this) 90 | return string(rs) 91 | } -------------------------------------------------------------------------------- /demos/golang/go.mod: -------------------------------------------------------------------------------- 1 | module demo 2 | 3 | go 1.13 4 | -------------------------------------------------------------------------------- /demos/java/.classpath: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /demos/java/.gitignore: -------------------------------------------------------------------------------- 1 | # Compiled class file 2 | *.class 3 | 4 | # Log file 5 | *.log 6 | 7 | # BlueJ files 8 | *.ctxt 9 | 10 | # Mobile Tools for Java (J2ME) 11 | .mtj.tmp/ 12 | 13 | # Package Files # 14 | *.jar 15 | *.war 16 | *.nar 17 | *.ear 18 | *.zip 19 | *.tar.gz 20 | *.rar 21 | 22 | # virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml 23 | hs_err_pid* 24 | 25 | !lib/** -------------------------------------------------------------------------------- /demos/java/.project: -------------------------------------------------------------------------------- 1 | 2 | 3 | java 4 | 5 | 6 | 7 | 8 | 9 | org.eclipse.jdt.core.javabuilder 10 | 11 | 12 | 13 | 14 | 15 | org.eclipse.jdt.core.javanature 16 | 17 | 18 | 19 | 1599063072264 20 | 21 | 30 22 | 23 | org.eclipse.core.resources.regexFilterMatcher 24 | node_modules|.git|__CREATED_BY_JAVA_LANGUAGE_SERVER__ 25 | 26 | 27 | 28 | 29 | -------------------------------------------------------------------------------- /demos/java/.settings/org.eclipse.jdt.core.prefs: -------------------------------------------------------------------------------- 1 | eclipse.preferences.version=1 2 | org.eclipse.jdt.core.compiler.codegen.inlineJsrBytecode=enabled 3 | org.eclipse.jdt.core.compiler.codegen.targetPlatform=11 4 | org.eclipse.jdt.core.compiler.codegen.unusedLocal=preserve 5 | org.eclipse.jdt.core.compiler.compliance=10 6 | org.eclipse.jdt.core.compiler.debug.lineNumber=generate 7 | org.eclipse.jdt.core.compiler.debug.localVariable=generate 8 | org.eclipse.jdt.core.compiler.debug.sourceFile=generate 9 | org.eclipse.jdt.core.compiler.problem.assertIdentifier=error 10 | org.eclipse.jdt.core.compiler.problem.enumIdentifier=error 11 | org.eclipse.jdt.core.compiler.source=11 12 | -------------------------------------------------------------------------------- /demos/java/.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | // Use IntelliSense to find out which attributes exist for C# debugging 3 | // Use hover for the description of the existing attributes 4 | // For further information visit https://github.com/OmniSharp/omnisharp-vscode/blob/master/debugger-launchjson.md 5 | "version": "0.2.0", 6 | "configurations": [ 7 | { 8 | "type": "java", 9 | "name": "CodeLens (Launch) - DemoLinkedLists", 10 | "request": "launch", 11 | "mainClass": "DemoLinkedLists" 12 | }, 13 | { 14 | "name": "Java - Demo", 15 | "type": "java", 16 | "request": "launch", 17 | "projectName": "java" 18 | } 19 | ] 20 | } 21 | -------------------------------------------------------------------------------- /demos/java/.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "java.dependency.packagePresentation": "flat", 3 | "java.dependency.syncWithFolderExplorer": true, 4 | "debugVisualizer.debugAdapterConfigurations": {} 5 | } 6 | -------------------------------------------------------------------------------- /demos/java/lib/jackson-annotations-2.9.8.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hediet/vscode-debug-visualizer/96c26e5388eda9ed81a488f1d95a26a9af166214/demos/java/lib/jackson-annotations-2.9.8.jar -------------------------------------------------------------------------------- /demos/java/lib/jackson-core-2.9.8.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hediet/vscode-debug-visualizer/96c26e5388eda9ed81a488f1d95a26a9af166214/demos/java/lib/jackson-core-2.9.8.jar -------------------------------------------------------------------------------- /demos/java/lib/jackson-databind-2.9.8.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hediet/vscode-debug-visualizer/96c26e5388eda9ed81a488f1d95a26a9af166214/demos/java/lib/jackson-databind-2.9.8.jar -------------------------------------------------------------------------------- /demos/java/src/app/App.java: -------------------------------------------------------------------------------- 1 | package app; 2 | 3 | import app.GraphData.EdgeInfo; 4 | 5 | public class App { 6 | 7 | public static void main(String[] args) { 8 | // watch `list.visualize()` 9 | LinkedList list = new LinkedList(); 10 | list.append("1"); 11 | list.append("2"); 12 | list.append("3"); 13 | list.append("4"); 14 | } 15 | } 16 | 17 | class LinkedList { 18 | private Node head; 19 | 20 | public void append(T value) { 21 | if (head == null) { 22 | head = new Node(value); 23 | } else { 24 | Node m = head; 25 | while (m.next != null) { 26 | m = m.next; 27 | } 28 | m.next = new Node(value); 29 | } 30 | } 31 | 32 | public String visualize() { 33 | Node list = new Node(null); 34 | list.next = this.head; 35 | 36 | return GraphData.from(list, (item, info) -> { 37 | if (item != list) { 38 | info.id = item.value.toString(); 39 | } else { 40 | info.label = "list"; 41 | } 42 | if (item.next != null) { 43 | EdgeInfo> ei = info.addEdge(item.next); 44 | ei.label = "next"; 45 | } 46 | return info; 47 | }).toString(); 48 | } 49 | 50 | static class Node { 51 | public T value; 52 | public Node next; 53 | 54 | public Node(T value) { 55 | this.value = value; 56 | } 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /demos/java/src/app/ExtractedData.java: -------------------------------------------------------------------------------- 1 | package app; 2 | 3 | import java.util.HashMap; 4 | 5 | import com.fasterxml.jackson.annotation.JsonIgnore; 6 | import com.fasterxml.jackson.core.JsonProcessingException; 7 | import com.fasterxml.jackson.databind.ObjectMapper; 8 | 9 | public abstract class ExtractedData { 10 | public HashMap getKind() { 11 | HashMap m = new HashMap<>(); 12 | for (String s : getTags()) { 13 | m.put(s, true); 14 | } 15 | return m; 16 | } 17 | 18 | @JsonIgnore 19 | protected abstract String[] getTags(); 20 | 21 | @Override 22 | public String toString() { 23 | ObjectMapper objectMapper = new ObjectMapper(); 24 | try { 25 | String val = objectMapper.writeValueAsString(this); 26 | return val; 27 | } catch (JsonProcessingException e) { 28 | return "JsonProcessingException"; 29 | } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /demos/java/src/app/TextData.java: -------------------------------------------------------------------------------- 1 | package app; 2 | 3 | public class TextData extends ExtractedData { 4 | private final String textValue; 5 | 6 | public TextData(String text) { 7 | this.textValue = text; 8 | } 9 | 10 | @Override 11 | protected String[] getTags() { 12 | return new String[] { "text" }; 13 | } 14 | 15 | public String getText() { 16 | return this.textValue; 17 | } 18 | } -------------------------------------------------------------------------------- /demos/js/.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "editor.minimap.enabled": false, 3 | "git.enabled": false, 4 | "typescript.tsc.autoDetect": "off", 5 | "debugVisualizer.js.customScriptPaths": [ 6 | "${workspaceFolder}/custom-visualizer.js", 7 | ] 8 | } -------------------------------------------------------------------------------- /demos/js/.vscode/tasks.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "2.0.0", 3 | "tasks": [ 4 | { 5 | "type": "npm", 6 | "script": "dev", 7 | "problemMatcher": "$tsc-watch", 8 | "isBackground": true, 9 | "presentation": { 10 | "reveal": "never" 11 | }, 12 | "group": { 13 | "kind": "build", 14 | "isDefault": true 15 | } 16 | } 17 | ] 18 | } 19 | -------------------------------------------------------------------------------- /demos/js/README.md: -------------------------------------------------------------------------------- 1 | # JS Demos 2 | 3 | Simply run `yarn` and select the launch target you want to debug in the debugger pane of VS Code. 4 | -------------------------------------------------------------------------------- /demos/js/custom-visualizer.js: -------------------------------------------------------------------------------- 1 | // @ts-check 2 | /** 3 | * @type {import("@hediet/debug-visualizer-data-extraction").LoadDataExtractorsFn} 4 | */ 5 | module.exports = (register, helpers) => { 6 | register({ 7 | id: "map", 8 | getExtractions(data, collector, context) { 9 | if (!(data instanceof Map)) { 10 | return; 11 | } 12 | 13 | collector.addExtraction({ 14 | priority: 1000, 15 | id: "map", 16 | name: "Map", 17 | extractData() { 18 | return helpers.asData({ 19 | kind: { table: true }, 20 | rows: [...data].map(([k, v]) => ({ key: k, value: v })) 21 | }); 22 | }, 23 | }); 24 | }, 25 | }); 26 | 27 | register({ 28 | id: "binaryViewer", 29 | getExtractions(data, collector, context) { 30 | if (typeof data !== "number") { 31 | return; 32 | } 33 | /* 34 | context.addCallFrameRequest({ 35 | methodName: "Module._load", 36 | pathRegExp: ".*", 37 | }); 38 | 39 | context.addCallFrameRequest({ 40 | methodName: "LinkedList.insertAt", 41 | pathRegExp: ".*", 42 | });*/ 43 | 44 | collector.addExtraction({ 45 | priority: 10000, 46 | id: "binary", 47 | name: "Bits of Integer", 48 | extractData() { 49 | return context.extract(JSON.stringify(context.callFrameInfos, undefined, 4)); 50 | 51 | return helpers.asData({ 52 | kind: { text: true }, 53 | text: data.toString(2), 54 | }); 55 | }, 56 | }); 57 | }, 58 | }); 59 | }; 60 | -------------------------------------------------------------------------------- /demos/js/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "demo", 3 | "version": "1.0.0", 4 | "license": "MIT", 5 | "dependencies": { 6 | "@hediet/node-reload": "0.7.3", 7 | "@types/node": "^13.7.4", 8 | "node-fetch": "^2.6.1", 9 | "typescript": "^3.8.2" 10 | }, 11 | "scripts": { 12 | "dev": "tsc --watch" 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /demos/js/src/MockLanguageServiceHost.ts: -------------------------------------------------------------------------------- 1 | import * as ts from "typescript"; 2 | 3 | export class MockLanguageServiceHost implements ts.LanguageServiceHost { 4 | constructor( 5 | private readonly files: Map, 6 | private readonly compilationSettings: ts.CompilerOptions 7 | ) {} 8 | 9 | getScriptFileNames(): string[] { 10 | return [...this.files.keys()]; 11 | } 12 | 13 | getScriptVersion(fileName: string): string { 14 | return ""; // our files don't change 15 | } 16 | 17 | getScriptSnapshot(fileName: string): ts.IScriptSnapshot | undefined { 18 | const content = this.files.get(fileName); 19 | if (!content) { 20 | return undefined; 21 | } 22 | return { 23 | dispose() {}, 24 | getChangeRange: () => undefined, 25 | getLength: () => content.length, 26 | getText: (start, end) => content.substr(start, end - start), 27 | }; 28 | } 29 | 30 | getCompilationSettings(): ts.CompilerOptions { 31 | return this.compilationSettings; 32 | } 33 | getCurrentDirectory(): string { 34 | return "/"; 35 | } 36 | getDefaultLibFileName(options: ts.CompilerOptions): string { 37 | return ts.getDefaultLibFileName(options); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /demos/js/src/demo_custom-data-extractor.ts: -------------------------------------------------------------------------------- 1 | import { getDataExtractorApi } from "@hediet/debug-visualizer-data-extraction"; 2 | 3 | // Registers all existing extractors. 4 | getDataExtractorApi().registerDefaultExtractors(); 5 | 6 | getDataExtractorApi().registerExtractor({ 7 | id: "my-extractor", 8 | getExtractions: (data, collector) => { 9 | if (data instanceof Foo) { 10 | collector.addExtraction({ 11 | id: "my-extractor", 12 | name: "My Extractor", 13 | priority: 2000, 14 | extractData: () => ({ kind: { text: true }, text: "Foo" }), 15 | }); 16 | } 17 | }, 18 | }); 19 | 20 | setTimeout(() => { 21 | new Main().run(); 22 | }, 0); 23 | 24 | class Foo {} 25 | 26 | class Main { 27 | run() { 28 | const f = new Foo(); 29 | // visualize `f` here! 30 | debugger; 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /demos/js/src/demo_doubly-linked-list.ts: -------------------------------------------------------------------------------- 1 | import { 2 | getDataExtractorApi, 3 | createGraphFromPointers, 4 | } from "@hediet/debug-visualizer-data-extraction"; 5 | 6 | getDataExtractorApi().registerDefaultExtractors(); 7 | 8 | setTimeout(() => { 9 | new Main().run(); 10 | }, 0); 11 | 12 | class Main { 13 | /** @pure */ 14 | run() { 15 | id = 0; 16 | const head = new DoublyLinkedListNode("1"); 17 | head.setNext(new DoublyLinkedListNode("2")); 18 | head.next!.setNext(new DoublyLinkedListNode("3")); 19 | const tail = new DoublyLinkedListNode("4"); 20 | head.next!.next!.setNext(tail); 21 | reverse(new DoublyLinkedList(head, tail)); 22 | console.log("finished"); 23 | } 24 | } 25 | 26 | function reverse(list: DoublyLinkedList) { 27 | // Open a new Debug Visualizer 28 | // and enter `visualize()`! 29 | const visualize = () => 30 | createGraphFromPointers( 31 | { 32 | last, 33 | "list.head": list.head, 34 | "list.tail": list.tail 35 | }, 36 | i => ({ 37 | id: i.id, 38 | label: i.name, 39 | color: finished.has(i) ? "lime" : "lightblue", 40 | edges: [ 41 | { to: i.next!, label: "next", color: "lightblue", }, 42 | { to: i.prev!, label: "prev", color: "lightgray" }, 43 | ].filter(r => !!r.to), 44 | })); 45 | 46 | // Finished nodes have correct pointers, 47 | // their next node is also finished. 48 | const finished = new Set(); 49 | var last: DoublyLinkedListNode | null = null; 50 | list.tail = list.head; 51 | while (list.head) { 52 | list.head.prev = list.head.next; 53 | list.head.next = last; 54 | finished.add(list.head); 55 | last = list.head; 56 | list.head = list.head.prev; 57 | } 58 | list.head = last; 59 | } 60 | 61 | class DoublyLinkedList { 62 | constructor( 63 | public head: DoublyLinkedListNode | null, 64 | public tail: DoublyLinkedListNode | null 65 | ) { } 66 | } 67 | 68 | let id = 0; 69 | class DoublyLinkedListNode { 70 | public readonly id = (id++).toString(); 71 | constructor(public name: string) { } 72 | 73 | next: DoublyLinkedListNode | null = null; 74 | prev: DoublyLinkedListNode | null = null; 75 | 76 | public setNext(val: DoublyLinkedListNode): void { 77 | val.prev = this; 78 | this.next = val; 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /demos/js/src/demo_fetch.js: -------------------------------------------------------------------------------- 1 | const fetch = require("node-fetch"); 2 | 3 | fetch("https://jsonplaceholder.typicode.com/users") 4 | .then((response) => response.json()) 5 | .then((json) => { 6 | debugger; 7 | }); 8 | -------------------------------------------------------------------------------- /demos/js/src/demo_random-walks.ts: -------------------------------------------------------------------------------- 1 | // visualize `data` 2 | const data = new Array(); 3 | let curValue = 0; 4 | 5 | for (let j = 0; j < 100; j++) { 6 | addManyRandomValues(); 7 | } 8 | 9 | function addManyRandomValues() { 10 | for (let i = 0; i < 100; i++) { 11 | addRandomValue(); 12 | } 13 | } 14 | 15 | function addRandomValue() { 16 | const delta = Math.random() 17 | > 0.5 ? 1 : -1; 18 | data.push(curValue); 19 | curValue += delta; 20 | } -------------------------------------------------------------------------------- /demos/js/src/demo_singly-linked-list.js: -------------------------------------------------------------------------------- 1 | // See https://codeburst.io/linked-lists-in-javascript-es6-code-part-1-6dd349c3dcc3 2 | /* 3 | After install the Debug Visualizer extension, 4 | press F1, enter "Open Debug Visualizer" and 5 | use the following code as expression to visualize. 6 | Then, press F5 and chose "node". 7 | ``` 8 | hedietDbgVis.createGraphFromPointers( 9 | hedietDbgVis.tryEval([ 10 | "list.head", 11 | "newNode", 12 | "node", 13 | "previous", 14 | this.constructor.name === "LinkedList" ? "this.head" : "err", 15 | ]), 16 | n => ({ 17 | id: n.data, 18 | color: "lightblue", 19 | label: `${n.data}`, 20 | edges: [{ to: n.next, label: "next" }].filter(i => !!i.to), 21 | }) 22 | ) 23 | ``` 24 | 25 | */ 26 | 27 | class LinkedList { 28 | constructor() { 29 | this.head = null; 30 | } 31 | } 32 | 33 | class Node { 34 | constructor(data, next = null) { 35 | (this.data = data), (this.next = next); 36 | } 37 | } 38 | 39 | LinkedList.prototype.insertAtBeginning = function(data) { 40 | // A newNode object is created with property data 41 | // and next = null 42 | let newNode = new Node(data); 43 | // The pointer next is assigned head pointer 44 | // so that both pointers now point at the same node. 45 | newNode.next = this.head; 46 | // As we are inserting at the beginning the head pointer 47 | // needs to now point at the newNode. 48 | this.head = newNode; 49 | return this.head; 50 | }; 51 | 52 | LinkedList.prototype.getAt = function(index) { 53 | let counter = 0; 54 | let node = this.head; 55 | while (node) { 56 | if (counter === index) { 57 | return node; 58 | } 59 | counter++; 60 | node = node.next; 61 | } 62 | return null; 63 | }; 64 | 65 | // The insertAt() function contains the steps to insert 66 | // a node at a given index. 67 | LinkedList.prototype.insertAt = function(data, index) { 68 | // if the list is empty i.e. head = null 69 | if (!this.head) { 70 | this.head = new Node(data); 71 | return; 72 | } 73 | // if new node needs to be inserted at the front 74 | // of the list i.e. before the head. 75 | if (index === 0) { 76 | this.head = new Node(data, this.head); 77 | return; 78 | } 79 | // else, use getAt() to find the previous node. 80 | const previous = this.getAt(index - 1); 81 | let newNode = new Node(data); 82 | newNode.next = previous.next; 83 | previous.next = newNode; 84 | 85 | return this.head; 86 | }; 87 | 88 | const list = new LinkedList(); 89 | debugger; // Press F10 to continue 90 | list.insertAtBeginning("4"); 91 | list.insertAtBeginning("3"); 92 | list.insertAtBeginning("2"); 93 | list.insertAtBeginning("1"); 94 | 95 | list.insertAt("3.5", 3); 96 | 97 | console.log("finished"); 98 | 99 | // Some Plotting. 100 | // Use `data` as expression to visualize. 101 | const data = []; 102 | for (let x = 0; x < 1000; x += 1) { 103 | data.push(Math.sin(x / 10)); 104 | 105 | if (x % 10 === 0) { 106 | console.log("step"); 107 | } 108 | } 109 | -------------------------------------------------------------------------------- /demos/js/src/demo_sorting.ts: -------------------------------------------------------------------------------- 1 | import { getDataExtractorApi } from "@hediet/debug-visualizer-data-extraction"; 2 | 3 | getDataExtractorApi().registerDefaultExtractors(); 4 | 5 | /* 6 | Visualize this expression: 7 | ```ts 8 | hedietDbgVis.markedGrid( 9 | array, 10 | hedietDbgVis.tryEval(["i", "j", "left", "right"]) 11 | ) 12 | ``` 13 | */ 14 | 15 | // From https://github.com/AvraamMavridis/Algorithms-Data-Structures-in-Typescript/blob/master/algorithms/quickSort.md 16 | /** @pure */ 17 | function main() { 18 | const array = [1, 2, 33, 31, 1, 2, 63, 123, 6, 32, 943, 346, 24]; 19 | const sorted = quickSort(array, 0, array.length - 1); 20 | console.log(sorted); 21 | } 22 | main(); 23 | 24 | function swap(array: Array, i: number, j: number) { 25 | [array[i], array[j]] = [array[j], array[i]]; 26 | } 27 | 28 | /** 29 | * Split array and swap values 30 | * 31 | * @param {Array} array 32 | * @param {number} [left=0] 33 | * @param {number} [right=array.length - 1] 34 | * @returns {number} 35 | */ 36 | function partition( 37 | array: Array, 38 | left: number = 0, 39 | right: number = array.length - 1 40 | ) { 41 | const pivot = Math.floor((right + left) / 2); 42 | const pivotVal = array[pivot]; 43 | let i = left; 44 | let j = right; 45 | 46 | while (i <= j) { 47 | while (array[i] < pivotVal) { 48 | i++; 49 | } 50 | 51 | while (array[j] > pivotVal) { 52 | j--; 53 | } 54 | 55 | if (i <= j) { 56 | swap(array, i, j); 57 | i++; 58 | j--; 59 | } 60 | } 61 | 62 | return i; 63 | } 64 | 65 | /** 66 | * Quicksort implementation 67 | * 68 | * @param {Array} array 69 | * @param {number} [left=0] 70 | * @param {number} [right=array.length - 1] 71 | * @returns {Array} 72 | */ 73 | function quickSort( 74 | array: Array, 75 | left: number = 0, 76 | right: number = array.length - 1 77 | ) { 78 | let index; 79 | 80 | if (array.length > 1) { 81 | index = partition(array, left, right); 82 | 83 | if (left < index - 1) { 84 | quickSort(array, left, index - 1); 85 | } 86 | 87 | if (index < right) { 88 | quickSort(array, index, right); 89 | } 90 | } 91 | 92 | return array; 93 | } 94 | -------------------------------------------------------------------------------- /demos/js/src/demo_stack-frames.js: -------------------------------------------------------------------------------- 1 | 2 | function test() { 3 | let i = 1; 4 | debugger; 5 | } 6 | 7 | let i = 0; 8 | test(); -------------------------------------------------------------------------------- /demos/js/src/demo_typescript-asts.ts: -------------------------------------------------------------------------------- 1 | import * as ts from "typescript"; 2 | import { getDataExtractorApi } from "@hediet/debug-visualizer-data-extraction"; 3 | import { MockLanguageServiceHost } from "./MockLanguageServiceHost"; 4 | 5 | // Registers all existing extractors. 6 | getDataExtractorApi().registerDefaultExtractors(); 7 | 8 | setTimeout(() => { 9 | new Main().run(); 10 | }, 0); 11 | 12 | class Main { 13 | run() { 14 | /*const files = new Map([ 15 | [ 16 | "main.ts", 17 | ` 18 | class Test1 { 19 | public foo(a: number) { 20 | const x = { a: 5 }; 21 | const y = { a: 5 }; 22 | } 23 | } 24 | `, 25 | ], 26 | ]); 27 | const serviceHost = new MockLanguageServiceHost(files, {}); 28 | const baseService = ts.createLanguageService( 29 | serviceHost, 30 | ts.createDocumentRegistry() 31 | ); 32 | const prog = baseService.getProgram()!; 33 | debugger; 34 | 35 | const c = prog.getTypeChecker(); 36 | let myValue = undefined; // Visualize `myValue` here! 37 | const sourceFileAst = prog.getSourceFiles()[0]; 38 | myValue = sourceFileAst; 39 | console.log("myValue is the source code of the AST"); 40 | debugger; 41 | 42 | myValue = { 43 | sf: sourceFileAst, 44 | fn: (n: ts.Node) => { 45 | try { 46 | const t = c.getTypeAtLocation(n); 47 | return t ? c.typeToString(t) : undefined; 48 | } catch (e) { 49 | return "" + e; 50 | } 51 | }, 52 | }; 53 | console.log("myValue is AST, annotated with type information"); 54 | debugger; 55 | 56 | myValue = { 57 | sf: sourceFileAst, 58 | fn: (n: ts.Node) => { 59 | try { 60 | const t = c.getSymbolAtLocation(n); 61 | return t ? ts.SymbolFlags[t.flags] : undefined; 62 | } catch (e) { 63 | return "" + e; 64 | } 65 | }, 66 | }; 67 | console.log("myValue is AST, annotated with symbol information"); 68 | debugger;*/ 69 | require; 70 | 71 | const sf = ts.createSourceFile("main.ts", 72 | ` 73 | class Test1 { 74 | public foo(a: number) { 75 | const x = { a: 5 }; 76 | const y = { a: 24 }; 77 | } 78 | } 79 | `, ts.ScriptTarget.ESNext, true); 80 | const traverse = (node: ts.Node) => { 81 | ts.forEachChild(node, child => { 82 | traverse(child); 83 | }); 84 | }; 85 | traverse(sf); 86 | } 87 | } 88 | 89 | /* 90 | myValue = { 91 | kind: { text: true, svg: true }, 92 | text: ` 93 | 94 | 98 | 99 | `, 100 | };*/ 101 | -------------------------------------------------------------------------------- /demos/js/src/playground.ts: -------------------------------------------------------------------------------- 1 | import { 2 | enableHotReload, 3 | registerUpdateReconciler, 4 | getReloadCount, 5 | hotCallExportedFunction, 6 | } from "@hediet/node-reload"; 7 | 8 | enableHotReload(); 9 | registerUpdateReconciler(module); 10 | 11 | import { registerDefaultExtractors } from "@hediet/debug-visualizer-data-extraction"; 12 | registerDefaultExtractors(); 13 | 14 | if (getReloadCount(module) === 0) { 15 | // set interval so that the file watcher can detect changes in other files. 16 | setInterval(() => { 17 | hotCallExportedFunction(module, run); 18 | }, 1000); 19 | } 20 | 21 | export function run() { 22 | new Demo().run(); 23 | } 24 | 25 | class Demo { 26 | private f = new Foo(new Foo(undefined)); 27 | private arr = [new Foo(this.f), this.f]; 28 | private set = new Set([this.arr[0], new Foo(new Foo(this.arr[1]))]); 29 | 30 | run() { 31 | debugger; 32 | } 33 | } 34 | 35 | class Foo { 36 | constructor(public readonly next: Foo | undefined) {} 37 | } 38 | -------------------------------------------------------------------------------- /demos/js/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "esnext", 4 | "module": "commonjs", 5 | "strict": true, 6 | "outDir": "dist", 7 | "skipLibCheck": true, 8 | "rootDir": "./src", 9 | "resolveJsonModule": true, 10 | "declaration": true, 11 | "declarationMap": true, 12 | "newLine": "LF", 13 | "sourceMap": true, 14 | "experimentalDecorators": true 15 | }, 16 | "include": ["src/**/*"] 17 | } 18 | -------------------------------------------------------------------------------- /demos/nim/.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "0.2.0", 3 | "configurations": [ 4 | { 5 | "type": "lldb", 6 | "request": "launch", 7 | "name": "Run your Executable", 8 | "program": "${workspaceFolder}/main", 9 | "args": [], 10 | "cwd": "${workspaceFolder}", 11 | "initCommands": [ 12 | // To let LLDB know that NCSTRING is just like a C string 13 | "type format add --format c-string NCSTRING" 14 | ], 15 | "preLaunchTask": "build" 16 | } 17 | ] 18 | } -------------------------------------------------------------------------------- /demos/nim/.vscode/tasks.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "2.0.0", 3 | "tasks": [ 4 | { 5 | "label": "build", 6 | "type": "shell", 7 | "command": "nim c main.nim" 8 | } 9 | ] 10 | } -------------------------------------------------------------------------------- /demos/nim/LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Danil Yarantsev 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /demos/nim/main.nim: -------------------------------------------------------------------------------- 1 | import random, json, options, sugar 2 | 3 | type 4 | Kind = object 5 | array: bool 6 | 7 | Label = object 8 | label: Option[string] 9 | 10 | Column = object 11 | content: Option[string] 12 | tag: Option[string] 13 | color: Option[string] 14 | 15 | Row = object 16 | label: Option[string] 17 | columns: seq[Column] 18 | 19 | Marker = object 20 | id: string 21 | row: int 22 | column: int 23 | rows: Option[int] 24 | columns: Option[int] 25 | label: Option[string] 26 | color: Option[string] 27 | 28 | Grid = object 29 | kind: Kind 30 | columnLabels: Option[seq[Label]] 31 | rows: seq[Row] 32 | markers: Option[seq[Marker]] 33 | 34 | proc showSeq[T](data: seq[T], markers: seq[(string, int)] = @[]): cstring = 35 | # Need to use cstring string type for C-compatible strings 36 | let labels = collect(newSeq): 37 | # Labels are indexes of the seq 38 | for x in 0 ..< data.len(): 39 | Label(label: some($x)) 40 | 41 | let columns = collect(newSeq): 42 | # Columns contain values of the seq 43 | for x in data: 44 | Column(content: some($x), tag: some($x)) 45 | 46 | let marks = collect(newSeq): 47 | # If there are any markers (id - string, index - int) 48 | for marker in markers: 49 | Marker(id: marker[0], row: 0, column: marker[1]) 50 | 51 | let grid = Grid( 52 | kind: Kind(array: true), 53 | rows: @[Row(columns: columns)], 54 | columnLabels: some(labels), 55 | markers: some(marks) 56 | ) 57 | # Serialize to JSON 58 | result = $ %grid 59 | 60 | proc main = 61 | # Random seed 62 | randomize() 63 | 64 | # Fill seq with 10 random values from 1 to 999 65 | var a = newSeq[int]() 66 | for x in 0..10: 67 | a.add rand(1..999) 68 | 69 | # Simple bubble sort with some markers 70 | # Open the visualizer and type in "ress" 71 | let n = a.len() - 1 72 | var ress = showSeq(a, @[("i", 0), ("j", 0)]) 73 | for i in 0 .. n: 74 | for j in 0 .. n - i - 1: 75 | if a[j] > a[j+1]: 76 | ress = showSeq(a, @[("i", i), ("j", j)]) # breakpoint 77 | let temp = a[j] 78 | a[j] = a[j + 1] 79 | a[j + 1] = temp 80 | ress = showSeq(a, @[("i", i), ("j", j)]) # breakpoint 81 | 82 | main() -------------------------------------------------------------------------------- /demos/nim/nim.cfg: -------------------------------------------------------------------------------- 1 | # For Nim source code lines 2 | debugger:native -------------------------------------------------------------------------------- /demos/php/.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | // Use IntelliSense to find out which attributes exist for C# debugging 3 | // Use hover for the description of the existing attributes 4 | // For further information visit https://github.com/OmniSharp/omnisharp-vscode/blob/master/debugger-launchjson.md 5 | "version": "0.2.0", 6 | "configurations": [ 7 | { 8 | "name": "PHP - Demo", 9 | "type": "php", 10 | "request": "launch", 11 | "program": "${workspaceFolder}/demo.php", 12 | "xdebugSettings": { 13 | "max_data": -1 14 | } 15 | } 16 | ] 17 | } 18 | -------------------------------------------------------------------------------- /demos/php/.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "debugVisualizer.debugAdapterConfigurations": { 3 | "php": { 4 | "context": "watch", 5 | "expressionTemplate": "${expr}" 6 | } 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /demos/php/demo.php: -------------------------------------------------------------------------------- 1 | ["graph" => true], 12 | "nodes" => [ 13 | ["id" => "1", "label" => "1"] 14 | ], 15 | "edges" => [] 16 | ]; 17 | // Visualize "$visualize()" 18 | $visualize = function () use (&$graph) { 19 | return json_encode($graph); 20 | }; 21 | 22 | for ($i = 2; $i < 100; $i++) { 23 | // add a node 24 | $id = "" . $i; 25 | array_push($graph["nodes"], 26 | ["id" => $id, "label" => $id]); 27 | // connects the node to a random edge 28 | $targetId = "" . random_int(1, $i - 1); 29 | array_push($graph["edges"], 30 | ["from" => $id, "to" => "" . $targetId]); 31 | } 32 | 33 | echo "finished"; 34 | -------------------------------------------------------------------------------- /demos/python/.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | // Use IntelliSense to find out which attributes exist for C# debugging 3 | // Use hover for the description of the existing attributes 4 | // For further information visit https://github.com/OmniSharp/omnisharp-vscode/blob/master/debugger-launchjson.md 5 | "version": "0.2.0", 6 | "configurations": [] 7 | } 8 | -------------------------------------------------------------------------------- /demos/python/Person.py: -------------------------------------------------------------------------------- 1 | class Person: 2 | def __init__(self, name, parents=None) -> None: 3 | self.name = name 4 | self.parents = [] if parents is None else parents 5 | 6 | def addParent(self, parent: "Person"): 7 | self.parents.append(parent) 8 | -------------------------------------------------------------------------------- /demos/python/debugvisualizer.py: -------------------------------------------------------------------------------- 1 | from Person import Person 2 | import json 3 | from vscodedebugvisualizer import globalVisualizationFactory 4 | 5 | 6 | class PersonVisualizer: 7 | def checkType(self, t): 8 | return isinstance(t, Person) 9 | 10 | def visualizePerson(self, person: Person, nodes=[], edges=[]): 11 | if person.name in [n["id"] for n in nodes]: 12 | return nodes, edges 13 | 14 | nodes.append( 15 | { 16 | "id": person.name, 17 | "label": person.name, 18 | } 19 | ) 20 | 21 | for p in person.parents: 22 | nodes, edges = self.visualizePerson(p, nodes, edges) 23 | edges.append( 24 | { 25 | "from": p.name, 26 | "to": person.name, 27 | } 28 | ) 29 | 30 | return nodes, edges 31 | 32 | def visualize(self, person: Person): 33 | jsonDict = { 34 | "kind": {"graph": True}, 35 | "nodes": [], 36 | "edges": [], 37 | } 38 | 39 | self.visualizePerson(person, jsonDict["nodes"], jsonDict["edges"]) 40 | 41 | return json.dumps(jsonDict) 42 | 43 | 44 | globalVisualizationFactory.addVisualizer(PersonVisualizer()) 45 | -------------------------------------------------------------------------------- /demos/python/demo.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | 3 | 4 | # put "x" in the Debug Visualizer and use step by step 5 | # debugging to see the diffrent types of data visualization 6 | 7 | 8 | x = 5 9 | x = "asdf" 10 | x = 5.5 11 | x = [1, 2, 3, 4, 5, 6, "a", "b"] 12 | x = ["b", "a", "c", "d", "e"] 13 | x.sort() 14 | x = { 15 | "asdf1": "dasdf", 16 | "asdf2": "dasdf", 17 | "asdf3": {"foo": "bar"}, 18 | } 19 | 20 | x = [1, 2, 3, 4, 5, 6] 21 | # histogram 22 | x = np.concatenate([np.arange(i) for i in range(9)]) 23 | x = x.reshape(2, -1) 24 | 25 | 26 | # one dimension 27 | x = np.arange(100) 28 | x = np.linspace(-np.pi, np.pi, 100) 29 | x = np.sin(x) 30 | 31 | # 2 dimension 32 | x = x.reshape(5, 20) 33 | # 2 dimension transpose 34 | x = x.transpose() 35 | x = x.transpose() 36 | 37 | # 3 dimensions 38 | x = x.reshape(2, 5, 10) 39 | 40 | # 4 dimensions 41 | x = x.reshape(2, 5, 2, 5) 42 | 43 | # big number 44 | x = np.empty(2 ** 24) 45 | x[0:1000000] = 1 46 | x[1000000:10000000] = 5 47 | 48 | # pyTorch 49 | try: 50 | import torch 51 | 52 | x = np.concatenate([np.arange(i) for i in range(9)]) 53 | x = x.reshape(2, -1) 54 | x = torch.Tensor(x) 55 | pass 56 | 57 | except ImportError: 58 | pass 59 | 60 | 61 | # tensorflow 62 | 63 | try: 64 | import tensorflow as tf 65 | 66 | x = np.concatenate([np.arange(i) for i in range(9)]) 67 | x = x.reshape(2, -1) 68 | x = tf.convert_to_tensor(x) 69 | pass 70 | except ImportError: 71 | pass 72 | 73 | 74 | # pandas 75 | 76 | try: 77 | import pandas as pd 78 | import plotly.express as px 79 | 80 | x = px.data.gapminder().query("year == 2007") 81 | pass 82 | except ImportError: 83 | pass 84 | 85 | # custom visualizer defined in ./debugvisualizer.py (this file is included automatically) 86 | 87 | from Person import Person 88 | 89 | x = Person("Aria") 90 | parent1 = Person("Eduart") 91 | parent2 = Person("Catelyn") 92 | x.addParent(parent1) 93 | x.addParent(parent2) 94 | parent1.addParent(Person("Benjen")) 95 | 96 | # direct debug-visualizer json as dict with property "kind" 97 | 98 | x = { 99 | "kind": {"dotGraph": True}, 100 | "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', 101 | } 102 | 103 | x = { 104 | "kind": {"plotly": True}, 105 | "data": [ 106 | { 107 | "mode": "lines", 108 | "type": "scatter", 109 | "x": ["A", "B", "C"], 110 | "xaxis": "x", 111 | "y": [4488916, 3918072, 3892124], 112 | "yaxis": "y", 113 | }, 114 | { 115 | "cells": {"values": [["A", "B", "C"], [341319, 281489, 294786], [4488916, 3918072, 3892124]]}, 116 | "domain": {"x": [0.0, 1.0], "y": [0.0, 0.60]}, 117 | "header": {"align": "left", "font": {"size": 10}, "values": ["Date", "Number", "Output"]}, 118 | "type": "table", 119 | }, 120 | ], 121 | "layout": {"xaxis": {"anchor": "y", "domain": [0.0, 1.0]}, "yaxis": {"anchor": "x", "domain": [0.75, 1.0]}}, 122 | } 123 | pass -------------------------------------------------------------------------------- /demos/python/graph.py: -------------------------------------------------------------------------------- 1 | # Install the python extension for VS Code 2 | # (https:#marketplace.visualstudio.com/items?itemName=ms-python.python). 3 | 4 | # The Debug Visualizer has no support for Python data extractors yet, 5 | # so to visualize data, your value must be a valid JSON string representing the data. 6 | # See readme for supported data schemas. 7 | 8 | from json import dumps 9 | from random import randint 10 | 11 | graph = { 12 | "kind": {"graph": True}, 13 | "nodes": [ 14 | {"id": "1", "label": "1"} 15 | ], 16 | "edges": [] 17 | } 18 | 19 | for i in range(2,100): 20 | # add a node 21 | id = str(i) 22 | graph["nodes"].append({"id": id, "label": id}) 23 | # connects the node to a random edge 24 | targetId = str(randint(1, i - 1)) 25 | graph["edges"].append({"from": id, "to": targetId}) 26 | print("i is " + str(i)) 27 | # try setting a breakpoint right above 28 | # then put graph into the visualization console and press enter 29 | # when you step through the code each time you hit the breakpoint 30 | # the graph should automatically refresh! 31 | 32 | # example of json_graph visualization with 10 nodes: 33 | # https://i.imgur.com/RqZuYHH.png 34 | 35 | print("finished") 36 | -------------------------------------------------------------------------------- /demos/python/insertion_sort.py: -------------------------------------------------------------------------------- 1 | """Python demo for sorting using VS Code Debug Visualizer.""" 2 | 3 | 4 | def serialize(arr): 5 | """Serialize an array into a format the visualizer can understand.""" 6 | formatted = { 7 | "kind": {"grid": True}, 8 | "rows": [ 9 | { 10 | "columns": [ 11 | {"content": str(value), "tag": str(value)} for value in arr 12 | ], 13 | } 14 | ], 15 | } 16 | return formatted 17 | 18 | 19 | arr = [6, 9, 3, 12, 1, 11, 5, 13, 8, 14, 2, 4, 10, 0, 7] 20 | 21 | # Put serialized into the Debug Visualizer console 22 | serialized = serialize(arr) 23 | 24 | # Set a breakpoint on the line below and go through the code in debug mode to 25 | # watch it update 26 | for target_idx in range(1, len(arr)): 27 | target_value = arr[target_idx] 28 | compare_idx = target_idx - 1 29 | 30 | while compare_idx >= 0 and arr[compare_idx] > target_value: 31 | arr[compare_idx + 1] = arr[compare_idx] 32 | serialized = serialize(arr) 33 | compare_idx -= 1 34 | 35 | arr[compare_idx + 1] = target_value 36 | serialized = serialize(arr) 37 | 38 | assert arr == sorted(arr) 39 | -------------------------------------------------------------------------------- /demos/ruby/README.md: -------------------------------------------------------------------------------- 1 | # Ruby Demos 2 | 3 | To visualize Ruby objects, you need to install [debugvisualizer.gem](https://github.com/ono-max/debugvisualizer). 4 | 5 | Install the gem by executing: 6 | 7 | $ gem install debugvisualizer 8 | -------------------------------------------------------------------------------- /demos/ruby/src/demo_custom_visualizer.rb: -------------------------------------------------------------------------------- 1 | require 'debugvisualizer' 2 | 3 | class Foo; end 4 | 5 | DebugVisualizer.register Foo do |data| 6 | { 7 | id: "my_visualizer", 8 | name: "My Visualizer", 9 | data: { 10 | kind: { text: true }, 11 | text: "Foo" 12 | } 13 | } 14 | end 15 | 16 | f = Foo.new 17 | # visualize `f` here! 18 | binding.break 19 | -------------------------------------------------------------------------------- /demos/ruby/src/demo_random_walks.rb: -------------------------------------------------------------------------------- 1 | data = [] 2 | curVal = 0 3 | 4 | 100.times{ 5 | 100.times{ 6 | delta = rand > 0.5 ? 1 : -1 7 | data << curVal 8 | curVal += delta 9 | # visualize `data` here and press F5(or Continue button) over and over! 10 | binding.break 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /demos/rust/.gitignore: -------------------------------------------------------------------------------- 1 | # Generated by Cargo 2 | # will have compiled files and executables 3 | /target/ 4 | 5 | # Remove Cargo.lock from gitignore if creating an executable, leave it for libraries 6 | # More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html 7 | Cargo.lock 8 | 9 | # These are backup files generated by rustfmt 10 | **/*.rs.bk -------------------------------------------------------------------------------- /demos/rust/.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | // Use IntelliSense to learn about possible attributes. 3 | // Hover to view descriptions of existing attributes. 4 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 5 | "version": "0.2.0", 6 | "configurations": [ 7 | { 8 | "type": "lldb", 9 | "request": "launch", 10 | "name": "run rust demo", 11 | "cargo": { 12 | "args": [ 13 | "build", 14 | "--bin=rust_demo", 15 | "--package=rust_demo", 16 | ], 17 | "filter": { 18 | "name": "rust_demo", 19 | "kind": "bin", 20 | }, 21 | }, 22 | "args": [], 23 | "cwd": "${workspaceFolder}" 24 | } 25 | ] 26 | } -------------------------------------------------------------------------------- /demos/rust/.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "debugVisualizer.debugAdapterConfigurations": { 3 | "lldb": { 4 | "expressionTemplate": "${expr}", 5 | "context": "watch" 6 | } 7 | } 8 | } -------------------------------------------------------------------------------- /demos/rust/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "rust_demo" 3 | version = "0.1.0" 4 | authors = ["hediet"] 5 | edition = "2018" 6 | 7 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 8 | 9 | [dependencies] 10 | serde = { version = "1.0", features = ["derive"] } 11 | serde_json = "1.0" -------------------------------------------------------------------------------- /demos/rust/src/main.rs: -------------------------------------------------------------------------------- 1 | use serde::Serialize; 2 | 3 | // expected to mirror 4 | // https://hediet.github.io/visualization/docs/visualization-data-schema.json 5 | // 6 | // implements the grid visualizer as an example 7 | 8 | pub type Label = String; 9 | 10 | /// `GridVisualizationData` in schema 11 | #[derive(Debug, Serialize)] 12 | pub struct Grid { 13 | kind: Kind, 14 | rows: Vec, 15 | 16 | #[serde(rename = "columnLabels")] 17 | #[serde(skip_serializing_if="Option::is_none")] 18 | column_labels: Option>, 19 | } 20 | 21 | #[derive(Debug, Serialize)] 22 | pub struct Kind { 23 | grid: bool, 24 | } 25 | 26 | #[derive(Debug, Serialize)] 27 | pub struct Row { 28 | columns: Vec, 29 | 30 | #[serde(skip_serializing_if="Option::is_none")] 31 | label: Option