Next Iteration

This commit is contained in:
Henning Dieterichs
2019-07-31 11:41:10 +02:00
parent 6175e8f55e
commit 922eb0b845
71 changed files with 19606 additions and 14862 deletions

87
.gitignore vendored
View File

@@ -1,4 +1,83 @@
out
node_modules
.vscode-test/
*.vsix
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
# Runtime data
pids
*.pid
*.seed
*.pid.lock
# Directory for instrumented libs generated by jscoverage/JSCover
lib-cov
# Coverage directory used by tools like istanbul
coverage
# nyc test coverage
.nyc_output
# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
.grunt
# Bower dependency directory (https://bower.io/)
bower_components
# node-waf configuration
.lock-wscript
# Compiled binary addons (https://nodejs.org/api/addons.html)
build/Release
# Dependency directories
node_modules/
jspm_packages/
# TypeScript v1 declaration files
typings/
# Optional npm cache directory
.npm
# Optional eslint cache
.eslintcache
# Optional REPL history
.node_repl_history
# Output of 'npm pack'
*.tgz
# Yarn Integrity file
.yarn-integrity
# dotenv environment variables file
.env
.env.test
# parcel-bundler cache (https://parceljs.org/)
.cache
# next.js build output
.next
# nuxt.js build output
.nuxt
# vuepress build output
.vuepress/dist
# Serverless directories
.serverless/
# FuseBox cache
.fusebox/
# DynamoDB Local files
.dynamodb/
dist/
out/

6
.prettierrc.json Normal file
View File

@@ -0,0 +1,6 @@
{
"trailingComma": "es5",
"tabWidth": 4,
"semi": true,
"useTabs": true
}

View File

@@ -1,7 +0,0 @@
{
// See http://go.microsoft.com/fwlink/?LinkId=827846
// for the documentation about the extensions.json format
"recommendations": [
"eg2.tslint"
]
}

47
.vscode/launch.json vendored
View File

@@ -1,36 +1,17 @@
// A launch configuration that compiles the extension and then opens it inside a new window
// Use IntelliSense to learn about possible attributes.
// Hover to view descriptions of existing attributes.
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
{
"version": "0.2.0",
"configurations": [
{
"name": "Extension",
"type": "extensionHost",
"request": "launch",
"runtimeExecutable": "${execPath}",
"args": [
"--extensionDevelopmentPath=${workspaceFolder}"
],
"outFiles": [
"${workspaceFolder}/out/**/*.js"
],
"preLaunchTask": "npm: watch"
},
{
"name": "Extension Tests",
"type": "extensionHost",
"request": "launch",
"runtimeExecutable": "${execPath}",
"args": [
"--extensionDevelopmentPath=${workspaceFolder}",
"--extensionTestsPath=${workspaceFolder}/out/test"
],
"outFiles": [
"${workspaceFolder}/out/test/**/*.js"
],
"preLaunchTask": "npm: watch"
}
]
"configurations": [
{
"name": "Run Extension",
"type": "extensionHost",
"request": "launch",
"runtimeExecutable": "${execPath}",
"args": [
"--extensionDevelopmentPath=${workspaceFolder}/extension",
"S:\\dev\\vscode\\vscode-debug-visualizer2\\example-project"
],
"outFiles": ["${workspaceFolder}/extension/out/**/*.js"],
"preLaunchTask": "npm: dev - extension"
}
]
}

17
.vscode/settings.json vendored
View File

@@ -1,11 +1,8 @@
// Place your settings in this file to overwrite default and user settings.
{
"files.exclude": {
"out": false // set this to true to hide the "out" folder with the compiled JS files
},
"search.exclude": {
"out": true // set this to false to include "out" folder in search results
},
// Turn off tsc task auto detection since we have the necessary tasks as npm scripts
"typescript.tsc.autoDetect": "off"
}
"search.exclude": {
"extension/out": true
},
"plantuml.diagramsRoot": "docs",
"plantuml.exportFormat": "png",
"plantuml.exportOutDir": "docs/exported"
}

38
.vscode/tasks.json vendored
View File

@@ -1,20 +1,20 @@
// See https://go.microsoft.com/fwlink/?LinkId=733558
// for the documentation about the tasks.json format
{
"version": "2.0.0",
"tasks": [
{
"type": "npm",
"script": "watch",
"problemMatcher": "$tsc-watch",
"isBackground": true,
"presentation": {
"reveal": "never"
},
"group": {
"kind": "build",
"isDefault": true
}
}
]
}
"version": "2.0.0",
"tasks": [
{
"type": "npm",
"label": "npm: dev - extension",
"script": "dev",
"problemMatcher": "$tsc-watch",
"isBackground": true,
"presentation": {
"reveal": "never"
},
"path": "extension/",
"group": {
"kind": "build",
"isDefault": true
}
}
]
}

View File

@@ -1 +1 @@
# debug-visualizer README
uiae

View File

@@ -0,0 +1,20 @@
{
"name": "@hediet/debug-visualizer-data-extraction",
"private": true,
"version": "0.1.0",
"main": "dist/index.js",
"types": "dist/index.d.ts",
"author": {
"name": "Henning Dieterichs",
"email": "henning.dieterichs@live.de"
},
"license": "MIT",
"files": [
"dist",
"src"
],
"scripts": {
"dev": "tsc --watch"
},
"dependencies": {}
}

View File

@@ -0,0 +1,46 @@
import { ExtractedData } from "./DataExtractionResult";
export module CommonDataTypes {
export interface Text extends ExtractedData {
kind: { text: true };
text: string;
// what should be used here? File type (.ts, .txt) or mime type?
fileType?: string;
}
export interface Svg extends Text {
kind: { text: true; svg: true };
text: string;
}
export interface Html extends Text {
kind: { text: true; html: true };
text: string;
}
export interface DotGraph extends Text {
kind: { text: true; dotGraph: true };
text: string;
}
export interface TreeNodeData extends ExtractedData {
kind: { tree: true };
root: TreeNode<{}>;
}
export interface AstData extends TreeNodeData, Text {
kind: { text: true; tree: true; ast: true };
root: TreeNode<{
position: number;
length: number;
}>;
}
}
export interface TreeNode<TExtraData> {
id: string | undefined;
name: string;
children: TreeNode<TExtraData>[];
data: TExtraData;
isSelected: boolean;
}

View File

@@ -0,0 +1,20 @@
export type DataExtractionResult = {
data: ExtractedData;
usedExtractor: DataExtractorInfo;
availableExtractors: DataExtractorInfo[];
};
// is json
export type ExtractedData = {
kind: Record<string, true>;
};
export type DataExtractorInfo = {
id: DataExtractorId;
name: string;
priority: number;
};
export type DataExtractorId = {
__brand: "DataExtractorId";
};

View File

@@ -0,0 +1,3 @@
export * from "./js";
export * from "./CommonDataTypes";
export * from "./DataExtractionResult";

View File

@@ -0,0 +1,21 @@
import { ExtractedData } from "../DataExtractionResult";
export interface DataExtractor<T extends ExtractedData> {
id: string;
getExtractions(
data: any,
extractionCollector: ExtractionCollector<T>
): void;
}
export interface ExtractionCollector<T extends ExtractedData> {
addExtraction(extraction: DataExtraction<T>): void;
}
export interface DataExtraction<TData extends ExtractedData> {
// the higher the better
priority: number;
id: string;
name: string;
extractData(): TData;
}

View File

@@ -0,0 +1,144 @@
import {
ExtractedData,
DataExtractionResult,
DataExtractorInfo,
} from "../DataExtractionResult";
import {
DataExtractor,
DataExtraction,
ExtractionCollector,
} from "./DataExtractor";
export type DataResult =
| {
kind: "Data";
extractionResult: DataExtractionResult;
}
| { kind: "NoExtractors" };
export interface JSONString<T> extends String {
__brand: { json: T };
}
export interface DataExtractorApi {
registerExtractor<TExtractedData extends ExtractedData>(
extractor: DataExtractor<TExtractedData>
): void;
getData(
value: unknown,
preferredDataExtractorId: string | undefined
): JSONString<DataResult>;
}
declare const window: any;
export function selfContainedInitDataExtractorApi(): boolean {
const obj = typeof window === "object" ? (window as any) : (global as any);
const key = "@hediet/data-extractor";
const prefix = key + "::";
let api: DataExtractorApi | undefined = obj[key];
if (api) {
return false;
}
function toJson<TData>(data: TData): JSONString<TData> {
return JSON.stringify(data) as any;
}
function getExtractors(): DataExtractor<ExtractedData>[] {
return Object.entries(obj)
.filter(([key, value]) => key.startsWith(prefix))
.map(([key, value]) => value as DataExtractor<ExtractedData>);
}
obj[key] = api = {
registerExtractor(extractor) {
obj[prefix + extractor.id] = extractor;
},
getData(value, preferredDataExtractorId) {
const extractions = new Array<DataExtraction<ExtractedData>>();
const collector: ExtractionCollector<ExtractedData> = {
addExtraction(extraction) {
extractions.push(extraction);
},
};
for (const e of getExtractors()) {
e.getExtractions(value, collector);
}
extractions.sort((a, b) => b.priority - a.priority);
let usedExtraction = extractions[0];
if (!usedExtraction) {
return toJson({ kind: "NoExtractors" } as DataResult);
}
if (preferredDataExtractorId) {
const preferred = extractions.find(
e => e.id === preferredDataExtractorId
);
if (preferred) {
usedExtraction = preferred;
}
}
function mapExtractor(
e: DataExtraction<ExtractedData>
): DataExtractorInfo {
return {
id: e.id as any,
name: e.name,
priority: e.priority,
};
}
const data = usedExtraction.extractData();
return toJson({
kind: "Data",
extractionResult: {
data,
usedExtractor: mapExtractor(usedExtraction),
availableExtractors: extractions.map(mapExtractor),
},
} as DataResult);
},
};
api.registerExtractor({
id: "toString",
getExtractions(data, collector) {
collector.addExtraction({
id: "toString",
name: "To String",
priority: 100,
extractData() {
return {
kind: {
text: true,
},
text: data.toString(),
};
},
});
},
});
return true;
}
export const ApiHasNotBeenInitializedCode = "EgH0cybXij1jYUozyakO" as const;
export function selfContainedGetInitializedDataExtractorApi(): DataExtractorApi {
const obj = typeof window === "object" ? (window as any) : (global as any);
const key = "@hediet/data-extractor";
let api: DataExtractorApi | undefined = obj[key];
if (!api) {
const code: typeof ApiHasNotBeenInitializedCode =
"EgH0cybXij1jYUozyakO";
throw new Error(
`Data Extractor API has not been initialized. Code: ${code}`
);
}
return api;
}
export function getDataExtractorApi(): DataExtractorApi {
selfContainedInitDataExtractorApi();
return selfContainedGetInitializedDataExtractorApi();
}

View File

@@ -0,0 +1,93 @@
import * as ts from "typescript";
import { DataExtractor, ExtractionCollector } from "../DataExtractor";
import { CommonDataTypes } from "../../CommonDataTypes";
export class TypeScriptAstDataExtractor
implements DataExtractor<CommonDataTypes.AstData> {
readonly id = "TypeScriptAst";
getExtractions(
data: any,
collector: ExtractionCollector<CommonDataTypes.AstData>
): void {
if (typeof data !== "object" || data === undefined) {
return;
}
const tsApi = require("typescript") as typeof ts | undefined;
if (!tsApi) {
return;
}
if ((tsApi as any).isNode(data)) {
const n = data as ts.Node;
collector.addExtraction({
id: "ts-ast",
name: "TypeScript AST",
priority: 1000,
extractData() {
return {
kind: { text: true, tree: true, ast: true },
root: toTreeNode(data, "root"),
text: n.getSourceFile().text,
fileType: "ts",
};
},
});
}
}
}
function findKey(value: any, object: any): string | null {
for (var key in object) {
if (key.startsWith("_")) continue;
var member = object[key];
if (member === value) return key;
if (Array.isArray(member) && member.indexOf(value) !== -1) return key;
}
return null;
}
function toTreeNode(
node: ts.Node,
memberName: string
): CommonDataTypes.AstData["root"] {
const name = ts.SyntaxKind[node.kind];
const children = node
.getChildren()
.map((childNode, idx) => {
let parentPropertyName = findKey(childNode, node) || "";
if (childNode.kind == ts.SyntaxKind.SyntaxList) {
childNode.getChildren().some(c => {
parentPropertyName = findKey(c, node) || "";
return !!parentPropertyName;
});
if (childNode.getChildren().length === 0) return null!;
}
if (node.kind == ts.SyntaxKind.SyntaxList) {
parentPropertyName = "" + idx;
}
return toTreeNode(childNode, parentPropertyName);
})
.filter(c => c !== null);
return {
name: name,
id: memberName,
children: children,
data: {
length: 0,
position: 0,
},
isSelected: false,
// startPos: node.pos,
// endPos: node.end
};
}

View File

@@ -0,0 +1,3 @@
export * from "./Extractors/TypeScriptDataExtractors";
export * from "./DataExtractor";
export * from "./DataExtractorApi";

View File

@@ -0,0 +1,16 @@
{
"compilerOptions": {
"target": "esnext",
"module": "commonjs",
"strict": true,
"outDir": "dist",
"skipLibCheck": true,
"rootDir": "./src",
"resolveJsonModule": true,
"declaration": true,
"declarationMap": true,
"newLine": "LF",
"sourceMap": true
},
"include": ["./src/**/*"]
}

16
example-project/.vscode/launch.json vendored Normal file
View File

@@ -0,0 +1,16 @@
{
// Use IntelliSense to learn about possible attributes.
// Hover to view descriptions of existing attributes.
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
"version": "0.2.0",
"configurations": [
{
"type": "node",
"request": "launch",
"name": "Launch Program",
"program": "${workspaceFolder}\\src\\index.ts",
"outFiles": ["${workspaceFolder}/dist/**/*.js"],
"preLaunchTask": "npm: dev"
}
]
}

19
example-project/.vscode/tasks.json vendored Normal file
View File

@@ -0,0 +1,19 @@
{
"version": "2.0.0",
"tasks": [
{
"type": "npm",
"label": "npm: dev",
"script": "dev",
"problemMatcher": "$tsc-watch",
"isBackground": true,
"presentation": {
"reveal": "never"
},
"group": {
"kind": "build",
"isDefault": true
}
}
]
}

View File

@@ -0,0 +1,13 @@
{
"name": "demo",
"version": "1.0.0",
"license": "MIT",
"dependencies": {
"@hediet/std": "^0.4.0",
"@hediet/node-reload": "0.7.0",
"typescript": "^3.5.3"
},
"scripts": {
"dev": "tsc --watch"
}
}

View File

@@ -0,0 +1,45 @@
import {
enableHotReload,
registerUpdateReconciler,
getReloadCount,
hotClass,
} from "@hediet/node-reload";
import {
getDataExtractorApi,
TypeScriptAstDataExtractor,
CommonDataTypes,
} from "@hediet/debug-visualizer-data-extraction";
enableHotReload();
import * as ts from "typescript";
registerUpdateReconciler(module);
getDataExtractorApi().registerExtractor(new TypeScriptAstDataExtractor());
@hotClass(module)
class Main {
run() {
const sf = ts.createSourceFile(
"test",
`
class Test {
public test() {
console.log("aaa");
const x = 4 + 1;
}
}
`,
ts.ScriptTarget.Latest,
true
);
console.log(sf);
//new TypeScriptAstDataExtractor().getExtractions(sf, );
debugger;
}
}
if (getReloadCount(module) === 0) {
new Main().run();
}

View File

@@ -0,0 +1,17 @@
{
"compilerOptions": {
"target": "esnext",
"module": "commonjs",
"strict": true,
"outDir": "dist",
"skipLibCheck": true,
"rootDir": "./src",
"resolveJsonModule": true,
"declaration": true,
"declarationMap": true,
"newLine": "LF",
"sourceMap": true,
"experimentalDecorators": true
},
"include": ["src/**/*"]
}

436
example-project/yarn.lock Normal file
View File

@@ -0,0 +1,436 @@
# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.
# yarn lockfile v1
"@hediet/live-debug@^0.5.1":
version "0.5.2"
resolved "https://registry.yarnpkg.com/@hediet/live-debug/-/live-debug-0.5.2.tgz#f2ee0034f014836bfd68e26c35a168ab42b15486"
integrity sha512-HBqVbyFez1oQlIPO9dzkFi7/9Gvt6ZpssgURzYaVFREl9Oq03nPd6V7Ay4bW03xFHPo3YT2Yff9mFmGQUX7JEg==
dependencies:
"@hediet/std" "^0.6.0"
"@hediet/typed-json-rpc" "^0.7.6"
"@hediet/typed-json-rpc-websocket" "^0.7.7"
stacktracey "^1.2.112"
ws "^7.0.0"
"@hediet/node-reload@0.7.0":
version "0.7.0"
resolved "https://registry.yarnpkg.com/@hediet/node-reload/-/node-reload-0.7.0.tgz#ec50160ce26203079f18e7024cd58025fa79652a"
integrity sha512-gWFbq2tOThSV9GT+pFx1JJzFuOV7l40IsseOFHMJGyvLmmAdgbJPV3OZuV9gcsXIGocOtbfEyky9S4tQELLLxA==
dependencies:
"@hediet/live-debug" "^0.5.1"
"@hediet/std" "^0.6.0"
"@hediet/typed-json-rpc" "^0.7.6"
"@types/node" "^12.0.7"
"@types/stack-trace" "^0.0.29"
chokidar "^3.0.1"
stack-trace "^0.0.10"
ws "^7.0.0"
"@hediet/std@^0.4.0":
version "0.4.0"
resolved "https://registry.yarnpkg.com/@hediet/std/-/std-0.4.0.tgz#95dfd6e9d62f8bf9d91f3925d37b7b359b2af047"
integrity sha512-2kw4vSOprLchTjhcg8qQo6fdbBxJicSUrzZXgL0KzgMv18hjVmpm57NPLCPfI6115kAaMIWvMnlHFLYbx7poQQ==
dependencies:
"@types/chai" "^4.1.7"
"@types/sinon" "^7.0.10"
chai "^4.2.0"
sinon "^7.2.7"
"@hediet/std@^0.6.0":
version "0.6.0"
resolved "https://registry.yarnpkg.com/@hediet/std/-/std-0.6.0.tgz#f7d2ffc1180ecef6ff272d0d6780f0c0d9337e4e"
integrity sha512-XzBiZo6pnt01k266lSbXOBZd7jrQQSutoH3IpI/1GuL09zxuDX+c0XersEbMCGS45S2l/TSlISyV3MH1lbYJEg==
dependencies:
"@types/chai" "^4.1.7"
"@types/sinon" "^7.0.10"
chai "^4.2.0"
mobx "^5.10.0"
sinon "^7.2.7"
"@hediet/typed-json-rpc-websocket@^0.7.7":
version "0.7.7"
resolved "https://registry.yarnpkg.com/@hediet/typed-json-rpc-websocket/-/typed-json-rpc-websocket-0.7.7.tgz#b1555528226e2bf3202d1d0e1870d04b59c14048"
integrity sha512-0Aakr7ktqjR9paPRHM0fhePZDA8kKWn2t2Al3ta9nXSsY4ec/HknEwR9NMhXD3lATa/wvvKVIymalzKuRoV82A==
dependencies:
"@hediet/typed-json-rpc" "^0.7.5"
"@types/ws" "^6.0.1"
isomorphic-ws "^4.0.1"
"@hediet/typed-json-rpc@^0.7.5", "@hediet/typed-json-rpc@^0.7.6":
version "0.7.7"
resolved "https://registry.yarnpkg.com/@hediet/typed-json-rpc/-/typed-json-rpc-0.7.7.tgz#bd7e586ad5c0480f7bd6bc40d6ad202e0a80b54d"
integrity sha512-lyjbVqt+mFrRS0+2cOVDithtfG9gfRV5CJMVDwV+F2gfCUjTe1aZu4xObpDDLfxfoZOGgtPNNg6jCNhvXSxQHg==
dependencies:
"@hediet/std" "^0.6.0"
io-ts "^1.8.6"
"@sinonjs/commons@^1", "@sinonjs/commons@^1.0.2", "@sinonjs/commons@^1.4.0":
version "1.4.0"
resolved "https://registry.yarnpkg.com/@sinonjs/commons/-/commons-1.4.0.tgz#7b3ec2d96af481d7a0321252e7b1c94724ec5a78"
integrity sha512-9jHK3YF/8HtJ9wCAbG+j8cD0i0+ATS9A7gXFqS36TblLPNy6rEEc+SB0imo91eCboGaBYGV/MT1/br/J+EE7Tw==
dependencies:
type-detect "4.0.8"
"@sinonjs/formatio@^3.1.0", "@sinonjs/formatio@^3.2.1":
version "3.2.1"
resolved "https://registry.yarnpkg.com/@sinonjs/formatio/-/formatio-3.2.1.tgz#52310f2f9bcbc67bdac18c94ad4901b95fde267e"
integrity sha512-tsHvOB24rvyvV2+zKMmPkZ7dXX6LSLKZ7aOtXY6Edklp0uRcgGpOsQTTGTcWViFyx4uhWc6GV8QdnALbIbIdeQ==
dependencies:
"@sinonjs/commons" "^1"
"@sinonjs/samsam" "^3.1.0"
"@sinonjs/samsam@^3.1.0", "@sinonjs/samsam@^3.3.1":
version "3.3.2"
resolved "https://registry.yarnpkg.com/@sinonjs/samsam/-/samsam-3.3.2.tgz#63942e3d5eb0b79f6de3bef9abfad15fb4b6401b"
integrity sha512-ILO/rR8LfAb60Y1Yfp9vxfYAASK43NFC2mLzpvLUbCQY/Qu8YwReboseu8aheCEkyElZF2L2T9mHcR2bgdvZyA==
dependencies:
"@sinonjs/commons" "^1.0.2"
array-from "^2.1.1"
lodash "^4.17.11"
"@sinonjs/text-encoding@^0.7.1":
version "0.7.1"
resolved "https://registry.yarnpkg.com/@sinonjs/text-encoding/-/text-encoding-0.7.1.tgz#8da5c6530915653f3a1f38fd5f101d8c3f8079c5"
integrity sha512-+iTbntw2IZPb/anVDbypzfQa+ay64MW0Zo8aJ8gZPWMMK6/OubMVb6lUPMagqjOPnmtauXnFCACVl3O7ogjeqQ==
"@types/chai@^4.1.7":
version "4.1.7"
resolved "https://registry.yarnpkg.com/@types/chai/-/chai-4.1.7.tgz#1b8e33b61a8c09cbe1f85133071baa0dbf9fa71a"
integrity sha512-2Y8uPt0/jwjhQ6EiluT0XCri1Dbplr0ZxfFXUz+ye13gaqE8u5gL5ppao1JrUYr9cIip5S6MvQzBS7Kke7U9VA==
"@types/events@*":
version "3.0.0"
resolved "https://registry.yarnpkg.com/@types/events/-/events-3.0.0.tgz#2862f3f58a9a7f7c3e78d79f130dd4d71c25c2a7"
integrity sha512-EaObqwIvayI5a8dCzhFrjKzVwKLxjoG9T6Ppd5CEo07LRKfQ8Yokw54r5+Wq7FaBQ+yXRvQAYPrHwya1/UFt9g==
"@types/node@*", "@types/node@^12.0.7":
version "12.6.8"
resolved "https://registry.yarnpkg.com/@types/node/-/node-12.6.8.tgz#e469b4bf9d1c9832aee4907ba8a051494357c12c"
integrity sha512-aX+gFgA5GHcDi89KG5keey2zf0WfZk/HAQotEamsK2kbey+8yGKcson0hbK8E+v0NArlCJQCqMP161YhV6ZXLg==
"@types/node@^8.0.7":
version "8.10.51"
resolved "https://registry.yarnpkg.com/@types/node/-/node-8.10.51.tgz#80600857c0a47a8e8bafc2dae6daed6db58e3627"
integrity sha512-cArrlJp3Yv6IyFT/DYe+rlO8o3SIHraALbBW/+CcCYW/a9QucpLI+n2p4sRxAvl2O35TiecpX2heSZtJjvEO+Q==
"@types/sinon@^7.0.10":
version "7.0.13"
resolved "https://registry.yarnpkg.com/@types/sinon/-/sinon-7.0.13.tgz#ca039c23a9e27ebea53e0901ef928ea2a1a6d313"
integrity sha512-d7c/C/+H/knZ3L8/cxhicHUiTDxdgap0b/aNJfsmLwFu/iOP17mdgbQsbHA3SJmrzsjD0l3UEE5SN4xxuz5ung==
"@types/stack-trace@^0.0.29":
version "0.0.29"
resolved "https://registry.yarnpkg.com/@types/stack-trace/-/stack-trace-0.0.29.tgz#eb7a7c60098edb35630ed900742a5ecb20cfcb4d"
integrity sha512-TgfOX+mGY/NyNxJLIbDWrO9DjGoVSW9+aB8H2yy1fy32jsvxijhmyJI9fDFgvz3YP4lvJaq9DzdR/M1bOgVc9g==
"@types/ws@^6.0.1":
version "6.0.1"
resolved "https://registry.yarnpkg.com/@types/ws/-/ws-6.0.1.tgz#ca7a3f3756aa12f62a0a62145ed14c6db25d5a28"
integrity sha512-EzH8k1gyZ4xih/MaZTXwT2xOkPiIMSrhQ9b8wrlX88L0T02eYsddatQlwVFlEPyEqV0ChpdpNnE51QPH6NVT4Q==
dependencies:
"@types/events" "*"
"@types/node" "*"
anymatch@^3.0.1:
version "3.0.3"
resolved "https://registry.yarnpkg.com/anymatch/-/anymatch-3.0.3.tgz#2fb624fe0e84bccab00afee3d0006ed310f22f09"
integrity sha512-c6IvoeBECQlMVuYUjSwimnhmztImpErfxJzWZhIQinIvQWoGOnB0dLIgifbPHQt5heS6mNlaZG16f06H3C8t1g==
dependencies:
normalize-path "^3.0.0"
picomatch "^2.0.4"
array-from@^2.1.1:
version "2.1.1"
resolved "https://registry.yarnpkg.com/array-from/-/array-from-2.1.1.tgz#cfe9d8c26628b9dc5aecc62a9f5d8f1f352c1195"
integrity sha1-z+nYwmYoudxa7MYqn12PHzUsEZU=
as-table@^1.0.36:
version "1.0.52"
resolved "https://registry.yarnpkg.com/as-table/-/as-table-1.0.52.tgz#537fb0cfcc6d04b498c8922df5f75ed357ec91e6"
integrity sha512-Lw3OjVL9BDGSrU6RoK5aTm79SnrDY0Zvucxc+oOTqZrC2Sw575IhkOTPtvpzviliL/WJaCSWzJdKFdYeI+8wFg==
dependencies:
printable-characters "^1.0.42"
assertion-error@^1.1.0:
version "1.1.0"
resolved "https://registry.yarnpkg.com/assertion-error/-/assertion-error-1.1.0.tgz#e60b6b0e8f301bd97e5375215bda406c85118c0b"
integrity sha512-jgsaNduz+ndvGyFt3uSuWqvy4lCnIJiovtouQN5JZHOKCS2QuhEdbcQHFhVksz2N2U9hXJo8odG7ETyWlEeuDw==
async-limiter@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/async-limiter/-/async-limiter-1.0.0.tgz#78faed8c3d074ab81f22b4e985d79e8738f720f8"
integrity sha512-jp/uFnooOiO+L211eZOoSyzpOITMXx1rBITauYykG3BRYPu8h0UcxsPNB04RR5vo4Tyz3+ay17tR6JVf9qzYWg==
binary-extensions@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/binary-extensions/-/binary-extensions-2.0.0.tgz#23c0df14f6a88077f5f986c0d167ec03c3d5537c"
integrity sha512-Phlt0plgpIIBOGTT/ehfFnbNlfsDEiqmzE2KRXoX1bLIlir4X/MR+zSyBEkL05ffWgnRSf/DXv+WrUAVr93/ow==
braces@^3.0.2:
version "3.0.2"
resolved "https://registry.yarnpkg.com/braces/-/braces-3.0.2.tgz#3454e1a462ee8d599e236df336cd9ea4f8afe107"
integrity sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==
dependencies:
fill-range "^7.0.1"
chai@^4.2.0:
version "4.2.0"
resolved "https://registry.yarnpkg.com/chai/-/chai-4.2.0.tgz#760aa72cf20e3795e84b12877ce0e83737aa29e5"
integrity sha512-XQU3bhBukrOsQCuwZndwGcCVQHyZi53fQ6Ys1Fym7E4olpIqqZZhhoFJoaKVvV17lWQoXYwgWN2nF5crA8J2jw==
dependencies:
assertion-error "^1.1.0"
check-error "^1.0.2"
deep-eql "^3.0.1"
get-func-name "^2.0.0"
pathval "^1.1.0"
type-detect "^4.0.5"
check-error@^1.0.2:
version "1.0.2"
resolved "https://registry.yarnpkg.com/check-error/-/check-error-1.0.2.tgz#574d312edd88bb5dd8912e9286dd6c0aed4aac82"
integrity sha1-V00xLt2Iu13YkS6Sht1sCu1KrII=
chokidar@^3.0.1:
version "3.0.2"
resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-3.0.2.tgz#0d1cd6d04eb2df0327446188cd13736a3367d681"
integrity sha512-c4PR2egjNjI1um6bamCQ6bUNPDiyofNQruHvKgHQ4gDUP/ITSVSzNsiI5OWtHOsX323i5ha/kk4YmOZ1Ktg7KA==
dependencies:
anymatch "^3.0.1"
braces "^3.0.2"
glob-parent "^5.0.0"
is-binary-path "^2.1.0"
is-glob "^4.0.1"
normalize-path "^3.0.0"
readdirp "^3.1.1"
optionalDependencies:
fsevents "^2.0.6"
data-uri-to-buffer@^2.0.0:
version "2.0.1"
resolved "https://registry.yarnpkg.com/data-uri-to-buffer/-/data-uri-to-buffer-2.0.1.tgz#ca8f56fe38b1fd329473e9d1b4a9afcd8ce1c045"
integrity sha512-OkVVLrerfAKZlW2ZZ3Ve2y65jgiWqBKsTfUIAFbn8nVbPcCZg6l6gikKlEYv0kXcmzqGm6mFq/Jf2vriuEkv8A==
dependencies:
"@types/node" "^8.0.7"
deep-eql@^3.0.1:
version "3.0.1"
resolved "https://registry.yarnpkg.com/deep-eql/-/deep-eql-3.0.1.tgz#dfc9404400ad1c8fe023e7da1df1c147c4b444df"
integrity sha512-+QeIQyN5ZuO+3Uk5DYh6/1eKO0m0YmJFGNmFHGACpf1ClL1nmlV/p4gNgbl2pJGxgXb4faqo6UE+M5ACEMyVcw==
dependencies:
type-detect "^4.0.0"
diff@^3.5.0:
version "3.5.0"
resolved "https://registry.yarnpkg.com/diff/-/diff-3.5.0.tgz#800c0dd1e0a8bfbc95835c202ad220fe317e5a12"
integrity sha512-A46qtFgd+g7pDZinpnwiRJtxbC1hpgf0uzP3iG89scHk0AUC7A1TGxf5OiiOUv/JMZR8GOt8hL900hV0bOy5xA==
fill-range@^7.0.1:
version "7.0.1"
resolved "https://registry.yarnpkg.com/fill-range/-/fill-range-7.0.1.tgz#1919a6a7c75fe38b2c7c77e5198535da9acdda40"
integrity sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==
dependencies:
to-regex-range "^5.0.1"
fp-ts@^1.0.0:
version "1.19.5"
resolved "https://registry.yarnpkg.com/fp-ts/-/fp-ts-1.19.5.tgz#3da865e585dfa1fdfd51785417357ac50afc520a"
integrity sha512-wDNqTimnzs8QqpldiId9OavWK2NptormjXnRJTQecNjzwfyp6P/8s/zG8e4h3ja3oqkKaY72UlTjQYt/1yXf9A==
fsevents@^2.0.6:
version "2.0.7"
resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.0.7.tgz#382c9b443c6cbac4c57187cdda23aa3bf1ccfc2a"
integrity sha512-a7YT0SV3RB+DjYcppwVDLtn13UQnmg0SWZS7ezZD0UjnLwXmy8Zm21GMVGLaFGimIqcvyMQaOJBrop8MyOp1kQ==
get-func-name@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/get-func-name/-/get-func-name-2.0.0.tgz#ead774abee72e20409433a066366023dd6887a41"
integrity sha1-6td0q+5y4gQJQzoGY2YCPdaIekE=
get-source@^1.0.34:
version "1.0.37"
resolved "https://registry.yarnpkg.com/get-source/-/get-source-1.0.37.tgz#f1f3f799abd2a196de73ba8b853c38cd76c04583"
integrity sha512-Q0YbhnCgvqFe3OTuxkNvWCM+22O4hkx7D0g8em7iFW4vmw/TiX8dhsFIaP6yRkyN9ETHgzU190L3keCg2MADtg==
dependencies:
data-uri-to-buffer "^2.0.0"
source-map "^0.6.1"
glob-parent@^5.0.0:
version "5.0.0"
resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-5.0.0.tgz#1dc99f0f39b006d3e92c2c284068382f0c20e954"
integrity sha512-Z2RwiujPRGluePM6j699ktJYxmPpJKCfpGA13jz2hmFZC7gKetzrWvg5KN3+OsIFmydGyZ1AVwERCq1w/ZZwRg==
dependencies:
is-glob "^4.0.1"
has-flag@^3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-3.0.0.tgz#b5d454dc2199ae225699f3467e5a07f3b955bafd"
integrity sha1-tdRU3CGZriJWmfNGfloH87lVuv0=
io-ts@^1.8.6:
version "1.10.4"
resolved "https://registry.yarnpkg.com/io-ts/-/io-ts-1.10.4.tgz#cd5401b138de88e4f920adbcb7026e2d1967e6e2"
integrity sha512-b23PteSnYXSONJ6JQXRAlvJhuw8KOtkqa87W4wDtvMrud/DTJd5X+NpOOI+O/zZwVq6v0VLAaJ+1EDViKEuN9g==
dependencies:
fp-ts "^1.0.0"
is-binary-path@^2.1.0:
version "2.1.0"
resolved "https://registry.yarnpkg.com/is-binary-path/-/is-binary-path-2.1.0.tgz#ea1f7f3b80f064236e83470f86c09c254fb45b09"
integrity sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==
dependencies:
binary-extensions "^2.0.0"
is-extglob@^2.1.1:
version "2.1.1"
resolved "https://registry.yarnpkg.com/is-extglob/-/is-extglob-2.1.1.tgz#a88c02535791f02ed37c76a1b9ea9773c833f8c2"
integrity sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=
is-glob@^4.0.1:
version "4.0.1"
resolved "https://registry.yarnpkg.com/is-glob/-/is-glob-4.0.1.tgz#7567dbe9f2f5e2467bc77ab83c4a29482407a5dc"
integrity sha512-5G0tKtBTFImOqDnLB2hG6Bp2qcKEFduo4tZu9MT/H6NQv/ghhy30o55ufafxJ/LdH79LLs2Kfrn85TLKyA7BUg==
dependencies:
is-extglob "^2.1.1"
is-number@^7.0.0:
version "7.0.0"
resolved "https://registry.yarnpkg.com/is-number/-/is-number-7.0.0.tgz#7535345b896734d5f80c4d06c50955527a14f12b"
integrity sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==
isarray@0.0.1:
version "0.0.1"
resolved "https://registry.yarnpkg.com/isarray/-/isarray-0.0.1.tgz#8a18acfca9a8f4177e09abfc6038939b05d1eedf"
integrity sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8=
isomorphic-ws@^4.0.1:
version "4.0.1"
resolved "https://registry.yarnpkg.com/isomorphic-ws/-/isomorphic-ws-4.0.1.tgz#55fd4cd6c5e6491e76dc125938dd863f5cd4f2dc"
integrity sha512-BhBvN2MBpWTaSHdWRb/bwdZJ1WaehQ2L1KngkCkfLUGF0mAWAT1sQUQacEmQ0jXkFw/czDXPNQSL5u2/Krsz1w==
just-extend@^4.0.2:
version "4.0.2"
resolved "https://registry.yarnpkg.com/just-extend/-/just-extend-4.0.2.tgz#f3f47f7dfca0f989c55410a7ebc8854b07108afc"
integrity sha512-FrLwOgm+iXrPV+5zDU6Jqu4gCRXbWEQg2O3SKONsWE4w7AXFRkryS53bpWdaL9cNol+AmR3AEYz6kn+o0fCPnw==
lodash@^4.17.11:
version "4.17.15"
resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.15.tgz#b447f6670a0455bbfeedd11392eff330ea097548"
integrity sha512-8xOcRHvCjnocdS5cpwXQXVzmmh5e5+saE2QGoeQmbKmRS6J3VQppPOIt0MnmE+4xlZoumy0GPG0D0MVIQbNA1A==
lolex@^4.0.1, lolex@^4.1.0:
version "4.1.0"
resolved "https://registry.yarnpkg.com/lolex/-/lolex-4.1.0.tgz#ecdd7b86539391d8237947a3419aa8ac975f0fe1"
integrity sha512-BYxIEXiVq5lGIXeVHnsFzqa1TxN5acnKnPCdlZSpzm8viNEOhiigupA4vTQ9HEFQ6nLTQ9wQOgBknJgzUYQ9Aw==
mobx@^5.10.0:
version "5.13.0"
resolved "https://registry.yarnpkg.com/mobx/-/mobx-5.13.0.tgz#0fd68f10aa5ff2d146a4ed9e145b53337cfbca59"
integrity sha512-eSAntMSMNj0PFL705rgv+aB/z1RjNqDnFEpBe18yQVreXTWiVgIrmBUXzjnJfuba+eo4eAk6zi+/gXQkSUea8A==
nise@^1.4.10:
version "1.5.0"
resolved "https://registry.yarnpkg.com/nise/-/nise-1.5.0.tgz#d03ea0e6c1b75c638015aa3585eddc132949a50d"
integrity sha512-Z3sfYEkLFzFmL8KY6xnSJLRxwQwYBjOXi/24lb62ZnZiGA0JUzGGTI6TBIgfCSMIDl9Jlu8SRmHNACLTemDHww==
dependencies:
"@sinonjs/formatio" "^3.1.0"
"@sinonjs/text-encoding" "^0.7.1"
just-extend "^4.0.2"
lolex "^4.1.0"
path-to-regexp "^1.7.0"
normalize-path@^3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/normalize-path/-/normalize-path-3.0.0.tgz#0dcd69ff23a1c9b11fd0978316644a0388216a65"
integrity sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==
path-to-regexp@^1.7.0:
version "1.7.0"
resolved "https://registry.yarnpkg.com/path-to-regexp/-/path-to-regexp-1.7.0.tgz#59fde0f435badacba103a84e9d3bc64e96b9937d"
integrity sha1-Wf3g9DW62suhA6hOnTvGTpa5k30=
dependencies:
isarray "0.0.1"
pathval@^1.1.0:
version "1.1.0"
resolved "https://registry.yarnpkg.com/pathval/-/pathval-1.1.0.tgz#b942e6d4bde653005ef6b71361def8727d0645e0"
integrity sha1-uULm1L3mUwBe9rcTYd74cn0GReA=
picomatch@^2.0.4:
version "2.0.7"
resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.0.7.tgz#514169d8c7cd0bdbeecc8a2609e34a7163de69f6"
integrity sha512-oLHIdio3tZ0qH76NybpeneBhYVj0QFTfXEFTc/B3zKQspYfYYkWYgFsmzo+4kvId/bQRcNkVeguI3y+CD22BtA==
printable-characters@^1.0.42:
version "1.0.42"
resolved "https://registry.yarnpkg.com/printable-characters/-/printable-characters-1.0.42.tgz#3f18e977a9bd8eb37fcc4ff5659d7be90868b3d8"
integrity sha1-Pxjpd6m9jrN/zE/1ZZ176Qhos9g=
readdirp@^3.1.1:
version "3.1.1"
resolved "https://registry.yarnpkg.com/readdirp/-/readdirp-3.1.1.tgz#b158123ac343c8b0f31d65680269cc0fc1025db1"
integrity sha512-XXdSXZrQuvqoETj50+JAitxz1UPdt5dupjT6T5nVB+WvjMv2XKYj+s7hPeAVCXvmJrL36O4YYyWlIC3an2ePiQ==
dependencies:
picomatch "^2.0.4"
sinon@^7.2.7:
version "7.3.2"
resolved "https://registry.yarnpkg.com/sinon/-/sinon-7.3.2.tgz#82dba3a6d85f6d2181e1eca2c10d8657c2161f28"
integrity sha512-thErC1z64BeyGiPvF8aoSg0LEnptSaWE7YhdWWbWXgelOyThent7uKOnnEh9zBxDbKixtr5dEko+ws1sZMuFMA==
dependencies:
"@sinonjs/commons" "^1.4.0"
"@sinonjs/formatio" "^3.2.1"
"@sinonjs/samsam" "^3.3.1"
diff "^3.5.0"
lolex "^4.0.1"
nise "^1.4.10"
supports-color "^5.5.0"
source-map@^0.6.1:
version "0.6.1"
resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.6.1.tgz#74722af32e9614e9c287a8d0bbde48b5e2f1a263"
integrity sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==
stack-trace@^0.0.10:
version "0.0.10"
resolved "https://registry.yarnpkg.com/stack-trace/-/stack-trace-0.0.10.tgz#547c70b347e8d32b4e108ea1a2a159e5fdde19c0"
integrity sha1-VHxws0fo0ytOEI6hoqFZ5f3eGcA=
stacktracey@^1.2.112:
version "1.2.117"
resolved "https://registry.yarnpkg.com/stacktracey/-/stacktracey-1.2.117.tgz#7249d41937c37af29011173131d6b9aa62e6ec05"
integrity sha512-sj9olGCco8jxMw3fcmhL5er1yuVL2qSMhNfycwx+iIWz6uVpG+bQ2YNpVSlWscGmSskt61gSmUBjZ0Mb5jYBLw==
dependencies:
as-table "^1.0.36"
get-source "^1.0.34"
supports-color@^5.5.0:
version "5.5.0"
resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-5.5.0.tgz#e2e69a44ac8772f78a1ec0b35b689df6530efc8f"
integrity sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==
dependencies:
has-flag "^3.0.0"
to-regex-range@^5.0.1:
version "5.0.1"
resolved "https://registry.yarnpkg.com/to-regex-range/-/to-regex-range-5.0.1.tgz#1648c44aae7c8d988a326018ed72f5b4dd0392e4"
integrity sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==
dependencies:
is-number "^7.0.0"
type-detect@4.0.8, type-detect@^4.0.0, type-detect@^4.0.5:
version "4.0.8"
resolved "https://registry.yarnpkg.com/type-detect/-/type-detect-4.0.8.tgz#7646fb5f18871cfbb7749e69bd39a6388eb7450c"
integrity sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==
typescript@^3.5.3:
version "3.5.3"
resolved "https://registry.yarnpkg.com/typescript/-/typescript-3.5.3.tgz#c830f657f93f1ea846819e929092f5fe5983e977"
integrity sha512-ACzBtm/PhXBDId6a6sDJfroT2pOWt/oOnk4/dElG5G33ZL776N3Y6/6bKZJBFpd+b05F3Ct9qDjMeJmRWtE2/g==
ws@^7.0.0:
version "7.1.1"
resolved "https://registry.yarnpkg.com/ws/-/ws-7.1.1.tgz#f9942dc868b6dffb72c14fd8f2ba05f77a4d5983"
integrity sha512-o41D/WmDeca0BqYhsr3nJzQyg9NF5X8l/UdnFNux9cS3lwB+swm8qGWX5rn+aD6xfBU3rGmtHij7g7x6LxFU3A==
dependencies:
async-limiter "^1.0.0"

View File

@@ -1,30 +0,0 @@
const matrix = [
[1, 2, 3],
[4, 5, 6],
[7, 8, 9],
];
let str = "test1";
for (let i = 0; i < 100000; i++) {
str += i;
}
function randInt(max) {
return Math.floor(Math.random() * max);
}
const nodes = [];
for (let i = 0; i < 10; i++) {
nodes.push(i);
}
const edges = [];
for (let j = 0; j < 15; j++) {
edges.push([ randInt(nodes.length - 1), randInt(nodes.length - 1) ]);
}
console.log(matrix, str);

View File

@@ -1,9 +1,10 @@
.vscode/**
.vscode-test/**
out/test/**
out/**/*.map
src/**
.gitignore
tsconfig.json
vsc-extension-quickstart.md
tslint.json
**/tsconfig.json
**/tslint.json
**/*.map
**/*.ts

57
extension/package.json Normal file
View File

@@ -0,0 +1,57 @@
{
"name": "vscode-debug-visualizer",
"displayName": "VS Code Debug Visualizer",
"description": "",
"icon": "docs/logo.png",
"version": "0.1.0",
"license": "MIT",
"engines": {
"vscode": "^1.35.0"
},
"publisher": "hediet",
"author": {
"email": "henning.dieterichs@live.de",
"name": "Henning Dieterichs"
},
"readme": "./README.md",
"repository": {
"type": "git",
"url": "https://github.com/hediet/vscode-rpc-server.git"
},
"categories": [
"Other"
],
"activationEvents": [
"*"
],
"main": "./out/extension.js",
"contributes": {
"commands": [],
"configuration": {}
},
"scripts": {
"vscode:prepublish": "yarn run build",
"build": "tsc -p ./",
"dev": "tsc -watch -p ./"
},
"dependencies": {
"@hediet/std": "^0.6.0",
"@hediet/typed-json-rpc": "^0.7.7",
"@hediet/typed-json-rpc-websocket-server": "^0.7.7",
"@types/express": "^4.17.0",
"@types/serve-static": "^1.13.2",
"express": "^4.17.1",
"open": "^6.4.0",
"serve-static": "^1.14.1",
"ws": "^7.1.1"
},
"devDependencies": {
"@hediet/node-reload": "^0.7.2",
"@types/node": "^12.6.8",
"@types/vscode": "1.36.0",
"tslint": "^5.18.0",
"typescript": "^3.5.3",
"webpack": "^4.38.0",
"webpack-cli": "^3.3.6"
}
}

View File

@@ -0,0 +1,22 @@
import { DataExtractorId } from "@hediet/debug-visualizer-data-extraction";
import { DataExtractionState } from "@hediet/debug-visualizer-vscode-shared";
export interface DataSource {
createEvaluationWatcher(expression: string): EvaluationWatcher;
}
export interface EvaluationWatcher {
readonly expression: string;
// is observable
readonly preferredDataExtractor: DataExtractorId | undefined;
setPreferredDataExtractor(id: DataExtractorId): void;
refresh(): void;
// is observable
state: DataExtractionState;
dispose(): void;
}

View File

@@ -0,0 +1,157 @@
import { DataSource, EvaluationWatcher } from "./DataSource";
import { observable } from "mobx";
import * as vscode from "vscode";
import { Disposable } from "@hediet/std/disposable";
import {
selfContainedGetInitializedDataExtractorApi,
DataResult,
DataExtractorId,
ApiHasNotBeenInitializedCode,
selfContainedInitDataExtractorApi,
} from "@hediet/debug-visualizer-data-extraction";
import { DataExtractionState } from "@hediet/debug-visualizer-vscode-shared";
export function createJsDebuggerSource(): JsDataSource {
return new JsDebuggerSourceImplementation();
}
export interface JsCode extends String {
__brand: "JsCode";
}
export interface JsDataSource extends DataSource {
registerDataExtractor(classExpression: JsCode): void;
}
class JsDebuggerSourceImplementation implements JsDataSource {
private lastFrameId: number | null = null;
private readonly watchers = new Set<ObservableEvaluationWatcher>();
public readonly dispose = Disposable.fn();
constructor() {
this.dispose.track(
vscode.debug.onDidReceiveDebugSessionCustomEvent(e => {
if (e.event === "paused") {
const frameId = e.body.currentFrameId;
this.lastFrameId = frameId;
for (const w of this.watchers) {
w.refresh();
}
}
})
);
}
registerDataExtractor(classExpression: JsCode): void {}
public createEvaluationWatcher(expression: string): EvaluationWatcher {
const w = new ObservableEvaluationWatcher(expression, this);
this.watchers.add(w);
this.refresh(w);
return w;
}
public removeWatcher(w: ObservableEvaluationWatcher) {
this.watchers.delete(w);
}
public async refresh(w: ObservableEvaluationWatcher): Promise<void> {
const session = vscode.debug.activeDebugSession;
if (!session) {
w._state = { kind: "noDebugSession" };
return;
}
try {
w._state = { kind: "loading" };
const expression = `(${selfContainedGetInitializedDataExtractorApi.toString()})().getData(${
w.expression
})`;
const reply = await session.customRequest("evaluate", {
expression,
frameId: this.lastFrameId,
context: "watch",
});
const resultStr = reply.result as string;
const jsonData = resultStr.substr(1, resultStr.length - 2);
const result = JSON.parse(jsonData) as DataResult;
if (result.kind === "NoExtractors") {
throw new Error("No extractors");
}
w._state = {
kind: "data",
result: result.extractionResult,
};
} catch (error) {
const msg = error.message as string | undefined;
if (msg && msg.includes(ApiHasNotBeenInitializedCode)) {
if (await this.initializeApi(session)) {
await this.refresh(w);
return;
}
}
w._state = {
kind: "error",
message: error.message,
};
}
}
private async initializeApi(
session: vscode.DebugSession
): Promise<boolean> {
try {
const expression = `(${selfContainedInitDataExtractorApi.toString()})()`;
const reply = await session.customRequest("evaluate", {
expression,
frameId: this.lastFrameId,
context: "watch",
});
if (reply.result === "true") {
// register extractors
}
return true;
} catch (error) {
return false;
}
}
}
class ObservableEvaluationWatcher implements EvaluationWatcher {
constructor(
public readonly expression: string,
private readonly source: JsDebuggerSourceImplementation
) {}
@observable
_preferredDataExtractor: DataExtractorId | undefined = undefined;
get preferredDataExtractor(): DataExtractorId | undefined {
return this._preferredDataExtractor;
}
public setPreferredDataExtractor(id: DataExtractorId): void {
this._preferredDataExtractor = id;
this.refresh();
}
public refresh(): void {
this.source.refresh(this);
}
@observable
public _state: DataExtractionState = { kind: "loading" };
public get state(): DataExtractionState {
return this._state;
}
public dispose(): void {
this.source.removeWatcher(this);
}
}

224
extension/src/extension.ts Normal file
View File

@@ -0,0 +1,224 @@
import {
window,
OutputChannel,
ExtensionContext,
StatusBarAlignment,
commands,
workspace,
ViewColumn,
WebviewPanel,
} from "vscode";
import {
enableHotReload,
hotRequireExportedFn,
registerUpdateReconciler,
getReloadCount,
} from "@hediet/node-reload";
enableHotReload({ entryModule: module });
import { Disposable } from "@hediet/std/disposable";
import { createJsDebuggerSource } from "./DataSource/JsDebuggerSource";
import { debugVisualizerUIContract } from "@hediet/debug-visualizer-vscode-shared";
import { ConsoleRpcLogger } from "@hediet/typed-json-rpc";
import { EvaluationWatcher } from "./DataSource/DataSource";
import { WebSocketStream } from "@hediet/typed-json-rpc-websocket";
import { observable, autorun } from "mobx";
import * as express from "express";
import * as http from "http";
import * as serveStatic from "serve-static";
import { AddressInfo } from "net";
import WebSocket = require("ws");
import { join } from "path";
registerUpdateReconciler(module);
class WebView {
private expression: string | null = null;
private lastFrameId: number | null = null;
constructor(private readonly webviewPanel: WebviewPanel) {}
}
const debugVisualizer = "debugVisualizer";
function getHtml(server: Server) {
const isDev = true;
return `
<html>
<head>
<style>
html { height: 100%; width: 100%; padding: 0; margin: 0; }
body { height: 100%; width: 100%; padding: 0; margin: 0; }
iframe { height: 100%; width: 100%; padding: 0; margin: 0; border: 0; display: block; }
</style>
</head>
<body>
<script>
window.serverPort = ${server.port};
</script>
${
isDev
? `<iframe src="http://localhost:8080/?serverPort=${
server.port
}" tabindex="0"></iframe>`
: `<script type="text/javascript" src="${
server.mainBundle
}"></script>`
}
</body>
</html>
`;
}
class WebViews {
private readonly debugVisualizations = new Map<WebviewPanel, WebView>();
constructor(private readonly server: Server) {}
public createNew() {
const panel = window.createWebviewPanel(
debugVisualizer,
"Debug Visualizer",
ViewColumn.Two,
{ enableScripts: true }
);
this.setupView(panel);
}
public restore(webviewPanel: WebviewPanel) {
this.setupView(webviewPanel);
}
private setupView(webviewPanel: WebviewPanel) {
webviewPanel.webview.html = getHtml(this.server);
const view = new WebView(webviewPanel);
this.debugVisualizations.set(webviewPanel, view);
webviewPanel.onDidDispose(() => {
this.debugVisualizations.delete(webviewPanel);
});
}
public dispose() {
for (const panel of this.debugVisualizations.keys()) {
panel.dispose();
}
}
}
// js-debug value-printer value-viewer
// #src: bla
// editor-src typescript-ast-viewer
import * as open from "open";
class ConnectionHandler {
public readonly dispose = Disposable.fn();
@observable
private watcher: EvaluationWatcher | undefined = undefined;
constructor(sources: Sources, stream: WebSocketStream, server: Server) {
const {
client,
channel,
} = debugVisualizerUIContract.registerServerToStream(
stream,
new ConsoleRpcLogger(),
{
refresh: async () => {
if (this.watcher) {
this.watcher.refresh();
}
},
setExpression: async ({ newExpression }) => {
if (this.watcher) {
this.dispose.untrack(this.watcher).dispose();
}
this.watcher = this.dispose.track(
sources.jsSource.createEvaluationWatcher(newExpression)
);
},
openInBrowser: async ({}) => {
open(server.indexUrl);
},
}
);
this.dispose.track(
Disposable.create(
autorun(() => {
if (this.watcher) {
client.updateState({
newState: this.watcher.state,
});
}
})
)
);
stream.onClosed.then(() => {
this.dispose();
});
}
}
class Server {
private server: http.Server;
constructor(sources: Sources) {
const app = express();
app.use(serveStatic(join(__dirname, "../../ui/dist")));
this.server = app.listen();
const wss = new WebSocket.Server({ server: this.server });
wss.on("connection", ws => {
const stream = new WebSocketStream(ws);
new ConnectionHandler(sources, stream, this);
});
console.log(this.port);
}
public get indexUrl(): string {
return `http://localhost:${this.port}/index.html?serverPort=${
this.port
}`;
}
public get mainBundle(): string {
return `http://localhost:${this.port}/main-efe6bdc4b62fdc7971ee.js`;
}
public get port(): number {
const httpPort = (this.server.address() as AddressInfo).port;
return httpPort;
}
}
export class Sources {
public readonly jsSource = createJsDebuggerSource();
}
export class Extension {
public dispose = Disposable.fn();
private readonly sources = new Sources();
private readonly server = new Server(this.sources);
private readonly views = this.dispose.track(new WebViews(this.server));
constructor() {
const i = this.dispose.track(window.createStatusBarItem());
i.text = "reload" + getReloadCount(module);
i.show();
this.views.createNew();
}
}
export function activate(context: ExtensionContext) {
context.subscriptions.push(
hotRequireExportedFn(module, Extension, Extension => new Extension())
);
}
export function deactivate() {}

17
extension/tsconfig.json Normal file
View File

@@ -0,0 +1,17 @@
{
"compilerOptions": {
"module": "commonjs",
"target": "es6",
"outDir": "out",
"lib": ["es6"],
"sourceMap": true,
"rootDir": "src",
"strict": true,
"skipLibCheck": true,
"experimentalDecorators": true
},
"include": [
"src/**/*",
"../js-data-extractors/src/Extractors/TypeScriptDataExtractors.ts"
]
}

View File

@@ -0,0 +1,2 @@
require("ts-node").register();
module.exports = require("./webpack.config.ts");

View File

@@ -0,0 +1,14 @@
import * as webpack from "webpack";
import path = require("path");
import HtmlWebpackPlugin = require("html-webpack-plugin");
import MonacoWebpackPlugin = require("monaco-editor-webpack-plugin");
import ForkTsCheckerWebpackPlugin = require("fork-ts-checker-webpack-plugin");
const r = (file: string) => path.resolve(__dirname, file);
module.exports = {
target: "node",
entry: r("./src/extension"),
} as webpack.Configuration;
// todo

3784
extension/yarn.lock Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -1,16 +0,0 @@
* Plugins
* Hot reloadable, i.e. webframes
* Visualization of Source and Debug Expressions
* Debug Expression: `dbg("foo.bar")`
* Source: `src()`
* Multiple Visualizations
* Visualization in new Windows
* Mouse Curser Visualization
* Global/Local Visualizers
* Visualization Reduction
* Visualization History

View File

@@ -1,40 +1,9 @@
{
"name": "debug-visualizer",
"displayName": "debug-visualizer",
"description": "",
"version": "0.0.1",
"publisher": "hediet",
"engines": {
"vscode": "^1.30.0"
},
"categories": [
"Other"
],
"activationEvents": [
"onCommand:extension.sayHello",
"onWebviewPanel:debugVisualizer"
],
"main": "./out/extension",
"contributes": {
"commands": [
{
"command": "extension.sayHello",
"title": "Hello World"
}
]
},
"scripts": {
"vscode:prepublish": "npm run compile",
"compile": "tsc -p ./",
"watch": "tsc -watch -p ./",
"postinstall": "node ./node_modules/vscode/bin/install",
"test": "npm run compile && node ./node_modules/vscode/bin/test"
},
"devDependencies": {
"typescript": "^2.6.1",
"vscode": "^1.1.6",
"tslint": "^5.8.0",
"@types/node": "^7.0.43",
"@types/mocha": "^2.2.42"
}
"private": true,
"workspaces": [
"./shared",
"./data-extraction",
"./extension",
"./ui"
]
}

23
shared/package.json Normal file
View File

@@ -0,0 +1,23 @@
{
"name": "@hediet/debug-visualizer-vscode-shared",
"private": true,
"version": "1.0.0",
"main": "dist/index.js",
"types": "dist/index.d.ts",
"author": {
"name": "Henning Dieterichs",
"email": "henning.dieterichs@live.de"
},
"license": "MIT",
"files": [
"dist",
"src"
],
"scripts": {
"dev": "tsc --watch"
},
"dependencies": {
"@hediet/std": "^0.6.0",
"@hediet/typed-json-rpc": "^0.7.7"
}
}

39
shared/src/contract.ts Normal file
View File

@@ -0,0 +1,39 @@
import { contract, notificationContract, types } from "@hediet/typed-json-rpc";
import { DataExtractionResult } from "@hediet/debug-visualizer-data-extraction";
function unchecked<T>(): types.Type<T, T, unknown> {
return new types.Type<T, T, unknown>(
"unchecked",
(u): u is T => true,
value => types.success(value as T),
value => value
);
}
export type DataExtractionState =
| { kind: "loading" }
| { kind: "error"; message: string }
| { kind: "noDebugSession" }
| {
kind: "data";
result: DataExtractionResult;
};
export const debugVisualizerUIContract = contract({
client: {
updateState: notificationContract({
params: types.type({
newState: unchecked<DataExtractionState>(),
}),
}),
},
server: {
setExpression: notificationContract({
params: types.type({
newExpression: types.string,
}),
}),
refresh: notificationContract({}),
openInBrowser: notificationContract({}),
},
});

1
shared/src/index.ts Normal file
View File

@@ -0,0 +1 @@
export * from "./contract";

16
shared/tsconfig.json Normal file
View File

@@ -0,0 +1,16 @@
{
"compilerOptions": {
"target": "esnext",
"module": "commonjs",
"strict": true,
"outDir": "dist",
"skipLibCheck": true,
"rootDir": "./src",
"resolveJsonModule": true,
"declaration": true,
"declarationMap": true,
"newLine": "LF",
"sourceMap": true
},
"include": ["./src/**/*"]
}

View File

@@ -1,158 +0,0 @@
import * as vscode from 'vscode';
const debugVisualizer = "debugVisualizer";
function getHtml() {
return `
<html>
<head>
<style>
html { height: 100%; width: 100%; padding: 0; margin: 0; }
body { height: 100%; width: 100%; padding: 0; margin: 0; }
iframe { height: 100%; width: 100%; padding: 0; margin: 0; border: 0; display: block; }
</style>
</head>
<body>
<iframe src="http://localhost:8080/"></iframe>
<script>
const api = acquireVsCodeApi();
window.addEventListener('message', event => {
if (event.source === window.frames[0]) {
if (event.data.command === "setExpression") {
api.setState({ expression: event.data.expression });
}
if (event.data.command === "initialized") {
console.log("initialized, sent ", api.getState());
window.frames[0].postMessage({ command: "setExpression", expression: api.getState().expression }, "*");
} else {
console.log("iframe -> vscode", event.data);
api.postMessage(event.data);
}
} else {
console.log("vscode -> iframe", event.data);
window.frames[0].postMessage(event.data, "*");
}
});
</script>
</body>
</html>
`;
}
class DebugVisualizerViewSerializer implements vscode.WebviewPanelSerializer {
constructor(private readonly views: WebViews) {}
async deserializeWebviewPanel(webviewPanel: vscode.WebviewPanel, state: any) {
this.views.restore(webviewPanel);
}
}
class WebView {
private expression: string|null = null;
private lastFrameId: number|null = null;
constructor(private readonly webviewPanel: vscode.WebviewPanel) {
webviewPanel.webview.onDidReceiveMessage(e => {
if (e.command === "setExpression") {
this.expression = e.expression;
if (this.lastFrameId) {
this.reevaluateExpression(this.lastFrameId);
}
}
});
}
public getExpression(): string|null {
return this.expression;
}
public async reevaluateExpression(frameId: number) {
this.lastFrameId = frameId;
if (!this.expression) { return; }
const session = vscode.debug.activeDebugSession;
if (session) {
try {
const reply = await session.customRequest("evaluate", { "expression": this.expression, frameId, context: "watch" });
this.webviewPanel.webview.postMessage({
command: "setValue",
value: { data: reply.result }
});
}
catch (error) {
vscode.window.showInformationMessage(`error: ${error.message}`);
}
}
}
}
class WebViews {
private readonly debugVisualizations = new Map<vscode.WebviewPanel, WebView>();
public createNew() {
const panel = vscode.window.createWebviewPanel(
debugVisualizer,
'Debug Visualizer',
vscode.ViewColumn.Two,
{ enableScripts: true }
);
this.setupView(panel);
}
public restore(webviewPanel: vscode.WebviewPanel) {
this.setupView(webviewPanel);
}
private setupView(webviewPanel: vscode.WebviewPanel) {
webviewPanel.webview.html = getHtml();
const view = new WebView(webviewPanel);
this.debugVisualizations.set(webviewPanel, view);
webviewPanel.onDidDispose(() => {
this.debugVisualizations.delete(webviewPanel);
});
}
public reevaluateExpressions(frameId: number) {
for (const vis of this.debugVisualizations.values()) {
vis.reevaluateExpression(frameId);
}
}
}
export function activate(context: vscode.ExtensionContext) {
const views = new WebViews();
vscode.debug.onDidReceiveDebugSessionCustomEvent(e => {
if (e.event === "paused") {
const frameId = e.body.currentFrameId;
views.reevaluateExpressions(frameId);
}
})
vscode.window.registerWebviewPanelSerializer(debugVisualizer,
new DebugVisualizerViewSerializer(views));
let disposable = vscode.commands.registerCommand('extension.sayHello', () => {
views.createNew();
//const session = vscode.debug.activeDebugSession;
/*
if (session) {
session.customRequest("evaluate", { "expression": "Math.sqrt(10)" }).then(reply => {
vscode.window.showInformationMessage(`result: ${reply.result}`);
}, error => {
vscode.window.showInformationMessage(`error: ${error.message}`);
});
}*/
});
context.subscriptions.push(disposable);
}
// this method is called when your extension is deactivated
export function deactivate() {
}

View File

@@ -1,22 +0,0 @@
//
// Note: This example test is leveraging the Mocha test framework.
// Please refer to their documentation on https://mochajs.org/ for help.
//
// The module 'assert' provides assertion methods from node
import * as assert from 'assert';
// You can import and use all API from the 'vscode' module
// as well as import your extension to test it
// import * as vscode from 'vscode';
// import * as myExtension from '../extension';
// Defines a Mocha test suite to group tests of similar kind together
suite("Extension Tests", function () {
// Defines a Mocha unit test
test("Something 1", function() {
assert.equal(-1, [1, 2, 3].indexOf(5));
assert.equal(-1, [1, 2, 3].indexOf(0));
});
});

View File

@@ -1,22 +0,0 @@
//
// PLEASE DO NOT MODIFY / DELETE UNLESS YOU KNOW WHAT YOU ARE DOING
//
// This file is providing the test runner to use when running extension tests.
// By default the test runner in use is Mocha based.
//
// You can provide your own test runner if you want to override it by exporting
// a function run(testRoot: string, clb: (error:Error) => void) that the extension
// host can call to run the tests. The test runner is expected to use console.log
// to report the results back to the caller. When the tests are finished, return
// a possible error to the callback or null if none.
import * as testRunner from 'vscode/lib/testrunner';
// You can directly control Mocha options by uncommenting the following lines
// See https://github.com/mochajs/mocha/wiki/Using-mocha-programmatically#set-options for more info
testRunner.configure({
ui: 'tdd', // the TDD UI is being used in extension.test.ts (suite, test, etc.)
useColors: true // colored output from test results
});
module.exports = testRunner;

View File

@@ -1,6 +1,6 @@
{
"extends": "recommended",
"rules": {
"await-promise": true
}
}
{
"extends": "recommended",
"rules": {
"await-promise": true
}
}

48
ui/package.json Normal file
View File

@@ -0,0 +1,48 @@
{
"name": "my-website",
"version": "0.0.1",
"scripts": {
"dev": "webpack-dev-server --hot",
"build": "webpack"
},
"dependencies": {
"@blueprintjs/core": "^3.17.1",
"@hediet/std": "^0.6.0",
"@types/html-webpack-plugin": "^3.2.0",
"@types/react-measure": "^2.0.5",
"@types/react-svg-pan-zoom": "^2.5.7",
"@types/webpack": "^4.32.1",
"blueprintjs": "^0.0.8",
"classnames": "^2.2.6",
"mobx": "^5.10.1",
"mobx-react": "^6.1.1",
"monaco-editor": "^0.17.1",
"popper.js": "^1.15.0",
"react": "^16.8.6",
"react-dom": "^16.8.6",
"react-measure": "^2.3.0",
"react-svg-pan-zoom": "^3.1.0",
"viz.js": "^2.1.2"
},
"devDependencies": {
"@types/classnames": "^2.2.8",
"@types/react": "^16.8.21",
"@types/react-dom": "^16.8.4",
"css-loader": "^3.0.0",
"file-loader": "^4.0.0",
"fork-ts-checker-webpack-plugin": "^1.4.2",
"html-webpack-plugin": "^3.2.0",
"monaco-editor-webpack-plugin": "^1.7.0",
"node-sass": "^4.12.0",
"raw-loader": "^3.0.0",
"sass": "^1.20.1",
"sass-loader": "^7.1.0",
"style-loader": "^0.23.1",
"ts-loader": "^6.0.3",
"ts-node": "^8.3.0",
"typescript": "^3.5.2",
"webpack": "^4.35.0",
"webpack-cli": "^3.3.4",
"webpack-dev-server": "^3.7.2"
}
}

View File

@@ -0,0 +1,149 @@
import { observer, disposeOnUnmount } from "mobx-react";
import * as classNames from "classnames";
import * as React from "react";
import * as ReactDOM from "react-dom";
import { ReactSVGPanZoom, Tool, Value } from "react-svg-pan-zoom";
import { computed, observable, autorun, runInAction, action } from "mobx";
import Measure, { ContentRect } from "react-measure";
import Viz from "viz.js";
import { Module, render } from "viz.js/full.render.js";
export interface GraphData {
kind: "GraphData";
nodes: { id: string; label: string }[];
edges: { from: string; to: string; label: string }[];
}
@observer
export class DotGraphViewer extends React.Component<{ data: GraphData }> {
render() {
const { nodes, edges } = this.props.data;
const dotContent = `
digraph MyGraph {
${nodes
.map(n => `${n.id} [ label = ${JSON.stringify(n.label)} ];`)
.join("\n ")}
${edges
.map(
e =>
`${e.from} -> ${e.to} [ label = ${JSON.stringify(
e.label
)} ];`
)
.join("\n")}
}
`;
return <DotViewer dotCode={dotContent} />;
}
}
const viz: any = new Viz({
Module: () => Module({ TOTAL_MEMORY: 1 << 30 }),
render,
});
@observer
export class DotViewer extends React.Component<{
dotCode: string;
svgRef?: (element: SVGSVGElement | null) => void;
}> {
@observable private svg: string | null = null;
@disposeOnUnmount
// @ts-ignore
private readonly _updateSvgAutorun = autorun(async () => {
const svg = await viz.renderString(this.props.dotCode);
runInAction("Update svg", () => (this.svg = svg));
});
render() {
if (!this.svg) {
return <div>Loading...</div>;
}
return <SvgViewer svgRef={this.props.svgRef} svgContent={this.svg} />;
}
}
function widthOrDefault(r: ContentRect): number {
if (r.bounds && r.bounds.width) {
return r.bounds.width;
}
return 1;
}
function heightOrDefault(r: ContentRect): number {
console.log(r);
if (r.bounds && r.bounds.height) {
return r.bounds.height;
}
return 1;
}
@observer
export class SvgViewer extends React.Component<{
svgContent: string;
svgRef?: (element: SVGSVGElement | null) => void;
}> {
@observable tool: Tool = "pan";
@action.bound
private setTool(tool: Tool): void {
this.tool = tool;
}
private setRef(element: ReactSVGPanZoom | null) {
if (this.props.svgRef) {
if (!element) {
this.props.svgRef(null);
return;
}
const svg = (element as any).ViewerDOM as SVGSVGElement;
this.props.svgRef(svg);
}
}
@observable value: Value | {} = {};
render() {
let { svgContent } = this.props;
let width: number = 0;
let height: number = 0;
svgContent = svgContent.replace(
/viewBox="[0-9\.]+ [0-9\.]+ ([0-9\.]+) ([0-9\.]+)"/,
(r, w, h) => {
width = parseFloat(w);
height = parseFloat(h);
return "";
}
);
const tool = this.tool;
const val = this.value;
return (
<Measure bounds>
{({ measureRef, contentRect }) => (
<div ref={measureRef} className="svgViewer">
<ReactSVGPanZoom
width={widthOrDefault(contentRect)}
height={heightOrDefault(contentRect)}
tool={tool}
value={val}
onChangeValue={v => (this.value = v)}
onChangeTool={this.setTool}
ref={e => this.setRef(e)}
>
<svg width={width} height={height}>
<g
dangerouslySetInnerHTML={{
__html: svgContent,
}}
/>
</svg>
</ReactSVGPanZoom>
</div>
)}
</Measure>
);
}
}

256
ui/src/Components/Point.ts Normal file
View File

@@ -0,0 +1,256 @@
function sqr(a: number) {
return a * a;
}
export type PointLike =
| Point
| { x?: number; y: number }
| { x: number; y?: number };
export function point(data: PointLike) {
if (data instanceof Point) {
return data;
}
return new Point(data.x || 0, data.y || 0);
}
export class Point {
public static readonly Zero = new Point(0, 0);
constructor(public readonly x: number, public readonly y: number) {}
public mapXY(fn: (arg: number) => number): Point {
return new Point(fn(this.x), fn(this.y));
}
public distance(other: PointLike = Point.Zero): number {
const d = this.sub(other);
return Math.sqrt(sqr(d.x) + sqr(d.y));
}
public sub(other: PointLike): Point {
const o = point(other);
return new Point(this.x - o.x, this.y - o.y);
}
public add(other: PointLike): Point {
const o = point(other);
return new Point(this.x + o.x, this.y + o.y);
}
public mul(scalar: number): Point {
return new Point(this.x * scalar, this.y * scalar);
}
public div(scalar: number): Point {
return new Point(this.x / scalar, this.y / scalar);
}
public equals(other: PointLike) {
const o = point(other);
return this.x === o.x && this.y === o.y;
}
public angle(): number {
const angle = Math.atan2(this.x, this.y);
return angle;
}
public getPointCloserTo(dest: PointLike, dist: number): Point {
if (this.equals(dest)) return this;
const angle = point(dest)
.sub(this)
.angle();
const result = new Point(
this.x + Math.sin(angle) * dist,
this.y + Math.cos(angle) * dist
);
return result;
}
public toJson(): { x: number; y: number } {
return { x: this.x, y: this.y };
}
}
function turn(p1: Point, p2: Point, p3: Point): number {
const A = (p3.y - p1.y) * (p2.x - p1.x);
const B = (p2.y - p1.y) * (p3.x - p1.x);
return A > B + Number.MIN_VALUE ? 1 : A + Number.MIN_VALUE < B ? -1 : 0;
}
export function isIntersect(
aStart: Point,
aEnd: Point,
bStart: Point,
bEnd: Point
): boolean {
return (
turn(aStart, bStart, bEnd) != turn(aEnd, bStart, bEnd) &&
turn(aStart, aEnd, bStart) != turn(aStart, aEnd, bEnd)
);
}
/**
* Liang-Barsky function by Daniel White
*
* @link http://www.skytopia.com/project/articles/compsci/clipping.html
*
* @param {number} x0
* @param {number} y0
* @param {number} x1
* @param {number} y1
* @param {array<number>} bbox
* @return {array<array<number>>|null}
*/
function liangBarsky(
x0: number,
y0: number,
x1: number,
y1: number,
bbox: [number, number, number, number]
): [[number, number], [number, number]] | null {
let [xmin, xmax, ymin, ymax] = bbox;
let t0 = 0,
t1 = 1;
let dx = x1 - x0,
dy = y1 - y0;
let p = 0,
q = 0,
r = 0;
for (let edge: 0 | 1 | 2 | 3 = 0; edge < 4; edge++) {
// Traverse through left, right, bottom, top edges.
if (edge === 0) {
p = -dx;
q = -(xmin - x0);
} else if (edge === 1) {
p = dx;
q = xmax - x0;
} else if (edge === 2) {
p = -dy;
q = -(ymin - y0);
} else if (edge === 3) {
p = dy;
q = ymax - y0;
}
r = q / p;
if (p === 0 && q < 0) return null; // Don't draw line at all. (parallel line outside)
if (p < 0) {
if (r > t1) return null;
// Don't draw line at all.
else if (r > t0) t0 = r; // Line is clipped!
} else if (p > 0) {
if (r < t0) return null;
// Don't draw line at all.
else if (r < t1) t1 = r; // Line is clipped!
}
}
return [[x0 + t0 * dx, y0 + t0 * dy], [x0 + t1 * dx, y0 + t1 * dy]];
}
export function intersectRectWithLine(
start: Point,
end: Point,
rect: Rectangle
): { first: Point; second: Point } | undefined {
const r = liangBarsky(start.x, start.y, end.x, end.y, [
rect.topLeft.x,
rect.bottomRight.x,
rect.topLeft.y,
rect.bottomRight.y,
]);
if (!r) {
return undefined;
}
return {
first: new Point(r[0][0], r[0][1]),
second: new Point(r[1][0], r[1][1]),
};
}
// first zoom then translate
export function scale(
clientOffset: Point,
clientSize: Point,
viewSize: Point
): { clientZoom: number; clientOffset: Point } {
const clientRatio = clientSize.x / clientSize.y;
const viewRatio = viewSize.x / viewSize.y;
let zoom = 1;
if (clientRatio < viewRatio) zoom = viewSize.y / clientSize.y;
else zoom = viewSize.x / clientSize.x;
const clientMid = clientOffset.mul(zoom).add(clientSize.mul(zoom / 2));
const viewMid = viewSize.div(2);
const clientOffset2 = viewMid.sub(clientMid);
return { clientOffset: clientOffset2, clientZoom: zoom };
}
export class Rectangle {
public static ofSize(position: PointLike, size: PointLike): Rectangle {
const pos = point(position);
return new Rectangle(pos, pos.add(size));
}
public static spanning(first: Point, ...rest: Point[]): Rectangle {
let min = first.toJson();
let max = first.toJson();
for (const p of rest) {
min.x = Math.min(min.x, p.x);
min.y = Math.min(min.y, p.y);
max.x = Math.max(max.x, p.x);
max.y = Math.max(max.y, p.y);
}
return new Rectangle(point(min), point(max));
}
constructor(
public readonly topLeft: Point,
public readonly bottomRight: Point
) {}
get center(): Point {
return this.bottomRight.add(this.topLeft).div(2);
}
get size(): Point {
return this.bottomRight.sub(this.topLeft);
}
get topRight(): Point {
return new Point(this.bottomRight.x, this.topLeft.y);
}
get bottomLeft(): Point {
return new Point(this.topLeft.x, this.bottomRight.y);
}
public intersects(selectionRect: Rectangle): boolean {
return !(
selectionRect.topLeft.x > this.bottomRight.x ||
selectionRect.bottomRight.x < this.topLeft.x ||
selectionRect.topLeft.y > this.bottomRight.y ||
selectionRect.bottomRight.y < this.topLeft.y
);
}
equals(other: Rectangle) {
return (
this.topLeft.equals(other.topLeft) &&
this.bottomRight.equals(other.bottomRight)
);
}
}

View File

@@ -0,0 +1,102 @@
import { Point, Rectangle } from "./Point";
import * as React from "react";
import { Properties } from "csstype";
type Omit<T, K> = Pick<T, Exclude<keyof T, K>>;
export function omit<T, K extends keyof T>(obj: T, keys: K[]): Omit<T, K> {
const newObj: Omit<T, K> = {} as any;
for (const [key, val] of Object.entries(obj)) {
if (!keys.includes(key as K)) {
(newObj as any)[key] = val;
}
}
return newObj;
}
export interface SvgAttributes<T> extends React.DOMAttributes<T> {
stroke?: string;
className?: string;
style?: Properties;
pointerEvents?: "none" | "auto" | "initial";
}
export function SvgText(
props: {
position: Point;
children: any;
childRef?: React.Ref<SVGTextElement>;
textAnchor?: "middle" | "end" | "start";
dominantBaseline?: "central" | "middle";
} & SvgAttributes<SVGTextElement>
) {
return (
<text
x={props.position.x}
y={props.position.y}
ref={props.childRef}
{...omit(props, ["position", "childRef"])}
/>
);
}
export function SvgCircle(
props: {
center: Point;
radius: number;
childRef?: React.Ref<SVGCircleElement>;
} & SvgAttributes<SVGCircleElement>
) {
return (
<circle
cx={props.center.x}
cy={props.center.y}
r={props.radius}
ref={props.childRef}
{...omit(props, ["center", "radius", "childRef"])}
/>
);
}
export function SvgLine(
props: { start: Point; end: Point } & SvgAttributes<SVGLineElement>
) {
return (
<line
x1={props.start.x}
y1={props.start.y}
x2={props.end.x}
y2={props.end.y}
{...omit(props, ["end", "start"])}
/>
);
}
export function SvgRect(
props: { rectangle: Rectangle; fill?: string } & SvgAttributes<
SVGRectElement
>
) {
const r = props.rectangle;
return (
<rect
x={r.topLeft.x}
y={r.topLeft.y}
width={r.size.x}
height={r.size.y}
{...omit(props, ["rectangle"])}
/>
);
}
/*
export function Svg(props: { position: Point } & SvgAttributes<SVGSVGElement>) {
return (
<svg
x={props.position.x}
y={props.position.y}
{...omit(props, ["position"])}
/>
);
}
*/

296
ui/src/Components/index.tsx Normal file
View File

@@ -0,0 +1,296 @@
import React = require("react");
import { Model } from "../Model";
import { observer } from "mobx-react";
import { observable, action, computed } from "mobx";
import { Popover, Icon, Button } from "@blueprintjs/core";
import classnames = require("classnames");
import Measure from "react-measure";
import { DotGraphViewer } from "./GraphViz";
import { TreeView } from "../Visualizers/TreeVisualizer/Views";
import { createTreeViewModelFromTreeNodeData } from "../Visualizers/TreeVisualizer/Visualizers";
import { CommonDataTypes } from "@hediet/debug-visualizer-data-extraction";
@observer
export class GUI extends React.Component<{ model: Model }> {
@observable text: string = "node";
@observable expanded = false;
render() {
const m = this.props.model;
return (
<div className="component-GUI" tabIndex={0}>
<div className="part-header">
<ExpandButton
expanded={this.expanded}
setExpanded={v => (this.expanded = v)}
/>
<div className="part-header-content">
<div className="part-header-main">
<ExpressionInput model={m} />
<Button
minimal
small
className="part-icon"
icon="refresh"
onClick={() => m.refresh()}
/>
<Button
minimal
small
className="part-icon"
icon="log-in"
onClick={() => m.openBrowser()}
/>
<Button
minimal
small
className="part-icon"
icon="locate"
onClick={() => m.useSelectionAsExpression()}
/>
</div>
{this.expanded && (
<>
<div style={{ height: 6 }} />
<div className="part-header-options">
<NamedSelect
name="Source"
selected={0}
options={[{ label: "js" }]}
/>
<div style={{ width: 10 }} />
<NamedSelect
name="Extractor"
selected={0}
options={[{ label: "literal" }]}
/>
<div style={{ width: 10 }} />
<NamedSelect
name="Visualizer"
selected={0}
options={[{ label: "literal" }]}
/>
</div>
</>
)}
</div>
</div>
<div className="part-visualizer">
<Visualizer model={m} />
</div>
</div>
);
}
renderValue(value: string) {
return <div>{`${value.length}`}</div>;
}
}
@observer
export class ExpressionInput extends React.Component<{ model: Model }> {
@observable editedExpression: string | undefined;
@computed get currentExpression(): string {
return this.editedExpression !== undefined
? this.editedExpression
: this.props.model.expression;
}
@action.bound
updateCurrentExpression(newValue: string) {
this.editedExpression = newValue;
}
@action.bound
submit() {
if (!this.editedExpression) {
return;
}
this.props.model.setExpression(this.editedExpression);
this.editedExpression = undefined;
}
render() {
return (
<input
className="part-expression-input"
value={this.currentExpression}
onChange={e => this.updateCurrentExpression(e.target.value)}
onBlur={this.submit}
onKeyPress={e => {
if (e.charCode === 13) {
// enter
this.submit();
}
}}
/>
);
}
}
@observer
export class Visualizer extends React.Component<{ model: Model }> {
render() {
return (
<div className="component-Visualizer">{this.renderContent()}</div>
);
/*
<DotGraphViewer
data={{
kind: "GraphData",
edges: [
{
from: "1",
to: "2",
label: "test",
},
],
nodes: [
{ id: "1", label: "foo" },
{ id: "2", label: "bar" },
],
}}
/>
*/
}
renderContent(): JSX.Element {
const s = this.props.model.state;
if (s.kind === "loading") {
return <NoData label="Loading" />;
} else if (s.kind === "error") {
return <NoData label={"Error: " + s.message} />;
} else if (s.kind === "noExpression") {
return <NoData label="No Expression" />;
} else if (s.kind === "noDebugSession") {
return <NoData label="No Debug Session" />;
} else if (s.kind === "data") {
const data = s.result.data;
if (data.kind.tree) {
const d = data as CommonDataTypes.TreeNodeData;
const m = createTreeViewModelFromTreeNodeData(d.root);
return <TreeView model={m} />;
} else {
return <pre>{JSON.stringify(s.result)}</pre>;
}
} else {
const nvr: never = s;
return <div />;
}
}
}
@observer
class NoData extends React.Component<{ label: string }> {
@observable height = 0;
@observable width = 0;
render() {
const { width, height } = this;
return (
<Measure
client={true}
onResize={e => {
if (e.client) {
this.height = e.client.height;
this.width = e.client.width;
}
}}
>
{({ measureRef }) => (
<div className="component-NoData" ref={measureRef}>
<svg>
<line x1={0} y1={0} x2={width} y2={height} />
<line x1={width} y1={0} x2={0} y2={height} />
<rect
x={width / 2 - 50}
y={height / 2 - 20}
width={100}
height={40}
/>
<text
x={width / 2}
y={height / 2}
textAnchor="middle"
alignmentBaseline="central"
children={this.props.label}
/>
</svg>
</div>
)}
</Measure>
);
}
}
export class ExpandButton extends React.Component<{
expanded: boolean;
setExpanded: (val: boolean) => void;
}> {
render() {
return (
<div
onClick={() => this.props.setExpanded(!this.props.expanded)}
className={classnames(
"component-ExpandButton",
this.props.expanded && "expanded"
)}
/>
);
}
}
function NamedSelect(props: {
name: string;
selected: number;
options: SelectableItem[];
}) {
return (
<div className="component-NamedSelect">
<div className="part-Select">
<Select {...props} />
</div>
<div>{props.name}</div>
</div>
);
}
interface SelectableItem {
label: string;
}
@observer
export class Select extends React.Component<{
selected: number;
options: SelectableItem[];
}> {
@computed get selected(): SelectableItem {
return this.props.options[this.props.selected];
}
render() {
return (
<div className="component-Select">
<Popover
position="auto-start"
modifiers={{ arrow: { enabled: false } }}
usePortal={false}
>
<button className="part-button">
{this.selected.label}
</button>
<div className={"part-content"}>
{this.props.options.map((o, idx) => (
<div key={idx}>{o.label}</div>
))}
</div>
</Popover>
</div>
);
}
}

88
ui/src/Model.ts Normal file
View File

@@ -0,0 +1,88 @@
import {
debugVisualizerUIContract,
DataExtractionState,
} from "@hediet/debug-visualizer-vscode-shared";
import { WebSocketStream } from "@hediet/typed-json-rpc-websocket";
import { ConsoleRpcLogger } from "@hediet/typed-json-rpc";
import { observable, action } from "mobx";
declare const window: Window & {
serverPort?: number;
};
export class Model {
port: number;
@observable expression: string = "";
@observable state: DataExtractionState | { kind: "noExpression" } = {
kind: "noExpression",
};
private server:
| typeof debugVisualizerUIContract.TServerInterface
| undefined = undefined;
constructor() {
if (window.serverPort) {
this.port = window.serverPort;
} else {
const url = new URL(window.location.href);
const portStr = url.searchParams.get("serverPort");
if (!portStr) {
throw new Error("No port given.");
}
this.port = parseInt(portStr);
}
this.stayConnected();
}
@action
setExpression(newExpression: string) {
this.expression = newExpression;
if (this.server) {
this.server.setExpression({ newExpression });
}
}
refresh() {
if (this.server) {
this.server.refresh();
}
}
async stayConnected(): Promise<void> {
while (true) {
try {
const stream = await WebSocketStream.connectTo({
host: "localhost",
port: this.port,
});
const {
server,
} = debugVisualizerUIContract.getServerFromStream(
stream,
new ConsoleRpcLogger(),
{
updateState: ({ newState }) => {
this.state = newState;
},
}
);
this.server = server;
await stream.onClosed;
} catch (e) {}
}
}
openBrowser() {
if (this.server) {
this.server.openInBrowser();
}
}
useSelectionAsExpression() {
throw new Error("Method not implemented.");
}
}

View File

@@ -0,0 +1,37 @@
import { TreeViewModel, TreeNodeViewModel, TreeWithPathView } from "./Views";
import React = require("react");
export function SampleTreeVisualizer() {
interface SampleTreeNode {
parentPropertyName: string | undefined;
name: string;
children: SampleTreeNode[];
}
const data = require("./sample-input.json") as SampleTreeNode;
const m = new TreeViewModel();
m.root = createModel(data, m);
m.root.children[0].children[4].children[1].children[0].isMarked = true;
function createModel(
root: SampleTreeNode,
viewModel: TreeViewModel
): TreeNodeViewModel {
const children: TreeNodeViewModel[] = root.children.map(c =>
createModel(c, viewModel)
);
const model = new TreeNodeViewModel(
viewModel,
root.parentPropertyName,
root.name,
children
);
for (const c of children) {
c.parent = model;
}
return model;
}
return <TreeWithPathView model={m} />;
}

View File

@@ -0,0 +1,409 @@
import { observer, disposeOnUnmount } from "mobx-react";
import { observable, autorun, computed, action, runInAction } from "mobx";
import React = require("react");
import classNames = require("classnames");
import { Rectangle, Point } from "../../Components/Point";
import { SvgLine } from "../../Components/SvgElements";
import "./style.scss";
export class TreeViewModel {
private version = 0;
private lastUpdateVersion = -1;
@observable public root: TreeNodeViewModel | undefined = undefined;
public invalidateOffsets() {
this.version++;
}
@action
public updateOffsets() {
if (!this.root) {
return;
}
if (this.lastUpdateVersion === this.version) {
return;
}
this.lastUpdateVersion = this.version;
this.root.updateOffsets();
}
@observable selected: TreeNodeViewModel | undefined = undefined;
@action public select(current: TreeNodeViewModel | undefined) {
if (this.selected) {
this.selected.isSelected = false;
}
if (current) {
current.isSelected = true;
}
this.selected = current;
}
private selectOnHoverEnabled = false;
private selectedBeforeHover: TreeNodeViewModel | undefined = undefined;
@action public hover(current: TreeNodeViewModel | undefined) {
if (this.selectOnHoverEnabled) {
if (current) {
this.select(current);
} else {
this.select(this.selectedBeforeHover);
}
}
}
selectOnHover(enable: boolean) {
if (this.selectOnHoverEnabled === enable) {
return;
}
if (enable) {
this.selectedBeforeHover = this.selected;
} else if (!this.selected) {
this.select(this.selectedBeforeHover);
}
this.selectOnHoverEnabled = enable;
}
}
export class TreeNodeViewModel {
public parent: TreeNodeViewModel | null = null;
constructor(
public readonly treeViewModel: TreeViewModel,
public readonly id: string | undefined,
public readonly name: string,
public readonly children: TreeNodeViewModel[]
) {}
@observable expanderOffsetRect: Rectangle | undefined = undefined;
private expanderDiv: HTMLDivElement | null = null;
@action.bound
setExpanderDiv(div: HTMLDivElement | null) {
this.expanderDiv = div;
this.treeViewModel.invalidateOffsets();
}
@observable public expanded: boolean = false;
@observable public isMarked: boolean = false;
@observable public isSelected: boolean = false;
@action
public toggleExpanded() {
this.expanded = !this.expanded;
this.treeViewModel.invalidateOffsets();
}
@computed public get isChildOrThisMarked(): boolean {
if (this.isMarked) return true;
return this.children.some(c => c.isChildOrThisMarked);
}
@computed public get isChildOrThisSelected(): boolean {
if (this.isSelected) return true;
return this.children.some(c => c.isChildOrThisSelected);
}
@computed public get isParentOrThisSelected(): boolean {
if (this.isSelected) return true;
if (this.parent) return this.parent.isParentOrThisSelected;
return false;
}
get path(): string[] {
const result = new Array<string>();
let c: TreeNodeViewModel = this;
while (c.parent) {
result.unshift(c.id || c.name);
c = c.parent;
}
if (result.length === 0) {
result.push("root");
}
return result;
}
updateOffsets() {
for (const c of this.children) {
c.updateOffsets();
}
if (!this.expanderDiv) {
return;
}
const newRect = Rectangle.ofSize(
new Point(this.expanderDiv.offsetLeft, this.expanderDiv.offsetTop),
new Point(
this.expanderDiv.offsetWidth,
this.expanderDiv.offsetHeight
)
);
if (
!this.expanderOffsetRect ||
!newRect.equals(this.expanderOffsetRect)
) {
this.expanderOffsetRect = newRect;
}
}
}
@observer
export class TreeWithPathView extends React.Component<{
model: TreeViewModel;
}> {
render() {
const model = this.props.model;
return (
<div className="component-TreeWithPathView">
<div className="part-path">
<span>
{model.selected ? (
<span>{model.selected.path.join(" / ")}</span>
) : (
"(Nothing Selected)"
)}
</span>
</div>
<div className="part-tree">
<TreeView model={model} />
</div>
</div>
);
}
}
@observer
export class TreeView extends React.Component<{ model: TreeViewModel }> {
render() {
const model = this.props.model;
return (
<div
tabIndex={0}
className="component-TreeView"
onKeyDown={e => {
if (e.keyCode === 18) {
// alt
e.preventDefault();
e.stopPropagation();
model.selectOnHover(true);
}
}}
onKeyUp={e => {
if (e.keyCode === 18) {
// alt
e.preventDefault();
e.stopPropagation();
model.selectOnHover(false);
}
}}
>
{model.root && (
<>
<svg className="part-svg">
<TreeNodeSvgView model={model.root} />
</svg>
<div className="part-node">
<TreeNodeView model={model.root} />
</div>
</>
)}
</div>
);
}
}
@observer
export class TreeNodeSvgView extends React.Component<{
model: TreeNodeViewModel;
}> {
render() {
const model = this.props.model;
if (
!model.expanderOffsetRect ||
model.children.length === 0 ||
model.expanded
) {
return <g />;
}
function findLastExpanderOffset(
predicate: (v: TreeNodeViewModel) => boolean
): Rectangle | undefined {
const all = model.children
.filter(predicate)
.map(c => c.expanderOffsetRect);
const last = all[all.length - 1];
return last;
}
const lastOffset = findLastExpanderOffset(() => true);
const selectedOffset = findLastExpanderOffset(
v => v.isChildOrThisSelected
);
const markedOffset = findLastExpanderOffset(v => v.isChildOrThisMarked);
if (!lastOffset) {
return <g />;
}
const start = model.expanderOffsetRect.center.add({ y: 10 });
return (
<g className="component-TreeNodeSvgView">
{markedOffset && (
<SvgLine
className="path childOrThisMarked"
start={start}
end={new Point(start.x, markedOffset.center.y)}
/>
)}
{selectedOffset && (
<SvgLine
className="path childOrThisSelected"
start={start}
end={new Point(start.x, selectedOffset.center.y)}
/>
)}
<SvgLine
className="path"
start={start}
end={new Point(start.x, lastOffset.center.y)}
/>
{model.children.map((c, idx) => {
const end = c.expanderOffsetRect!.center.sub({ x: 5 });
return (
<g key={idx}>
{c.isChildOrThisMarked && (
<SvgLine
className={classNames(
"path",
"childOrThisMarked"
)}
start={new Point(start.x, end.y)}
end={end}
/>
)}
<SvgLine
className={classNames(
"path",
c.isChildOrThisSelected &&
"childOrThisSelected"
)}
start={new Point(start.x, end.y)}
end={end}
/>
</g>
);
})}
{model.children.map((c, idx) => (
<TreeNodeSvgView key={idx} model={c} />
))}
</g>
);
}
}
@observer
export class TreeNodeView extends React.Component<{
model: TreeNodeViewModel;
}> {
@action.bound
private clickHandler() {
this.props.model.treeViewModel.select(this.props.model);
}
private mouseEnterOrLeaveHandler(enter: boolean) {
this.props.model.treeViewModel.hover(
enter ? this.props.model : undefined
);
}
@observable
private rootDiv: HTMLElement | null = null;
@action.bound
private setRootDiv(div: HTMLElement | null) {
this.rootDiv = div;
}
@disposeOnUnmount
private readonly _autorun = autorun(() => {
if (this.props.model.isMarked && this.rootDiv) {
this.rootDiv.scrollIntoView();
}
});
public componentDidMount() {
this.props.model.treeViewModel.updateOffsets();
}
public componentDidUpdate() {
this.props.model.treeViewModel.updateOffsets();
}
public render(): JSX.Element {
const model = this.props.model;
const collapsed = model.expanded;
return (
<div
className={classNames(
"component-TreeNodeView",
model.expanded && "collapsed",
model.isMarked && "selected",
model.isSelected && "hovered",
model.isChildOrThisSelected &&
!model.isSelected &&
"childHovered",
model.isParentOrThisSelected &&
!model.isSelected &&
"parentHovered"
)}
ref={this.setRootDiv}
onMouseOver={e => {
this.mouseEnterOrLeaveHandler(true);
e.stopPropagation();
}}
onMouseOut={e => {
this.mouseEnterOrLeaveHandler(false);
e.stopPropagation();
}}
>
<div className="part-header">
<div
ref={model.setExpanderDiv}
onClick={() => model.toggleExpanded()}
className={classNames(
"part-collapser",
model.children.length > 0 &&
"decorator-collapse-icon",
!collapsed && "expanded"
)}
/>
<span className="part-text" onClick={this.clickHandler}>
{model.id && (
<>
<span className="part-id">{model.id}</span>
<span>: </span>
</>
)}
<span className="part-name">{model.name}</span>
</span>
</div>
{model.children.length > 0 && !model.expanded && (
<div className="part-children">
{model.children.map((c, idx) => (
<TreeNodeView model={c} key={idx} />
))}
</div>
)}
</div>
);
}
}

View File

@@ -0,0 +1,29 @@
import { TreeViewModel, TreeNodeViewModel } from "./Views";
import { TreeNodeData } from "@hediet/debug-visualizer-data-extraction";
export function createTreeViewModelFromTreeNodeData(
root: TreeNodeData["root"]
): TreeViewModel {
const m = new TreeViewModel();
m.root = recurse(root, m);
return m;
function recurse(
root: TreeNodeData["root"],
viewModel: TreeViewModel
): TreeNodeViewModel {
const children: TreeNodeViewModel[] = root.children.map(c =>
recurse(c, viewModel)
);
const model = new TreeNodeViewModel(
viewModel,
root.id,
root.name,
children
);
for (const c of children) {
c.parent = model;
}
return model;
}
}

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,100 @@
.component-TreeView {
overflow: auto;
scroll-behavior: smooth;
height: 100%;
position: relative;
display: grid;
& > .part-svg,
& > .part-node {
height: 100%;
width: 100%;
grid-column: 1;
grid-row: 1;
}
}
.component-TreeNodeSvgView {
.path {
stroke: gray;
stroke-dasharray: 1;
&.childOrThisMarked {
stroke: orange;
stroke-width: 5;
stroke-dasharray: 0;
stroke-linecap: round;
}
&.childOrThisSelected {
stroke: black;
stroke-width: 2;
stroke-dasharray: 0;
}
}
}
.component-TreeNodeView {
font-family: -apple-system, BlinkMacSystemFont, Segoe WPC, Segoe UI,
HelveticaNeue-Light, Ubuntu, Droid Sans, sans-serif;
.part-header {
padding: 2px;
padding-left: 5px;
margin: 0;
display: flex;
align-items: center;
.part-collapser {
width: 10px;
height: 10px;
margin-right: 6px;
}
.part-text {
cursor: pointer;
.part-id {
color: blue;
}
}
}
.part-children {
margin-left: 0px;
margin-top: 0px;
padding: 5px;
padding-left: 15px;
}
&.selected {
& > .part-header {
font-weight: bold;
}
}
&.hovered {
& > .part-header > span {
font-weight: bold;
}
& > .part-children {
background: transparentize(gray, 0.8);
}
}
}
.component-TreeWithPathView {
display: flex;
flex-direction: column;
height: 100%;
.part-path {
overflow: hidden;
white-space: pre;
padding: 5px 10px;
border-bottom: solid gray 1px;
}
.part-tree {
overflow: auto;
flex: 1;
}
}

29
ui/src/index.tsx Normal file
View File

@@ -0,0 +1,29 @@
import * as React from "react";
import * as ReactDOM from "react-dom";
import "./style.scss";
import { Model } from "./Model";
import Components = require("./Components");
const model = new Model();
function render(target: HTMLDivElement) {
const c = require("./Components") as typeof Components;
ReactDOM.render(<c.GUI model={model} />, target);
}
const target = document.createElement("div");
target.className = "target";
document.body.appendChild(target);
render(target);
declare var module: {
hot?: { accept: (componentName: string, callback: () => void) => void };
};
declare var require: (name: string) => any;
if (module.hot) {
module.hot.accept("./Components", () => {
render(target);
});
}

197
ui/src/style.scss Normal file
View File

@@ -0,0 +1,197 @@
@import "./vscode-light.scss";
@import "~normalize.css";
@import "~@blueprintjs/core/lib/css/blueprint.css";
@import "~@blueprintjs/icons/lib/css/blueprint-icons.css";
body {
font-family: "Segoe UI", Tahoma, Geneva, Verdana, sans-serif;
margin: 0px;
padding: 0px;
border: 0px;
margin: 0;
padding: 0;
}
html,
body,
.target {
height: 100%;
}
.component-Visualizer {
width: 100%;
height: 100%;
overflow: auto;
}
.component-NoData {
width: 100%;
height: 100%;
svg {
float: left; // to prevent scrollbar.
// I don't know why it works.
width: 100%;
height: 100%;
rect {
fill: white;
}
line {
stroke: gray;
}
text {
stroke: black;
stroke-width: 0px;
}
}
}
.component-GUI {
display: flex;
flex-direction: column;
height: 100%;
& > .part-header {
padding: 6px 20px 6px 0;
background-color: var(--vscode-sideBar-background);
//border: solid 8px white;
//border-top: solid 1px var(--vscode-button-background);
// rgba(128, 128, 128, 0.15)
display: flex;
flex-direction: row;
.part-header-content {
flex: 1;
transition: flex 0.3s ease-out;
.part-header-main,
.part-header-options {
display: flex;
align-items: center;
.part-expression-input {
//color: rgb(155, 70, 176);
font-family: var(--vscode-editor-font-family);
flex: 1;
}
}
.part-header-main {
.part-icon {
margin-left: 4px;
}
}
.part-header-options {
.part-select {
min-width: 90px;
}
}
}
}
.part-visualizer {
//padding: 10px 20px;
flex: 1;
min-height: 0px;
height: 100%;
}
}
.decorator-collapse-icon {
background-image: url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16'%3E%3Cpath fill='%23646465' d='M6 4v8l4-4-4-4zm1 2.414L8.586 8 7 9.586V6.414z'/%3E%3C/svg%3E");
background-position: 50%;
background-position-x: 50%;
background-position-y: center;
background-repeat: no-repeat;
&.expanded {
background-image: url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16'%3E%3Cpath fill='%23646465' d='M11 10.07H5.344L11 4.414v5.656z'/%3E%3C/svg%3E");
}
}
.component-ExpandButton {
background-image: url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16'%3E%3Cpath fill='%23646465' d='M6 4v8l4-4-4-4zm1 2.414L8.586 8 7 9.586V6.414z'/%3E%3C/svg%3E");
background-position: 50%;
background-position-x: 50%;
background-position-y: center;
background-repeat: no-repeat;
width: 15px;
margin: -6px 6px -6px 0px;
&.expanded {
background-image: url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16'%3E%3Cpath fill='%23646465' d='M11 10.07H5.344L11 4.414v5.656z'/%3E%3C/svg%3E");
}
&:hover {
background-color: var(--vscode-titleBar-activeBackground);
}
}
.component-NamedSelect {
display: flex;
flex-direction: column;
align-items: center;
.part-Select {
min-width: 100px;
}
}
.component-Select {
.part-button {
background-color: var(--vscode-button-background);
color: white;
padding: 4px 10px;
width: 100%;
border: none;
cursor: pointer;
&:hover,
&:focus {
background-color: var(--vscode-button-hoverBackground);
}
}
.part-content {
background-color: var(--vscode-sideBar-background);
div {
color: black;
padding: 12px 16px;
text-decoration: none;
display: block;
&:hover {
background-color: #ddd;
}
}
}
.bp3-popover-wrapper {
width: 100%;
}
.bp3-popover-target {
width: 100%;
}
}
.svgViewer {
width: 100%;
height: 100%;
padding: 0;
margin: 0;
}

307
ui/src/vscode-light.scss Normal file
View File

@@ -0,0 +1,307 @@
:root {
--vscode-activityBar-background: #2c2c2c;
--vscode-activityBar-dropBackground: rgba(255, 255, 255, 0.12);
--vscode-activityBar-foreground: #ffffff;
--vscode-activityBar-inactiveForeground: rgba(255, 255, 255, 0.6);
--vscode-activityBarBadge-background: #007acc;
--vscode-activityBarBadge-foreground: #ffffff;
--vscode-badge-background: #c4c4c4;
--vscode-badge-foreground: #333333;
--vscode-breadcrumb-activeSelectionForeground: #4e4e4e;
--vscode-breadcrumb-background: #ffffff;
--vscode-breadcrumb-focusForeground: #4e4e4e;
--vscode-breadcrumb-foreground: rgba(97, 97, 97, 0.8);
--vscode-breadcrumbPicker-background: #f3f3f3;
--vscode-button-background: #007acc;
--vscode-button-foreground: #ffffff;
--vscode-button-hoverBackground: #0062a3;
--vscode-debugExceptionWidget-background: #f1dfde;
--vscode-debugExceptionWidget-border: #a31515;
--vscode-debugToolBar-background: #f3f3f3;
--vscode-descriptionForeground: #717171;
--vscode-diffEditor-insertedTextBackground: rgba(155, 185, 85, 0.2);
--vscode-diffEditor-removedTextBackground: rgba(255, 0, 0, 0.2);
--vscode-dropdown-background: #ffffff;
--vscode-dropdown-border: #cecece;
--vscode-editor-background: #ffffff;
--vscode-editor-findMatchBackground: #a8ac94;
--vscode-editor-findMatchHighlightBackground: rgba(234, 92, 0, 0.33);
--vscode-editor-findRangeHighlightBackground: rgba(180, 180, 180, 0.3);
--vscode-editor-focusedStackFrameHighlightBackground: rgba(
206,
231,
206,
0.45
);
--vscode-editor-font-family: Consolas, "Courier New", monospace;
--vscode-editor-font-size: 14;
--vscode-editor-font-weight: normal;
--vscode-editor-foreground: #000000;
--vscode-editor-hoverHighlightBackground: rgba(173, 214, 255, 0.15);
--vscode-editor-inactiveSelectionBackground: #e5ebf1;
--vscode-editor-lineHighlightBorder: #eeeeee;
--vscode-editor-rangeHighlightBackground: rgba(253, 255, 0, 0.2);
--vscode-editor-selectionBackground: #add6ff;
--vscode-editor-selectionHighlightBackground: rgba(173, 214, 255, 0.5);
--vscode-editor-snippetFinalTabstopHighlightBorder: rgba(10, 50, 100, 0.5);
--vscode-editor-snippetTabstopHighlightBackground: rgba(10, 50, 100, 0.2);
--vscode-editor-stackFrameHighlightBackground: rgba(255, 255, 102, 0.45);
--vscode-editor-wordHighlightBackground: rgba(87, 87, 87, 0.25);
--vscode-editor-wordHighlightStrongBackground: rgba(14, 99, 156, 0.25);
--vscode-editorActiveLineNumber-foreground: #0b216f;
--vscode-editorBracketMatch-background: rgba(0, 100, 0, 0.1);
--vscode-editorBracketMatch-border: #b9b9b9;
--vscode-editorCodeLens-foreground: #999999;
--vscode-editorCursor-foreground: #000000;
--vscode-editorError-foreground: #e51400;
--vscode-editorGroup-border: #e7e7e7;
--vscode-editorGroup-dropBackground: rgba(38, 119, 203, 0.18);
--vscode-editorGroupHeader-noTabsBackground: #ffffff;
--vscode-editorGroupHeader-tabsBackground: #f3f3f3;
--vscode-editorGutter-addedBackground: #81b88b;
--vscode-editorGutter-background: #ffffff;
--vscode-editorGutter-commentRangeForeground: #c5c5c5;
--vscode-editorGutter-deletedBackground: #ca4b51;
--vscode-editorGutter-modifiedBackground: #66afe0;
--vscode-editorHint-foreground: #6c6c6c;
--vscode-editorHoverWidget-background: #f3f3f3;
--vscode-editorHoverWidget-border: #c8c8c8;
--vscode-editorHoverWidget-statusBarBackground: #e7e7e7;
--vscode-editorIndentGuide-activeBackground: #939393;
--vscode-editorIndentGuide-background: #d3d3d3;
--vscode-editorInfo-foreground: #008000;
--vscode-editorLineNumber-activeForeground: #0b216f;
--vscode-editorLineNumber-foreground: #237893;
--vscode-editorLink-activeForeground: #0000ff;
--vscode-editorMarkerNavigation-background: #ffffff;
--vscode-editorMarkerNavigationError-background: #e51400;
--vscode-editorMarkerNavigationInfo-background: #008000;
--vscode-editorMarkerNavigationWarning-background: #e9a700;
--vscode-editorOverviewRuler-addedForeground: rgba(0, 122, 204, 0.6);
--vscode-editorOverviewRuler-border: rgba(127, 127, 127, 0.3);
--vscode-editorOverviewRuler-bracketMatchForeground: #a0a0a0;
--vscode-editorOverviewRuler-commonContentForeground: rgba(96, 96, 96, 0.4);
--vscode-editorOverviewRuler-currentContentForeground: rgba(
64,
200,
174,
0.5
);
--vscode-editorOverviewRuler-deletedForeground: rgba(0, 122, 204, 0.6);
--vscode-editorOverviewRuler-errorForeground: rgba(255, 18, 18, 0.7);
--vscode-editorOverviewRuler-findMatchForeground: rgba(209, 134, 22, 0.49);
--vscode-editorOverviewRuler-incomingContentForeground: rgba(
64,
166,
255,
0.5
);
--vscode-editorOverviewRuler-infoForeground: #008000;
--vscode-editorOverviewRuler-modifiedForeground: rgba(0, 122, 204, 0.6);
--vscode-editorOverviewRuler-rangeHighlightForeground: rgba(
0,
122,
204,
0.6
);
--vscode-editorOverviewRuler-selectionHighlightForeground: rgba(
160,
160,
160,
0.8
);
--vscode-editorOverviewRuler-warningForeground: #e9a700;
--vscode-editorOverviewRuler-wordHighlightForeground: rgba(
160,
160,
160,
0.8
);
--vscode-editorOverviewRuler-wordHighlightStrongForeground: rgba(
192,
160,
192,
0.8
);
--vscode-editorPane-background: #ffffff;
--vscode-editorRuler-foreground: #d3d3d3;
--vscode-editorSuggestWidget-background: #f3f3f3;
--vscode-editorSuggestWidget-border: #c8c8c8;
--vscode-editorSuggestWidget-foreground: #000000;
--vscode-editorSuggestWidget-highlightForeground: #0066bf;
--vscode-editorSuggestWidget-selectedBackground: #d6ebff;
--vscode-editorUnnecessaryCode-opacity: rgba(0, 0, 0, 0.47);
--vscode-editorWarning-foreground: #e9a700;
--vscode-editorWhitespace-foreground: rgba(51, 51, 51, 0.2);
--vscode-editorWidget-background: #f3f3f3;
--vscode-editorWidget-border: #c8c8c8;
--vscode-errorForeground: #a1260d;
--vscode-extensionBadge-remoteBackground: #007acc;
--vscode-extensionBadge-remoteForeground: #ffffff;
--vscode-extensionButton-prominentBackground: #327e36;
--vscode-extensionButton-prominentForeground: #ffffff;
--vscode-extensionButton-prominentHoverBackground: #28632b;
--vscode-focusBorder: rgba(0, 122, 204, 0.4);
--vscode-font-family: -apple-system, BlinkMacSystemFont, "Segoe WPC",
"Segoe UI", "Ubuntu", "Droid Sans", ans-serif;
--vscode-font-size: 13px;
--vscode-font-weight: normal;
--vscode-foreground: #616161;
--vscode-gitDecoration-addedResourceForeground: #587c0c;
--vscode-gitDecoration-conflictingResourceForeground: #6c6cc4;
--vscode-gitDecoration-deletedResourceForeground: #ad0707;
--vscode-gitDecoration-ignoredResourceForeground: #8e8e90;
--vscode-gitDecoration-modifiedResourceForeground: #895503;
--vscode-gitDecoration-submoduleResourceForeground: #1258a7;
--vscode-gitDecoration-untrackedResourceForeground: #007100;
--vscode-gitlens-gutterBackgroundColor: rgba(0, 0, 0, 0.05);
--vscode-gitlens-gutterForegroundColor: #747474;
--vscode-gitlens-gutterUncommittedForegroundColor: rgba(0, 188, 242, 0.6);
--vscode-gitlens-lineHighlightBackgroundColor: rgba(0, 188, 242, 0.2);
--vscode-gitlens-lineHighlightOverviewRulerColor: rgba(0, 188, 242, 0.6);
--vscode-gitlens-trailingLineBackgroundColor: rgba(0, 0, 0, 0);
--vscode-gitlens-trailingLineForegroundColor: rgba(153, 153, 153, 0.35);
--vscode-input-background: #ffffff;
--vscode-input-foreground: #616161;
--vscode-input-placeholderForeground: #767676;
--vscode-inputOption-activeBorder: #007acc;
--vscode-inputValidation-errorBackground: #f2dede;
--vscode-inputValidation-errorBorder: #be1100;
--vscode-inputValidation-infoBackground: #d6ecf2;
--vscode-inputValidation-infoBorder: #007acc;
--vscode-inputValidation-warningBackground: #f6f5d2;
--vscode-inputValidation-warningBorder: #b89500;
--vscode-list-activeSelectionBackground: #0074e8;
--vscode-list-activeSelectionForeground: #ffffff;
--vscode-list-dropBackground: #d6ebff;
--vscode-list-errorForeground: #b01011;
--vscode-list-focusBackground: #d6ebff;
--vscode-list-highlightForeground: #0066bf;
--vscode-list-hoverBackground: #e8e8e8;
--vscode-list-inactiveSelectionBackground: #e4e6f1;
--vscode-list-invalidItemForeground: #b89500;
--vscode-list-warningForeground: #855f00;
--vscode-listFilterWidget-background: #efc1ad;
--vscode-listFilterWidget-noMatchesOutline: #be1100;
--vscode-listFilterWidget-outline: rgba(0, 0, 0, 0);
--vscode-menu-background: #ffffff;
--vscode-menu-foreground: #616161;
--vscode-menu-selectionBackground: #0074e8;
--vscode-menu-selectionForeground: #ffffff;
--vscode-menu-separatorBackground: #888888;
--vscode-menubar-selectionBackground: rgba(0, 0, 0, 0.1);
--vscode-menubar-selectionForeground: #333333;
--vscode-merge-commonContentBackground: rgba(96, 96, 96, 0.16);
--vscode-merge-commonHeaderBackground: rgba(96, 96, 96, 0.4);
--vscode-merge-currentContentBackground: rgba(64, 200, 174, 0.2);
--vscode-merge-currentHeaderBackground: rgba(64, 200, 174, 0.5);
--vscode-merge-incomingContentBackground: rgba(64, 166, 255, 0.2);
--vscode-merge-incomingHeaderBackground: rgba(64, 166, 255, 0.5);
--vscode-notificationCenterHeader-background: #e7e7e7;
--vscode-notificationLink-foreground: #006ab1;
--vscode-notifications-background: #f3f3f3;
--vscode-notifications-border: #e7e7e7;
--vscode-panel-background: #ffffff;
--vscode-panel-border: rgba(128, 128, 128, 0.35);
--vscode-panel-dropBackground: rgba(38, 119, 203, 0.18);
--vscode-panelInput-border: #dddddd;
--vscode-panelTitle-activeBorder: rgba(128, 128, 128, 0.35);
--vscode-panelTitle-activeForeground: #424242;
--vscode-panelTitle-inactiveForeground: rgba(66, 66, 66, 0.75);
--vscode-peekView-border: #007acc;
--vscode-peekViewEditor-background: #f2f8fc;
--vscode-peekViewEditor-matchHighlightBackground: rgba(245, 216, 2, 0.87);
--vscode-peekViewEditorGutter-background: #f2f8fc;
--vscode-peekViewResult-background: #f3f3f3;
--vscode-peekViewResult-fileForeground: #1e1e1e;
--vscode-peekViewResult-lineForeground: #646465;
--vscode-peekViewResult-matchHighlightBackground: rgba(234, 92, 0, 0.3);
--vscode-peekViewResult-selectionBackground: rgba(51, 153, 255, 0.2);
--vscode-peekViewResult-selectionForeground: #6c6c6c;
--vscode-peekViewTitle-background: #ffffff;
--vscode-peekViewTitleDescription-foreground: rgba(108, 108, 108, 0.7);
--vscode-peekViewTitleLabel-foreground: #333333;
--vscode-pickerGroup-border: #cccedb;
--vscode-pickerGroup-foreground: #0066bf;
--vscode-progressBar-background: #0e70c0;
--vscode-quickInput-background: #f3f3f3;
--vscode-scrollbar-shadow: #dddddd;
--vscode-scrollbarSlider-activeBackground: rgba(0, 0, 0, 0.6);
--vscode-scrollbarSlider-background: rgba(100, 100, 100, 0.4);
--vscode-scrollbarSlider-hoverBackground: rgba(100, 100, 100, 0.7);
--vscode-settings-checkboxBackground: #ffffff;
--vscode-settings-checkboxBorder: #cecece;
--vscode-settings-dropdownBackground: #ffffff;
--vscode-settings-dropdownBorder: #cecece;
--vscode-settings-dropdownListBorder: #c8c8c8;
--vscode-settings-headerForeground: #444444;
--vscode-settings-modifiedItemIndicator: #66afe0;
--vscode-settings-numberInputBackground: #ffffff;
--vscode-settings-numberInputBorder: #cecece;
--vscode-settings-numberInputForeground: #616161;
--vscode-settings-textInputBackground: #ffffff;
--vscode-settings-textInputBorder: #cecece;
--vscode-settings-textInputForeground: #616161;
--vscode-sideBar-background: #f3f3f3;
--vscode-sideBar-dropBackground: rgba(0, 0, 0, 0.1);
--vscode-sideBarSectionHeader-background: rgba(128, 128, 128, 0.2);
--vscode-sideBarTitle-foreground: #6f6f6f;
--vscode-statusBar-background: #007acc;
--vscode-statusBar-debuggingBackground: #cc6633;
--vscode-statusBar-debuggingForeground: #ffffff;
--vscode-statusBar-foreground: #ffffff;
--vscode-statusBar-noFolderBackground: #68217a;
--vscode-statusBar-noFolderForeground: #ffffff;
--vscode-statusBarItem-activeBackground: rgba(255, 255, 255, 0.18);
--vscode-statusBarItem-hoverBackground: rgba(255, 255, 255, 0.12);
--vscode-statusBarItem-prominentBackground: rgba(0, 0, 0, 0.5);
--vscode-statusBarItem-prominentForeground: #ffffff;
--vscode-statusBarItem-prominentHoverBackground: rgba(0, 0, 0, 0.3);
--vscode-statusBarItem-remoteBackground: #16825d;
--vscode-statusBarItem-remoteForeground: #ffffff;
--vscode-tab-activeBackground: #ffffff;
--vscode-tab-activeForeground: #333333;
--vscode-tab-activeModifiedBorder: #33aaee;
--vscode-tab-border: #f3f3f3;
--vscode-tab-inactiveBackground: #ececec;
--vscode-tab-inactiveForeground: rgba(51, 51, 51, 0.7);
--vscode-tab-inactiveModifiedBorder: rgba(51, 170, 238, 0.5);
--vscode-tab-unfocusedActiveBackground: #ffffff;
--vscode-tab-unfocusedActiveForeground: rgba(51, 51, 51, 0.7);
--vscode-tab-unfocusedActiveModifiedBorder: rgba(51, 170, 238, 0.7);
--vscode-tab-unfocusedInactiveForeground: rgba(51, 51, 51, 0.35);
--vscode-tab-unfocusedInactiveModifiedBorder: rgba(51, 170, 238, 0.25);
--vscode-terminal-ansiBlack: #000000;
--vscode-terminal-ansiBlue: #0451a5;
--vscode-terminal-ansiBrightBlack: #666666;
--vscode-terminal-ansiBrightBlue: #0451a5;
--vscode-terminal-ansiBrightCyan: #0598bc;
--vscode-terminal-ansiBrightGreen: #14ce14;
--vscode-terminal-ansiBrightMagenta: #bc05bc;
--vscode-terminal-ansiBrightRed: #cd3131;
--vscode-terminal-ansiBrightWhite: #a5a5a5;
--vscode-terminal-ansiBrightYellow: #b5ba00;
--vscode-terminal-ansiCyan: #0598bc;
--vscode-terminal-ansiGreen: #00bc00;
--vscode-terminal-ansiMagenta: #bc05bc;
--vscode-terminal-ansiRed: #cd3131;
--vscode-terminal-ansiWhite: #555555;
--vscode-terminal-ansiYellow: #949800;
--vscode-terminal-background: #ffffff;
--vscode-terminal-border: rgba(128, 128, 128, 0.35);
--vscode-terminal-foreground: #333333;
--vscode-terminal-selectionBackground: rgba(0, 0, 0, 0.25);
--vscode-textBlockQuote-background: rgba(127, 127, 127, 0.1);
--vscode-textBlockQuote-border: rgba(0, 122, 204, 0.5);
--vscode-textCodeBlock-background: rgba(220, 220, 220, 0.4);
--vscode-textLink-activeForeground: #006ab1;
--vscode-textLink-foreground: #006ab1;
--vscode-textPreformat-foreground: #a31515;
--vscode-textSeparator-foreground: rgba(0, 0, 0, 0.18);
--vscode-titleBar-activeBackground: #dddddd;
--vscode-titleBar-activeForeground: #333333;
--vscode-titleBar-inactiveBackground: rgba(221, 221, 221, 0.6);
--vscode-titleBar-inactiveForeground: rgba(51, 51, 51, 0.6);
--vscode-tree-indentGuidesStroke: #a9a9a9;
--vscode-widget-shadow: #a8a8a8;
}

View File

@@ -1,16 +1,16 @@
{
"compilerOptions": {
"target": "esnext",
"module": "commonjs",
"strict": true,
"outDir": "dist",
"skipLibCheck": true,
"rootDir": "./src",
"resolveJsonModule": true,
"newLine": "LF",
"sourceMap": true,
"jsx": "react",
"experimentalDecorators": true
},
"include": ["src/**/*"]
}
{
"compilerOptions": {
"target": "esnext",
"module": "commonjs",
"strict": true,
"outDir": "dist",
"skipLibCheck": true,
"rootDir": "./src",
"resolveJsonModule": true,
"newLine": "LF",
"sourceMap": true,
"jsx": "react",
"experimentalDecorators": true
},
"include": ["src/**/*"]
}

2
ui/webpack.config.js Normal file
View File

@@ -0,0 +1,2 @@
require("ts-node").register();
module.exports = require("./webpack.config.ts");

40
ui/webpack.config.ts Normal file
View File

@@ -0,0 +1,40 @@
import * as webpack from "webpack";
import path = require("path");
import HtmlWebpackPlugin = require("html-webpack-plugin");
import MonacoWebpackPlugin = require("monaco-editor-webpack-plugin");
import ForkTsCheckerWebpackPlugin = require("fork-ts-checker-webpack-plugin");
const r = (file: string) => path.resolve(__dirname, file);
module.exports = {
entry: [r("src/index.tsx")],
output: {
path: r("dist"),
filename: "[name]-[hash].js",
chunkFilename: "[name]-[hash].js",
},
resolve: {
extensions: [".webpack.js", ".web.js", ".ts", ".tsx", ".js"],
},
devtool: "source-map",
module: {
rules: [
{ test: /\.css$/, loader: "style-loader!css-loader" },
{ test: /\.scss$/, loader: "style-loader!css-loader!sass-loader" },
{
test: /\.(jpe?g|png|gif|eot|ttf|svg|woff|woff2|md)$/i,
loader: "file-loader",
},
{
test: /\.tsx?$/,
loader: "ts-loader",
options: { transpileOnly: true },
},
],
},
plugins: [
new HtmlWebpackPlugin(),
new MonacoWebpackPlugin(),
new ForkTsCheckerWebpackPlugin(),
],
} as webpack.Configuration;

File diff suppressed because it is too large Load Diff

8078
webview/package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,47 +0,0 @@
{
"name": "template",
"version": "0.1.0",
"scripts": {
"dev": "webpack-dev-server -d --hot --inline",
"build": "webpack"
},
"dependencies": {
"@hediet/typed-json-rpc": "^0.1.0",
"@hediet/typed-json-rpc-websocket": "^0.1.0",
"classnames": "^2.2.6",
"mobx": "^5.7.0",
"mobx-react": "^5.4.2",
"react": "^16.6.3",
"react-dom": "^16.6.3",
"react-measure": "2.1.3",
"react-svg-pan-zoom": "^2.18.0",
"svg-inline-react": "^3.1.0",
"viz.js": "^2.1.2"
},
"devDependencies": {
"@types/classnames": "^2.2.6",
"@types/react": "^16.7.13",
"@types/react-dom": "^16.0.11",
"@types/react-measure": "^2.0.4",
"@types/react-svg-pan-zoom": "^2.5.6",
"clean-webpack-plugin": "^1.0.0",
"copy-webpack-plugin": "^4.6.0",
"css-loader": "^2.0.0",
"file-loader": "^2.0.0",
"git-revision-webpack-plugin": "^3.0.3",
"html-webpack-plugin": "^3.2.0",
"json-loader": "^0.5.7",
"mini-css-extract-plugin": "^0.5.0",
"node-sass": "^4.11.0",
"raw-loader": "^1.0.0",
"sass-loader": "^7.1.0",
"source-map-loader": "^0.2.4",
"style-loader": "^0.23.1",
"ts-loader": "^5.3.1",
"typescript": "^3.2.2",
"webpack": "^4.27.1",
"webpack-cli": "^3.1.2",
"webpack-dev-server": "^3.1.10",
"webpack-split-by-path": "^2.0.0"
}
}

View File

@@ -1,257 +0,0 @@
import { observer } from "mobx-react";
import * as classNames from "classnames";
import * as React from "react";
import * as ReactDOM from "react-dom";
import { ReactSVGPanZoom, Tool } from "react-svg-pan-zoom";
import { computed, observable, runInAction, action } from "mobx";
import Measure from "react-measure";
import { Model, ExpressionInputModel } from "./Model";
import { GraphData, DotGraphViewer, DotViewer } from "./DotGraphViewer";
const ENTER_KEYCODE = 13;
const ESCAPE = 27;
@observer
class ExpressionInput extends React.Component<{
rootModel: Model;
model: ExpressionInputModel;
}> {
private ref: HTMLInputElement | null;
private readonly inputTextChanged = (
event: React.ChangeEvent<HTMLInputElement>
) => {
this.props.model.setEditedExpression(event.target.value);
};
private readonly setRef = (ref: HTMLInputElement | null) => {
if (ref) {
ref.select();
}
};
private readonly onInputKeyDown = (e: React.KeyboardEvent) => {
if (e.keyCode === ENTER_KEYCODE) {
e.preventDefault();
this.props.model.finishEditing();
} else if (e.keyCode === ESCAPE) {
e.preventDefault();
this.props.model.abortEditing();
}
};
render() {
const model = this.props.model;
if (model.editedExpression !== null) {
return (
<input
ref={this.setRef}
className="expression-input"
value={model.editedExpression}
onChange={this.inputTextChanged}
onBlur={model.finishEditing}
onKeyDown={this.onInputKeyDown}
/>
);
} else {
return (
<div
className="expression"
onDoubleClick={model.activateEditing}
>
{this.props.rootModel.actualExpression}
</div>
);
}
}
}
import {
contract,
types as t,
requestContract,
notificationContract,
ConsoleRpcLogger
} from "@hediet/typed-json-rpc";
import { TypedWebSocketClientChannel } from "@hediet/typed-json-rpc-websocket";
const editorContract = contract({
server: {
highlightLine: requestContract({ params: { line: t.Integer } }),
highlight: requestContract({
params: {
startLine: t.Integer,
startCol: t.Integer,
endLine: t.Integer,
endCol: t.Integer
}
}),
decorate: requestContract({
params: {
decos: t.array(t.type({ line: t.Integer, text: t.string }))
}
})
},
client: {}
});
@observer
export class HighlightLine extends React.Component<{ content: string }> {
@observable localContent: string;
constructor(props: HighlightLine["props"]) {
super(props);
this.localContent = props.content;
}
/*
componentDidUpdate(props: this["props"]) {
this.localContent = this.props.content;
}*/
async send() {
const content = this.localContent.substr("highlight-line:".length);
const lines = content.split("\n");
const parts = lines[0].split(",");
const startLine = parseInt(parts[0]) - 1;
const startCol = parseInt(parts[1]) - 1;
const endLine = parseInt(parts[2]) - 1;
const endCol = parseInt(parts[3]);
let decos = lines.splice(1).map(l => {
const p = l.split(":");
return {
line: parseInt(p[0]) - 1,
text: " " + p.splice(1).join(":")
};
});
decos = decos.filter(d => !d.text.startsWith(" @"));
console.log("send", startLine, decos);
try {
let channel = await TypedWebSocketClientChannel.connectTo(
{ address: "ws://localhost:56024" },
new ConsoleRpcLogger()
);
const server = editorContract.getServerInterface(channel, {});
channel.startListen();
await server.highlight({ startLine, startCol, endLine, endCol });
await server.decorate({ decos });
} catch (exception) {
console.log("could not contact vscode: ", exception);
}
}
render() {
this.send();
return (
<textarea
value={this.localContent}
onChange={e => (this.localContent = "" + e.target.value)}
/>
);
}
}
@observer
export class GUI extends React.Component<{ model: Model }, {}> {
render() {
const m = this.props.model;
return (
<div
style={{
paddingTop: "0.5em",
display: "flex",
flexDirection: "column",
height: "100%",
boxSizing: "border-box"
}}
tabIndex={0}
onKeyDown={e => {
if (e.keyCode === 39) {
m.historyIndex++;
} else if ((e.keyCode = 37)) {
m.historyIndex--;
}
}}
>
<div style={{}}>
<ExpressionInput
rootModel={this.props.model}
model={this.props.model.expressionInput}
/>
</div>
<hr style={{ width: "100%" }} />
<div style={{ flex: "1", minHeight: "0px" }}>
{/* <input type="checkbox" onChange={e => this.props.model.expressionInput.isEditing = !!e.target.checked} /> */}
{m.value ? (
this.renderValue(m.value.data)
) : (
<div>No Data</div>
)}
</div>
</div>
);
}
private cachedValue: string;
private cachedParts: string[];
renderValue(value: string) {
try {
if (value.match(/"(.*)"/)) {
value = value.substr(1, value.length - 2);
}
const data = JSON.parse(value);
//console.log(value, (data as any).kind);
if ((data as any).kind === "GraphData") {
const value2 = data as GraphData;
return <DotGraphViewer data={value2} />;
}
} catch (e) {
console.error(value, e);
}
return value;
try {
let parsed = JSON.parse(`${value}`);
if (parsed === "None") {
return <textarea>None</textarea>;
}
if (parsed.startsWith("highlight-line:")) {
return <HighlightLine content={parsed} />;
}
} catch (e) {
console.error(e);
}
try {
return <DotViewer dotCode={JSON.parse(`${value}`)} />;
} catch (e) {
console.error(value, e);
}
/*return <DotViewer dotCode={JSON.parse(`${value}`)} />;*/
if (value !== this.cachedValue) {
this.cachedParts = value
.split("---------------------------")
.filter(e => !!e);
this.cachedValue = value;
console.log(`updated ${this.cachedParts.length} parts`);
}
//console.log(value.substr(0, 10000));
//console.log(`${this.cachedParts[0]}`);
//console.log(`"${this.cachedParts[0]}"`);
const idx = Math.max(
1,
Math.min(this.cachedParts.length - 2, this.props.model.historyIndex)
);
console.log(idx);
return <DotViewer dotCode={JSON.parse(`"${this.cachedParts[idx]}"`)} />;
return <div>{`${value.length}`}</div>;
}
}

View File

@@ -1,113 +0,0 @@
import { observer } from "mobx-react";
import * as classNames from "classnames";
import * as React from "react";
import * as ReactDOM from "react-dom";
import { ReactSVGPanZoom, Tool } from 'react-svg-pan-zoom';
import { computed, observable, autorun, runInAction } from "mobx";
import Measure, { ContentRect } from 'react-measure';
import Viz from 'viz.js';
import { Module, render } from 'viz.js/full.render.js';
export interface GraphData {
kind: "GraphData";
nodes: { id: string; label: string; }[];
edges: { from: string; to: string; label: string; }[];
}
let viz: any = new Viz({Module: () => Module({TOTAL_MEMORY: 1<<30}), render});
@observer
export class DotGraphViewer extends React.Component<{ data: GraphData }> {
render() {
const { nodes, edges } = this.props.data;
const dotContent = `
digraph MyGraph {
${nodes.map(n => `${n.id} [ label = ${JSON.stringify(n.label)} ];`).join("\n ")}
${edges.map(e => `${e.from} -> ${e.to} [ label = ${JSON.stringify(e.label)} ];`).join("\n")}
}
`;
return (
<DotViewer dotCode={dotContent} />
);
}
}
@observer
export class DotViewer extends React.Component<{ dotCode: string }> {
@observable private svg: string|null = null;
constructor(props) {
super(props);
autorun(async () => {
const svg = await viz.renderString(this.props.dotCode);
runInAction("Update svg", () => this.svg = svg);
});
}
render() {
if (!this.svg) {
return (<div>Loading...</div>);
}
return (
<SvgViewer svgContent={this.svg} />
);
}
}
function widthOrDefault(r: ContentRect): number {
if (r.bounds && r.bounds.width) {
return r.bounds.width;
}
return 1;
}
function heightOrDefault(r: ContentRect): number {
if (r.bounds && r.bounds.height) {
return r.bounds.height;
}
return 1;
}
export class SvgViewer extends React.Component<{ svgContent: string }, { tool: Tool }> {
constructor(props) {
super(props);
this.state = { tool: "pan" };
}
private readonly setTool = (tool: Tool) => {
this.setState({ tool });
};
render() {
let { svgContent } = this.props;
let width: number = 0;
let height: number = 0;
svgContent = svgContent.replace(/viewBox="[0-9\.]+ [0-9\.]+ ([0-9\.]+) ([0-9\.]+)"/, (r, w, h) => {
width = parseFloat(w);
height = parseFloat(h);
return "";
});
//console.log(svgContent);
return (
<Measure bounds>
{
({ measureRef, contentRect }) =>
(
<div ref={measureRef} className="svgViewer">
<ReactSVGPanZoom
width={widthOrDefault(contentRect)}
height={heightOrDefault(contentRect)}
tool={this.state.tool}
onChangeTool={this.setTool}
>
<svg width={width} height={height}>
<g dangerouslySetInnerHTML={{ __html: svgContent }} />
</svg>
</ReactSVGPanZoom>
</div>
)
}
</Measure>
);
}
}

View File

@@ -1,89 +0,0 @@
import { observable, computed, action, autorun, intercept, runInAction, values } from "mobx";
import Viz from 'viz.js';
import { Module, render } from 'viz.js/full.render.js';
let viz: any = new Viz({ Module, render });
export class ExpressionInputModel {
@observable public editedExpression: string|null = "";
@observable public expression: string = "";
@action
public readonly activateEditing = () => {
this.editedExpression = this.expression;
}
@action
public readonly setEditedExpression = (text: string) => {
this.editedExpression = text;
}
@action
public readonly finishEditing = () => {
if (this.editedExpression) {
this.expression = this.editedExpression;
this.editedExpression = null;
}
}
@action
public readonly abortEditing = () => {
this.editedExpression = null;
}
}
interface Value {
data: string;
}
type Data = SetValueCommand | SetExpressionCommand | { command: undefined; };
interface SetValueCommand {
command: "setValue";
value: Value;
}
interface SetExpressionCommand {
command: "setExpression";
expression: string;
}
export class Model {
public readonly expressionInput = new ExpressionInputModel();
@observable public value: Value | undefined = undefined;
@observable public historyIndex: number = 0;
@computed public get actualExpression(): string {
return this.expressionInput.expression;
//return this.expressionInput.expression.replace(/\$i/g, `${this.historyIndex}`);
}
constructor() {
window.addEventListener("message", event => {
const data = event.data as Data;
if (data.command === "setValue") {
runInAction("setValue", () => {
this.value = data.value;
});
}
if (data.command === "setExpression") {
runInAction("setExpression", () => {
this.expressionInput.editedExpression = data.expression ? null : "";
this.expressionInput.expression = data.expression;
});
}
});
window.parent.postMessage({ command: "initialized" }, "*");
autorun(() => {
setExpression(this.actualExpression);
});
}
}
function setExpression(expression: string) {
window.parent.postMessage({ command: "setExpression", expression }, "*");
}

View File

@@ -1,27 +0,0 @@
import * as React from "react";
import * as ReactDOM from "react-dom";
import "./style.scss";
import App = require("./App");
import { Model } from "./Model";
const model = new Model();
function render(target: HTMLDivElement, app: typeof App) {
ReactDOM.render(<app.GUI model={model} />, target);
}
const target = document.createElement("div");
target.className = "root";
render(target, App);
document.body.appendChild(target);
declare var module: { hot?: { accept: (componentName: string, callback: () => void) => void } };
declare var require: (name: string) => any;
if (module.hot) {
module.hot.accept("./App", () => {
console.log("render");
render(target, require("./App"));
});
}

View File

@@ -1,105 +0,0 @@
@import './vscode-defaults.scss';
body {
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
height: 100vh;
position: relative;
margin: 0px;
padding: 0px;
border: 0px;
background-color: var(--vscode-editor-background);
color: var(--vscode-editor-foreground);
font-family: var(--vscode-editor-font-family);
font-weight: var(--vscode-editor-font-weight);
font-size: var(--vscode-editor-font-size);
margin: 0;
padding: 0 20px;
}
html, .root, .gui {
height: 100vh;
position: relative;
margin: 0px;
padding: 0px;
border: 0px;
}
.gui {
display: flex;
flex-direction: column;
}
img {
max-width: 100%;
max-height: 100%;
}
a {
color: var(--vscode-textLink-foreground);
}
a:hover {
color: var(--vscode-textLink-activeForeground);
}
a:focus,
input:focus,
select:focus,
textarea:focus {
outline: 1px solid -webkit-focus-ring-color;
outline-offset: -1px;
}
code {
color: var(--vscode-textPreformat-foreground);
}
blockquote {
background: var(--vscode-textBlockQuote-background);
border-color: var(--vscode-textBlockQuote-border);
}
::-webkit-scrollbar {
width: 10px;
height: 10px;
}
::-webkit-scrollbar-thumb {
background-color: var(--vscode-scrollbarSlider-background);
}
::-webkit-scrollbar-thumb:hover {
background-color: var(--vscode-scrollbarSlider-hoverBackground);
}
::-webkit-scrollbar-thumb:active {
background-color: var(--vscode-scrollbarSlider-activeBackground);
}
.svgViewer {
height: 100%;
}
.expression {
color: #c586c0;
white-space: pre;
font-size: 13px;
font-family: Monaco,Menlo,Consolas,Droid Sans Mono,Inconsolata,Courier New,monospace,Droid Sans Fallback;
margin-top: 3px;
margin-bottom: 6px;
}
.expression-input {
width: 100%;
box-sizing : border-box;
margin-top: 1px;
margin-bottom: 0px;
font-size: 13px;
font-family: Monaco,Menlo,Consolas,Droid Sans Mono,Inconsolata,Courier New,monospace,Droid Sans Fallback;
background-color: rgb(48, 60, 65);
color: rgb(238, 255, 255);
border: 1px black;
}
hr {
border: 0.5px solid white;
}

View File

@@ -1,20 +0,0 @@
{
"compilerOptions": {
"outDir": "./dist/development",
"lib": ["es6", "dom"],
"module": "commonjs",
"target": "es5",
"jsx": "react",
"allowJs": true,
"strictNullChecks": true,
"sourceMap": true,
"experimentalDecorators": true
},
"include": [
"src/**/*"
],
"exclude": [
"dist",
"node_modules"
]
}

View File

@@ -1,33 +0,0 @@
var webpack = require('webpack');
var path = require('path');
var HtmlWebpackPlugin = require('html-webpack-plugin');
var r = file => path.resolve(__dirname, file[0]);
module.exports = {
entry: [ r`src/index.tsx` ],
output: {
path: r`dist`,
filename: "[name]-[hash].js",
chunkFilename: "[name]-[hash].js"
},
resolve: {
extensions: ['.webpack.js', '.web.js', '.ts', '.tsx', '.js']
},
devtool: 'source-map',
module: {
rules: [
{ test: /\.css$/, loader: "style-loader!css-loader" },
{ test: /\.scss$/, loader: "style-loader!css-loader!sass-loader" },
{ test: /\.(jpe?g|png|gif)$/i, loader: "file-loader" },
{
test: /\.tsx?$/, loader: 'ts-loader',
options: { transpileOnly: true }
}
]
},
plugins: [
// generates an index.html
new HtmlWebpackPlugin()
]
};

6146
yarn.lock Normal file

File diff suppressed because it is too large Load Diff