33 Commits

Author SHA1 Message Date
Henning Dieterichs
453e0ae5a8 Updates changelog. 2020-08-22 16:07:13 +02:00
Henning Dieterichs
08d97c2a82 Fixes logo reference. 2020-08-22 16:07:04 +02:00
Henning Dieterichs
e487dead95 Downgrades required vscode version. 2020-08-22 16:06:49 +02:00
Henning Dieterichs
8ce8e7dad8 Fixes typescript ast data extractor. 2020-08-22 16:06:36 +02:00
Henning Dieterichs
41747c5dd0 Fixes types. 2020-08-22 15:46:44 +02:00
Henning Dieterichs
1dd0349483 Adds types for bundled visualizations & improves data extractor types. 2020-08-22 15:40:24 +02:00
Henning Dieterichs
62415f1775 Fixes contract import. 2020-08-16 22:02:16 +02:00
Henning Dieterichs
b23b90c8bf Fixes dev port. 2020-08-16 11:03:05 +02:00
hediet
24aa6857e5 Code improvements. 2020-08-14 16:10:45 +02:00
hediet
7ba2428819 New logo, updates changelog, some code improvements. 2020-08-14 13:45:43 +02:00
hediet
f09e345908 Migrates to new visualization framework. 2020-08-13 21:55:02 +02:00
Henning Dieterichs
c56f21d8ab Converts backslashs to slashs. 2020-07-24 19:24:13 +02:00
Henning Dieterichs
5964c1c503 Adds support for pwa-node and adds a poll option. 2020-07-20 19:40:12 +02:00
Henning Dieterichs
e5707a0f2e fsevents fix. 2020-05-26 22:12:03 +02:00
Henning Dieterichs
d0f1a0144e Removes check script. 2020-05-26 22:05:49 +02:00
Henning Dieterichs
83b93b1369 Adds ci script. 2020-05-26 21:57:50 +02:00
Henning Dieterichs
39de71521b Adds automatic triage github action. 2020-05-26 21:57:24 +02:00
Henning Dieterichs
51a42eb497 adds check script. 2020-05-26 21:57:12 +02:00
Henning Dieterichs
ac90389aa1 Adds funding options. 2020-05-26 21:57:04 +02:00
Henning Dieterichs
80c5d34c81 v1.1.0 2020-05-13 18:43:20 +02:00
Henning Dieterichs
787fc73b33 Configures port mapping for webviews so that remote development is supported.
Enabled retainContextWhenHidden to get faster performance.
2020-05-13 18:43:06 +02:00
Henning Dieterichs
33b076cbd3 Removes default context. 2020-05-13 18:22:26 +02:00
Henning Dieterichs
d73b3797cb Treats pwa-chrome as js debug adapter. 2020-05-13 18:22:26 +02:00
Henning Dieterichs
04ea412322 Adds keywords and bugs property. 2020-05-13 18:21:27 +02:00
Henning Dieterichs
b65ba5fdbe Improves error messages and uses repl mode by default. 2020-05-13 18:20:54 +02:00
Henning Dieterichs
410e5d3048 Adds prettier as local dependency. 2020-05-13 00:11:34 +02:00
Henning Dieterichs
e9803a0e98 Makes server readonly. 2020-05-13 00:11:08 +02:00
Henning Dieterichs
0a65f6425e Fixes another Debug Session tracking bug. 2020-05-13 00:10:58 +02:00
Henning Dieterichs
c9625d4939 Fixes a bug that it does not track a debug session if the debug visualizer is initialized after the debugging has started. 2020-05-12 23:08:39 +02:00
Henning Dieterichs
59f94eda45 Merge pull request #51 from Yardanico/addnim
Add Nim demo
2020-05-11 10:22:55 +02:00
Danil Yarantsev
507e4fc695 Add license 2020-05-11 11:11:34 +03:00
Danil Yarantsev
52599482f3 Add Nim demo 2020-05-11 11:04:39 +03:00
Henning Dieterichs
415f061697 Updates extension readme. Adds comparison to the watch window. 2020-04-11 16:23:51 +02:00
111 changed files with 7438 additions and 17796 deletions

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

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

23
.github/workflows/ci.yml vendored Normal file
View File

@@ -0,0 +1,23 @@
on:
push:
branches:
- master
pull_request:
jobs:
build:
strategy:
matrix:
os: [macos-latest, ubuntu-latest, windows-latest]
runs-on: ${{ matrix.os }}
steps:
- name: Checkout
uses: actions/checkout@v2
with:
submodules: true
- name: Install Node.js
uses: actions/setup-node@v1
with:
node-version: 10.x
- run: yarn install
- run: yarn build

View File

@@ -0,0 +1,15 @@
name: Move new issues into Triage
on:
issues:
types: [opened]
jobs:
automate-project-columns:
runs-on: ubuntu-latest
steps:
- uses: alex-page/github-project-automation-plus@v0.2.3
with:
project: Backlog
column: Triage
repo-token: ${{ secrets.GH_TOKEN }}

8
.vscode/launch.json vendored
View File

@@ -15,7 +15,7 @@
"runtimeExecutable": "${execPath}",
"args": [
"--extensionDevelopmentPath=${workspaceFolder}/extension",
"${workspaceFolder}\\demos\\php"
"${workspaceFolder}/demos/php"
],
"env": {
"HOT_RELOAD": "true",
@@ -31,7 +31,7 @@
"runtimeExecutable": "${execPath}",
"args": [
"--extensionDevelopmentPath=${workspaceFolder}/extension",
"${workspaceFolder}\\demos\\php"
"${workspaceFolder}/demos/php"
],
"env": {
"HOT_RELOAD": "true",
@@ -47,7 +47,7 @@
"runtimeExecutable": "${execPath}",
"args": [
"--extensionDevelopmentPath=${workspaceFolder}/extension",
"${workspaceFolder}\\demos\\js"
"${workspaceFolder}/demos/python"
],
"env": {
"HOT_RELOAD": "",
@@ -62,7 +62,7 @@
"runtimeExecutable": "${execPath}",
"args": [
"--extensionDevelopmentPath=${workspaceFolder}/extension",
"${workspaceFolder}\\demos\\php"
"${workspaceFolder}/demos/js"
],
"env": {
"HOT_RELOAD": "",

View File

@@ -7,5 +7,6 @@
"plantuml.exportOutDir": "docs/exported",
"typescript.updateImportsOnFileMove.enabled": "prompt",
"mochaExplorer.files": "./data-extraction/test/**/*.test.ts",
"mochaExplorer.require": ["ts-node/register", "source-map-support/register"]
"mochaExplorer.require": ["ts-node/register", "source-map-support/register"],
"tasksStatusbar.taskLabelFilter": "dev"
}

View File

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

View File

@@ -1,7 +1,7 @@
{
"name": "@hediet/debug-visualizer-data-extraction",
"description": "A library that helps implementing data extractors for the Debug Visualizer VS Code extension.",
"version": "0.12.0",
"version": "0.13.0",
"main": "dist/index.js",
"types": "dist/index.d.ts",
"author": {

View File

@@ -1,122 +1,256 @@
import { ExtractedData } from "./DataExtractionResult";
// This file was created automatically. Do not edit it manually!
export type CommonDataType =
| CommonDataTypes.Text
| CommonDataTypes.Svg
| CommonDataTypes.Html
| CommonDataTypes.DotGraph
| CommonDataTypes.Tree
| CommonDataTypes.Ast
| CommonDataTypes.Graph
| CommonDataTypes.Plotly
| CommonDataTypes.Grid;
export type KnownVisualizationData =
| TreeVisualizationData
| AstTreeVisualizationData
| GraphvizDotVisualizationData
| GraphVisualizationData
| GraphVisualizationData
| GridVisualizationData
| ImageVisualizationData
| MonacoTextVisualizationData
| TableVisualizationData
| PlotlyVisualizationData
| SimpleTextVisualizationData
| SvgVisualizationData;
export function isCommonDataType<T>(
data: ExtractedData,
kind: T
): data is Narrow<CommonDataType, { kind: T }> {
for (const key of Object.keys(kind)) {
if (!(key in data.kind)) {
return false;
}
}
return true;
}
export type TreeVisualizationData = {
kind: {
tree: true;
};
root: TreeNode;
};
type Narrow<T, TKind> = T extends TKind ? T : never;
export module CommonDataTypes {
export interface Text {
kind: { text: true };
text: string;
mimeType?: string;
fileName?: string;
}
export interface Graph {
kind: { graph: true };
nodes: NodeGraphData[];
edges: EdgeGraphData[];
}
export interface Svg extends Text {
kind: { text: true; svg: true };
}
export interface Html extends Text {
kind: { text: true; html: true };
}
export interface DotGraph extends Text {
kind: { text: true; dotGraph: true };
}
export interface Tree<TData = unknown> {
kind: { tree: true };
root: TreeNode<TData>;
}
export interface Grid {
kind: { array: true };
columnLabels?: { label?: string }[];
rows: {
label?: string;
columns: {
content?: string;
tag?: string;
color?: string;
}[];
}[];
markers?: {
id: string;
row: number;
column: number;
rows?: number;
columns?: number;
label?: string;
color?: string;
}[];
}
export interface Ast
extends Tree<{
position: number;
length: number;
}>,
Text {
kind: { text: true; tree: true; ast: true };
}
export interface Plotly {
kind: { plotly: true };
data: Partial<Plotly.Data>[];
}
}
export interface TreeNode<TExtraData> {
name: string;
children: TreeNode<TExtraData>[];
data: TExtraData;
id?: string;
value?: string;
emphasizedValue?: string;
export type TreeNode = {
/**
* The children of this tree-node
*/
children: TreeNode[];
/**
* The parts that make up the text of this item
*/
items: TreeNodeItem[];
/**
* If a node is selected, the concatenation of all segment values from root to the selected node is shown to the user.
*/
segment?: string;
/**
* Marked nodes are highlighted and scrolled into view on every visualization update.
*/
isMarked?: boolean;
}
};
export interface NodeGraphData {
export type TreeNodeItem = {
/**
* The text to show
*/
text: string;
/**
* The style of the text
*/
emphasis?: "style1" | "style2" | "style3" | string;
};
export type AstTreeVisualizationData = {
kind: {
ast: true;
tree: true;
text: true;
};
root: AstTreeNode;
text: string;
fileName?: string;
};
export type AstTreeNode = {
children: AstTreeNode[];
items: AstTreeNodeItem[];
segment?: string;
isMarked?: boolean;
span: {
start: number;
length: number;
};
};
export type AstTreeNodeItem = {
text: string;
emphasis?: "style1" | "style2" | "style3" | string;
};
export type GraphvizDotVisualizationData = {
kind: {
dotGraph: true;
};
text: string;
};
export type GraphVisualizationData = {
kind: {
graph: true;
};
nodes: GraphNode[];
edges: GraphEdge[];
};
export type GraphNode = {
id: string;
label?: string;
color?: string;
shape?: "ellipse" | "box";
}
};
export interface EdgeGraphData {
export type GraphEdge = {
from: string;
to: string;
label?: string;
id?: string;
color?: string;
dashes?: boolean;
}
};
export type GridVisualizationData = {
kind: {
grid: true;
};
columnLabels?: {
label?: string;
}[];
rows: {
label?: string;
columns: {
content?: string;
/**
* A value to identify this cell. Should be unique.
*/
tag?: string;
color?: string;
}[];
}[];
markers?: {
id: string;
row: number;
column: number;
rows?: number;
columns?: number;
label?: string;
color?: string;
}[];
};
export type ImageVisualizationData = {
kind: {
imagePng: true;
};
/**
* The base 64 encoded PNG representation of the image
*/
base64Data: string;
};
export type MonacoTextVisualizationData = {
kind: {
text: true;
};
/**
* The text to show
*/
text: string;
/**
* An optional filename that might be used for chosing a syntax highlighter
*/
fileName?: string;
};
export type TableVisualizationData = {
kind: {
table: true;
};
/**
* An array of objects. The properties of the objects are used as columns.
*/
rows: {}[];
};
export type PlotlyVisualizationData = {
kind: {
plotly: true;
};
/**
* Expecting Plotly.Data[] (https://github.com/DefinitelyTyped/DefinitelyTyped/blob/795ce172038dbafcb9cba030d637d733a7eea19c/types/plotly.js/index.d.ts#L1036)
*/
data: {
text?: string | string[];
xaxis?: string;
yaxis?: string;
x?: (string | number | null)[] | (string | number | null)[][];
y?: (string | number | null)[] | (string | number | null)[][];
z?: (string | number | null)[] | (string | number | null)[][];
type?:
| "bar"
| "box"
| "candlestick"
| "choropleth"
| "contour"
| "heatmap"
| "histogram"
| "indicator"
| "mesh3d"
| "ohlc"
| "parcoords"
| "pie"
| "pointcloud"
| "scatter"
| "scatter3d"
| "scattergeo"
| "scattergl"
| "scatterpolar"
| "scatterternary"
| "sunburst"
| "surface"
| "treemap"
| "waterfall"
| "funnel"
| "funnelarea"
| "scattermapbox";
mode?:
| "lines"
| "markers"
| "text"
| "lines+markers"
| "text+markers"
| "text+lines"
| "text+lines+markers"
| "none"
| "gauge"
| "number"
| "delta"
| "number+delta"
| "gauge+number"
| "gauge+number+delta"
| "gauge+delta";
}[];
/**
* Expecting Partial<Plotly.Layout> (https://github.com/DefinitelyTyped/DefinitelyTyped/blob/795ce172038dbafcb9cba030d637d733a7eea19c/types/plotly.js/index.d.ts#L329)
*/
layout?: {
title?: string;
};
};
export type SimpleTextVisualizationData = {
kind: {
text: true;
};
text: string;
};
export type SvgVisualizationData = {
kind: {
svg: true;
};
/**
* The svg content
*/
text: string;
};

View File

@@ -1,27 +1,27 @@
export type DataExtractionResult = {
data: ExtractedData;
export interface DataExtractionResult {
data: VisualizationData;
usedExtractor: DataExtractorInfo;
availableExtractors: DataExtractorInfo[];
};
}
/**
* Instances must be valid json values.
*/
export type ExtractedData = {
export interface VisualizationData {
kind: Record<string, true>;
};
}
export type DataExtractorInfo = {
export interface DataExtractorInfo {
id: DataExtractorId;
name: string;
priority: number;
};
}
export type DataExtractorId = {
__brand: "DataExtractorId";
} & string;
export function isExtractedData(val: unknown): val is ExtractedData {
export function isVisualizationData(val: unknown): val is VisualizationData {
if (typeof val !== "object" || !val || !("kind" in val)) {
return false;
}

View File

@@ -1,5 +1,5 @@
import {
ExtractedData,
VisualizationData,
DataExtractionResult,
} from "../../DataExtractionResult";
@@ -7,14 +7,12 @@ export interface DataExtractorApi {
/**
* Registers a single extractor.
*/
registerExtractor<TExtractedData extends ExtractedData>(
extractor: DataExtractor<TExtractedData>
): void;
registerExtractor(extractor: DataExtractor): void;
/**
* Registers multiple extractors.
*/
registerExtractors(extractors: DataExtractor<ExtractedData>[]): void;
registerExtractors(extractors: DataExtractor[]): void;
/**
* Extracts data from the result of `valueFn`.
@@ -46,23 +44,23 @@ export interface JSONString<T> extends String {
__brand: { json: T };
}
export interface DataExtractor<T extends ExtractedData> {
export interface DataExtractor {
/**
* Must be unique among all data extractors.
*/
id: string;
getExtractions(
data: unknown,
extractionCollector: ExtractionCollector<T>,
extractionCollector: ExtractionCollector,
context: DataExtractorContext
): void;
}
export interface ExtractionCollector<T extends ExtractedData> {
export interface ExtractionCollector {
/**
* Suggests a possible extraction.
*/
addExtraction(extraction: DataExtraction<T>): void;
addExtraction(extraction: DataExtraction): void;
}
export interface DataExtractorContext {
@@ -72,7 +70,7 @@ export interface DataExtractorContext {
evalFn: <TEval>(expression: string) => TEval;
}
export interface DataExtraction<TData extends ExtractedData> {
export interface DataExtraction {
/**
* Higher priorities are preferred.
*/
@@ -87,5 +85,5 @@ export interface DataExtraction<TData extends ExtractedData> {
* A user friendly name of this extraction.
*/
name: string;
extractData(): TData;
extractData(): VisualizationData;
}

View File

@@ -6,7 +6,7 @@ import {
DataExtraction,
ExtractionCollector,
} from "./DataExtractorApi";
import { ExtractedData, DataExtractorInfo } from "../../DataExtractionResult";
import { DataExtractorInfo } from "../../DataExtractionResult";
import { registerDefaultExtractors } from "./default-extractors";
/**
@@ -15,24 +15,17 @@ import { registerDefaultExtractors } from "./default-extractors";
export class DataExtractorApiImpl implements DataExtractorApi {
public static lastEvalFn: (<T>(expression: string) => T) | undefined;
private readonly extractors = new Map<
string,
DataExtractor<ExtractedData>
>();
private readonly extractors = new Map<string, DataExtractor>();
private toJson<TData>(data: TData): JSONString<TData> {
return JSON.stringify(data) as any;
}
public registerExtractor<TExtractedData extends ExtractedData>(
extractor: DataExtractor<TExtractedData>
): void {
public registerExtractor(extractor: DataExtractor): void {
this.extractors.set(extractor.id, extractor);
}
public registerExtractors(
extractors: DataExtractor<ExtractedData>[]
): void {
public registerExtractors(extractors: DataExtractor[]): void {
for (const e of extractors) {
this.registerExtractor(e);
}
@@ -43,8 +36,8 @@ export class DataExtractorApiImpl implements DataExtractorApi {
evalFn: <T>(expression: string) => T,
preferredDataExtractorId: string | undefined
): JSONString<DataResult> {
const extractions = new Array<DataExtraction<ExtractedData>>();
const extractionCollector: ExtractionCollector<ExtractedData> = {
const extractions = new Array<DataExtraction>();
const extractionCollector: ExtractionCollector = {
addExtraction(extraction) {
extractions.push(extraction);
},
@@ -72,9 +65,7 @@ export class DataExtractorApiImpl implements DataExtractorApi {
}
}
function mapExtractor(
e: DataExtraction<ExtractedData>
): DataExtractorInfo {
function mapExtractor(e: DataExtraction): DataExtractorInfo {
return {
id: e.id as any,
name: e.name,

View File

@@ -1,18 +1,18 @@
import { ExtractedData, isExtractedData } from "../../../DataExtractionResult";
import { isVisualizationData } from "../../../DataExtractionResult";
import {
DataExtractor,
ExtractionCollector,
DataExtractorContext,
} from "../..";
export class AsIsDataExtractor implements DataExtractor<ExtractedData> {
export class AsIsDataExtractor implements DataExtractor {
readonly id = "as-is";
getExtractions(
data: unknown,
extractionCollector: ExtractionCollector<ExtractedData>,
extractionCollector: ExtractionCollector,
context: DataExtractorContext
): void {
if (!isExtractedData(data)) {
if (!isVisualizationData(data)) {
return;
}

View File

@@ -1,32 +1,33 @@
import { ExtractedData } from "../../../DataExtractionResult";
import { VisualizationData } from "../../../DataExtractionResult";
import {
DataExtractor,
ExtractionCollector,
DataExtractorContext,
} from "../DataExtractorApi";
export class GetDebugVisualizationDataExtractor
implements DataExtractor<ExtractedData> {
readonly id = "get-debug-visualization";
export class GetVisualizationDataExtractor implements DataExtractor {
readonly id = "get-visualization-data";
getExtractions(
data: unknown,
collector: ExtractionCollector<ExtractedData>,
collector: ExtractionCollector,
context: DataExtractorContext
): void {
if (
typeof data !== "object" ||
!data ||
!("getDebugVisualization" in data)
) {
if (typeof data !== "object" || !data) {
return;
}
const getVisualizationData = (data as any)
.getVisualizationData as Function;
if (typeof getVisualizationData !== "function") {
return;
}
collector.addExtraction({
id: this.id,
name: "Use Method 'getDebugVisualization'",
name: "Use Method 'getVisualizationData'",
priority: 600,
extractData() {
return (data as any).getDebugVisualization();
return getVisualizationData.apply(data);
},
});
}

View File

@@ -1,16 +1,16 @@
import { ExtractedData } from "../../../DataExtractionResult";
import {
DataExtractor,
ExtractionCollector,
DataExtractorContext,
} from "../..";
import { CommonDataTypes } from "../../../CommonDataTypes";
import { GridVisualizationData } from "../../../CommonDataTypes";
import { expect } from "../../../util";
export class GridExtractor implements DataExtractor<ExtractedData> {
export class GridExtractor implements DataExtractor {
readonly id = "grid";
getExtractions(
data: unknown,
extractionCollector: ExtractionCollector<ExtractedData>,
extractionCollector: ExtractionCollector,
context: DataExtractorContext
): void {
if (!Array.isArray(data)) {
@@ -21,12 +21,11 @@ export class GridExtractor implements DataExtractor<ExtractedData> {
id: this.id,
name: "Array As Grid",
priority: 500,
extractData(): CommonDataTypes.Grid {
return {
kind: { array: true },
extractData: () =>
expect<GridVisualizationData>({
kind: { grid: true },
rows: [{ columns: data.map(d => ({ tag: d })) }],
};
},
}),
});
}
}

View File

@@ -1,4 +1,4 @@
import { ExtractedData } from "../../../DataExtractionResult";
import { VisualizationData } from "../../../DataExtractionResult";
import {
DataExtractor,
ExtractionCollector,
@@ -6,12 +6,12 @@ import {
} from "../DataExtractorApi";
import { createGraph, CreateGraphEdge } from "../../helpers";
export class ObjectGraphExtractor implements DataExtractor<ExtractedData> {
export class ObjectGraphExtractor implements DataExtractor {
readonly id = "object-graph";
getExtractions(
data: unknown,
collector: ExtractionCollector<ExtractedData>,
collector: ExtractionCollector,
context: DataExtractorContext
): void {
function isObject(val: unknown): val is object {

View File

@@ -1,17 +1,17 @@
import { ExtractedData } from "../../../DataExtractionResult";
import { PlotlyVisualizationData } from "../../../CommonDataTypes";
import { expect } from "../../../util";
import {
DataExtractor,
ExtractionCollector,
DataExtractorContext,
} from "../DataExtractorApi";
import { CommonDataTypes } from "../../../CommonDataTypes";
export class PlotDataExtractor implements DataExtractor<ExtractedData> {
export class PlotlyDataExtractor implements DataExtractor {
readonly id = "plot";
getExtractions(
data: unknown,
collector: ExtractionCollector<ExtractedData>,
collector: ExtractionCollector,
context: DataExtractorContext
): void {
if (!Array.isArray(data)) {
@@ -25,14 +25,13 @@ export class PlotDataExtractor implements DataExtractor<ExtractedData> {
id: "plot-y",
name: "Plot as y-Values",
priority: 1001,
extractData() {
return {
extractData: () =>
expect<PlotlyVisualizationData>({
kind: {
plotly: true,
},
data: [{ y: data }],
} as CommonDataTypes.Plotly;
},
}),
});
}
}

View File

@@ -1,16 +1,17 @@
import { ExtractedData } from "../../../DataExtractionResult";
import { MonacoTextVisualizationData } from "../../../CommonDataTypes";
import { expect } from "../../../util";
import {
DataExtractor,
ExtractionCollector,
DataExtractorContext,
} from "../DataExtractorApi";
export class ToStringDataExtractor implements DataExtractor<ExtractedData> {
export class ToStringDataExtractor implements DataExtractor {
readonly id = "to-string";
getExtractions(
data: unknown,
collector: ExtractionCollector<ExtractedData>,
collector: ExtractionCollector,
context: DataExtractorContext
): void {
collector.addExtraction({
@@ -18,12 +19,12 @@ export class ToStringDataExtractor implements DataExtractor<ExtractedData> {
name: "To String",
priority: 100,
extractData() {
return {
return expect<MonacoTextVisualizationData>({
kind: {
text: true,
},
text: "" + data,
};
});
},
});
}

View File

@@ -1,19 +1,19 @@
import * as ts from "typescript"; // Only compile-time import!
import { AstTreeNode } from "../../..";
import { AstTreeVisualizationData } from "../../../CommonDataTypes";
import { expect } from "../../../util";
import {
DataExtractor,
ExtractionCollector,
DataExtractorContext,
} from "../DataExtractorApi";
import { CommonDataTypes } from "../../../CommonDataTypes";
// This class is self contained and can be injected into both nodejs and browser environments.
export class TypeScriptAstDataExtractor
implements DataExtractor<CommonDataTypes.Ast> {
export class TypeScriptAstDataExtractor implements DataExtractor {
readonly id = "typescript-ast";
getExtractions(
data: unknown,
collector: ExtractionCollector<CommonDataTypes.Ast>,
collector: ExtractionCollector,
{ evalFn }: DataExtractorContext
): void {
if (!data) {
@@ -40,114 +40,21 @@ export class TypeScriptAstDataExtractor
return;
}
function findKey(value: any, object: any): string | null {
for (var key in object) {
if (key.startsWith("_")) continue;
var member = object[key];
if (member === value) return key;
if (Array.isArray(member) && member.indexOf(value) !== -1) {
return key;
}
}
return null;
}
function getChildren(node: ts.Node): ts.Node[] {
const result = new Array<ts.Node>();
tsApi.forEachChild(node, n => {
result.push(n);
});
return result;
}
function toTreeNode(
node: ts.Node,
memberName: string,
marked: Set<ts.Node>,
emphasizedValueFn: (node: ts.Node) => string | undefined
): CommonDataTypes.Ast["root"] {
const name = tsApi.SyntaxKind[node.kind];
const children = getChildren(node)
.map((childNode, idx) => {
let parentPropertyName = findKey(childNode, node) || "";
if (childNode.kind == tsApi.SyntaxKind.SyntaxList) {
const children = getChildren(childNode);
children.some(c => {
parentPropertyName = findKey(c, node) || "";
return !!parentPropertyName;
});
if (children.length === 0) return null!;
}
if (node.kind == tsApi.SyntaxKind.SyntaxList) {
parentPropertyName = "" + idx;
}
return toTreeNode(
childNode,
parentPropertyName,
marked,
emphasizedValueFn
);
})
.filter(c => c !== null);
let value: string | undefined = undefined;
if (tsApi.isIdentifier(node)) {
value = node.text || (node.escapedText as string);
} else if (tsApi.isLiteralExpression(node)) {
value = node.text;
}
return {
name: name,
id: memberName,
children: children,
data: {
length: node.end - node.pos,
position: node.pos,
},
emphasizedValue: emphasizedValueFn(node),
isMarked: marked.has(node),
value,
};
}
function isNode(node: unknown): node is ts.Node {
return (
typeof node === "object" &&
node !== null &&
(tsApi.isToken(node as any) || (tsApi as any).isNode(node))
);
}
function getSourceFile(node: ts.Node | any): ts.SourceFile {
if (!node) {
throw new Error("Detached node");
}
if (tsApi.isSourceFile(node)) {
return node;
}
if (!("getSourceFile" in node)) {
return getSourceFile(node.parent);
}
return node.getSourceFile();
}
const helper = new Helper(tsApi);
let rootSourceFile: ts.SourceFile | undefined = undefined;
let rootNode: ts.Node | undefined = undefined;
let marked: Set<ts.Node>;
let fn: (n: ts.Node) => string | undefined = (n: ts.Node) => undefined;
if (Array.isArray(data) && data.every(isNode) && data.length > 0) {
rootSourceFile = getSourceFile(data[0] as ts.Node);
if (
Array.isArray(data) &&
data.every(helper.isNode) &&
data.length > 0
) {
rootSourceFile = helper.getSourceFile(data[0] as ts.Node);
marked = new Set(data);
} else if (isNode(data)) {
rootSourceFile = getSourceFile(data);
} else if (helper.isNode(data)) {
rootSourceFile = helper.getSourceFile(data);
marked = new Set([data]);
} else if (typeof data === "object" && data) {
marked = new Set();
@@ -162,15 +69,18 @@ export class TypeScriptAstDataExtractor
rootNode = item;
}
let nodes: Array<ts.Node>;
if (isNode(item)) {
if (helper.isNode(item)) {
nodes = [item];
} else if (Array.isArray(item) && item.every(isNode)) {
} else if (
Array.isArray(item) &&
item.every(helper.isNode)
) {
nodes = item;
} else {
return;
}
if (nodes.length > 0 && !rootSourceFile) {
rootSourceFile = getSourceFile(nodes[0]);
rootSourceFile = helper.getSourceFile(nodes[0]);
}
for (const n of nodes) {
marked.add(n);
@@ -192,9 +102,9 @@ export class TypeScriptAstDataExtractor
name: "TypeScript AST",
priority: 1000,
extractData() {
return {
return expect<AstTreeVisualizationData>({
kind: { text: true, tree: true, ast: true },
root: toTreeNode(
root: helper.toTreeNode(
rootNode || finalRootSourceFile,
"root",
marked,
@@ -202,8 +112,122 @@ export class TypeScriptAstDataExtractor
),
text: finalRootSourceFile.text,
fileName: "index.ts",
};
});
},
});
}
}
class Helper {
constructor(private readonly tsApi: typeof ts) {}
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;
}
getChildren(node: ts.Node): ts.Node[] {
const result = new Array<ts.Node>();
this.tsApi.forEachChild(node, n => {
result.push(n);
});
return result;
}
toTreeNode(
node: ts.Node,
memberName: string,
marked: Set<ts.Node>,
emphasizedValueFn: (node: ts.Node) => string | undefined
): AstTreeNode {
const name = this.tsApi.SyntaxKind[node.kind];
const children = this.getChildren(node)
.map((childNode, idx) => {
let parentPropertyName = this.findKey(childNode, node) || "";
if (childNode.kind == this.tsApi.SyntaxKind.SyntaxList) {
const children = this.getChildren(childNode);
children.some(c => {
parentPropertyName = this.findKey(c, node) || "";
return !!parentPropertyName;
});
if (children.length === 0) return null!;
}
if (node.kind == this.tsApi.SyntaxKind.SyntaxList) {
parentPropertyName = "" + idx;
}
return this.toTreeNode(
childNode,
parentPropertyName,
marked,
emphasizedValueFn
);
})
.filter(c => c !== null);
let value: string | undefined = undefined;
if (this.tsApi.isIdentifier(node)) {
value = node.text || (node.escapedText as string);
} else if (this.tsApi.isLiteralExpression(node)) {
value = node.text;
}
const items: AstTreeNode["items"] = [
{ text: `${memberName}: `, emphasis: "style1" },
{ text: name },
];
const emphasizedVal = emphasizedValueFn(node);
if (value) {
items.push({ text: value, emphasis: "style2" });
}
if (emphasizedVal) {
items.push({ text: emphasizedVal, emphasis: "style3" });
}
return {
items,
children: children,
span: {
length: node.end - node.pos,
start: node.pos,
},
isMarked: marked.has(node),
};
}
isNode = (node: unknown): node is ts.Node => {
return (
typeof node === "object" &&
node !== null &&
(this.tsApi.isToken(node as any) ||
(this.tsApi as any).isNode(node))
);
};
getSourceFile(node: ts.Node | any): ts.SourceFile {
if (!node) {
throw new Error("Detached node");
}
if (this.tsApi.isSourceFile(node)) {
return node;
}
if (!("getSourceFile" in node)) {
return this.getSourceFile(node.parent);
}
return node.getSourceFile();
}
}

View File

@@ -1,9 +1,9 @@
import { DataExtractorApi } from "../DataExtractorApi";
import { TypeScriptAstDataExtractor } from "./TypeScriptDataExtractors";
import { AsIsDataExtractor } from "./AsIsDataExtractor";
import { GetDebugVisualizationDataExtractor } from "./GetDebugVisualizationDataExtractor";
import { GetVisualizationDataExtractor } from "./GetDebugVisualizationDataExtractor";
import { ToStringDataExtractor } from "./ToStringExtractor";
import { PlotDataExtractor } from "./PlotlyDataExtractor";
import { PlotlyDataExtractor } from "./PlotlyDataExtractor";
import { ObjectGraphExtractor } from "./ObjectGraphExtractor";
import { getDataExtractorApi } from "../injection";
import { GridExtractor } from "./GridExtractor";
@@ -18,9 +18,9 @@ export function registerDefaultExtractors(
for (const item of [
new TypeScriptAstDataExtractor(),
new AsIsDataExtractor(),
new GetDebugVisualizationDataExtractor(),
new GetVisualizationDataExtractor(),
new ToStringDataExtractor(),
new PlotDataExtractor(),
new PlotlyDataExtractor(),
new ObjectGraphExtractor(),
new GridExtractor(),
]) {

View File

@@ -1,10 +1,10 @@
import {
CommonDataTypes,
EdgeGraphData,
NodeGraphData,
GraphEdge,
GraphNode,
GraphVisualizationData,
} from "../../CommonDataTypes";
export type CreateGraphEdge<T> = { to: T } & Omit<EdgeGraphData, "from" | "to">;
export type CreateGraphEdge<T> = { to: T } & Omit<GraphEdge, "from" | "to">;
/**
* Given a list of roots, it creates a graph by following their edges recursively.
@@ -17,10 +17,10 @@ export function createGraph<T>(
) => {
id?: string | number;
edges: CreateGraphEdge<T>[];
} & Omit<NodeGraphData, "id">,
} & Omit<GraphNode, "id">,
options: { maxSize?: number } = {}
): CommonDataTypes.Graph {
const r: CommonDataTypes.Graph = {
): GraphVisualizationData {
const r: GraphVisualizationData = {
kind: {
graph: true,
},

View File

@@ -1,4 +1,4 @@
import { NodeGraphData, CommonDataTypes } from "../../CommonDataTypes";
import { GraphNode, GraphVisualizationData } from "../../CommonDataTypes";
import { CreateGraphEdge, createGraph } from "./createGraph";
/**
@@ -12,8 +12,8 @@ export function createGraphFromPointers<T>(
) => {
id?: string | number;
edges: CreateGraphEdge<T>[];
} & Omit<NodeGraphData, "id">
): CommonDataTypes.Graph {
} & Omit<GraphNode, "id">
): GraphVisualizationData {
const marker = {};
interface Pointer {

View File

@@ -1,11 +1,11 @@
import { CommonDataTypes } from "../../CommonDataTypes";
import { GridVisualizationData } from "../../CommonDataTypes";
export function markedGrid(
arr: any[],
marked: Record<string, number>
): CommonDataTypes.Grid {
): GridVisualizationData {
return {
kind: { array: true },
kind: { grid: true },
rows: [{ columns: arr.map(d => ({ tag: d })) }],
markers: Object.entries(marked).map(([key, val]) => ({
id: key,

View File

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

View File

@@ -2,7 +2,7 @@
# yarn lockfile v1
"@hediet/live-debug@^0.5.1", "@hediet/live-debug@^0.5.2":
"@hediet/live-debug@^0.5.1":
version "0.5.2"
resolved "https://registry.yarnpkg.com/@hediet/live-debug/-/live-debug-0.5.2.tgz#f2ee0034f014836bfd68e26c35a168ab42b15486"
integrity sha512-HBqVbyFez1oQlIPO9dzkFi7/9Gvt6ZpssgURzYaVFREl9Oq03nPd6V7Ay4bW03xFHPo3YT2Yff9mFmGQUX7JEg==
@@ -13,30 +13,21 @@
stacktracey "^1.2.112"
ws "^7.0.0"
"@hediet/node-reload@0.7.0":
version "0.7.0"
resolved "https://registry.yarnpkg.com/@hediet/node-reload/-/node-reload-0.7.0.tgz#ec50160ce26203079f18e7024cd58025fa79652a"
integrity sha512-gWFbq2tOThSV9GT+pFx1JJzFuOV7l40IsseOFHMJGyvLmmAdgbJPV3OZuV9gcsXIGocOtbfEyky9S4tQELLLxA==
"@hediet/node-reload@0.7.3":
version "0.7.3"
resolved "https://registry.yarnpkg.com/@hediet/node-reload/-/node-reload-0.7.3.tgz#305d68f54185200b018b14f257037ab8d8ca9857"
integrity sha512-icOyfDpArfvqmw0VLf2CoGR/jCSuWk3fScJGFuXQHpb0c9yNoGTiuG3Q8L2iJIuDzCjRu47DR2T+yCvZsY/f+w==
dependencies:
"@hediet/live-debug" "^0.5.1"
"@hediet/std" "^0.6.0"
"@hediet/typed-json-rpc" "^0.7.6"
"@types/node" "^12.0.7"
"@types/stack-trace" "^0.0.29"
chalk "^2.4.2"
chokidar "^3.0.1"
stack-trace "^0.0.10"
ws "^7.0.0"
"@hediet/std@^0.4.0":
version "0.4.0"
resolved "https://registry.yarnpkg.com/@hediet/std/-/std-0.4.0.tgz#95dfd6e9d62f8bf9d91f3925d37b7b359b2af047"
integrity sha512-2kw4vSOprLchTjhcg8qQo6fdbBxJicSUrzZXgL0KzgMv18hjVmpm57NPLCPfI6115kAaMIWvMnlHFLYbx7poQQ==
dependencies:
"@types/chai" "^4.1.7"
"@types/sinon" "^7.0.10"
chai "^4.2.0"
sinon "^7.2.7"
"@hediet/std@^0.6.0":
version "0.6.0"
resolved "https://registry.yarnpkg.com/@hediet/std/-/std-0.6.0.tgz#f7d2ffc1180ecef6ff272d0d6780f0c0d9337e4e"
@@ -109,6 +100,11 @@
resolved "https://registry.yarnpkg.com/@types/node/-/node-12.6.8.tgz#e469b4bf9d1c9832aee4907ba8a051494357c12c"
integrity sha512-aX+gFgA5GHcDi89KG5keey2zf0WfZk/HAQotEamsK2kbey+8yGKcson0hbK8E+v0NArlCJQCqMP161YhV6ZXLg==
"@types/node@^13.7.4":
version "13.13.15"
resolved "https://registry.yarnpkg.com/@types/node/-/node-13.13.15.tgz#fe1cc3aa465a3ea6858b793fd380b66c39919766"
integrity sha512-kwbcs0jySLxzLsa2nWUAGOd/s21WU1jebrEdtzhsj1D4Yps1EOuyI1Qcu+FD56dL7NRNIJtDDjcqIG22NwkgLw==
"@types/node@^8.0.7":
version "8.10.51"
resolved "https://registry.yarnpkg.com/@types/node/-/node-8.10.51.tgz#80600857c0a47a8e8bafc2dae6daed6db58e3627"
@@ -132,6 +128,13 @@
"@types/events" "*"
"@types/node" "*"
ansi-styles@^3.2.1:
version "3.2.1"
resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-3.2.1.tgz#41fbb20243e50b12be0f04b8dedbf07520ce841d"
integrity sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==
dependencies:
color-convert "^1.9.0"
anymatch@^3.0.1:
version "3.0.3"
resolved "https://registry.yarnpkg.com/anymatch/-/anymatch-3.0.3.tgz#2fb624fe0e84bccab00afee3d0006ed310f22f09"
@@ -186,6 +189,15 @@ chai@^4.2.0:
pathval "^1.1.0"
type-detect "^4.0.5"
chalk@^2.4.2:
version "2.4.2"
resolved "https://registry.yarnpkg.com/chalk/-/chalk-2.4.2.tgz#cd42541677a54333cf541a49108c1432b44c9424"
integrity sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==
dependencies:
ansi-styles "^3.2.1"
escape-string-regexp "^1.0.5"
supports-color "^5.3.0"
check-error@^1.0.2:
version "1.0.2"
resolved "https://registry.yarnpkg.com/check-error/-/check-error-1.0.2.tgz#574d312edd88bb5dd8912e9286dd6c0aed4aac82"
@@ -206,6 +218,18 @@ chokidar@^3.0.1:
optionalDependencies:
fsevents "^2.0.6"
color-convert@^1.9.0:
version "1.9.3"
resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-1.9.3.tgz#bb71850690e1f136567de629d2d5471deda4c1e8"
integrity sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==
dependencies:
color-name "1.1.3"
color-name@1.1.3:
version "1.1.3"
resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.3.tgz#a7d0558bd89c42f795dd42328f740831ca53bc25"
integrity sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=
data-uri-to-buffer@^2.0.0:
version "2.0.1"
resolved "https://registry.yarnpkg.com/data-uri-to-buffer/-/data-uri-to-buffer-2.0.1.tgz#ca8f56fe38b1fd329473e9d1b4a9afcd8ce1c045"
@@ -225,6 +249,11 @@ diff@^3.5.0:
resolved "https://registry.yarnpkg.com/diff/-/diff-3.5.0.tgz#800c0dd1e0a8bfbc95835c202ad220fe317e5a12"
integrity sha512-A46qtFgd+g7pDZinpnwiRJtxbC1hpgf0uzP3iG89scHk0AUC7A1TGxf5OiiOUv/JMZR8GOt8hL900hV0bOy5xA==
escape-string-regexp@^1.0.5:
version "1.0.5"
resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz#1b61c0562190a8dff6ae3bb2cf0200ca130b86d4"
integrity sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=
fill-range@^7.0.1:
version "7.0.1"
resolved "https://registry.yarnpkg.com/fill-range/-/fill-range-7.0.1.tgz#1919a6a7c75fe38b2c7c77e5198535da9acdda40"
@@ -404,7 +433,7 @@ stacktracey@^1.2.112:
as-table "^1.0.36"
get-source "^1.0.34"
supports-color@^5.5.0:
supports-color@^5.3.0, supports-color@^5.5.0:
version "5.5.0"
resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-5.5.0.tgz#e2e69a44ac8772f78a1ec0b35b689df6530efc8f"
integrity sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==
@@ -423,10 +452,10 @@ type-detect@4.0.8, type-detect@^4.0.0, type-detect@^4.0.5:
resolved "https://registry.yarnpkg.com/type-detect/-/type-detect-4.0.8.tgz#7646fb5f18871cfbb7749e69bd39a6388eb7450c"
integrity sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==
typescript@^3.5.3:
version "3.5.3"
resolved "https://registry.yarnpkg.com/typescript/-/typescript-3.5.3.tgz#c830f657f93f1ea846819e929092f5fe5983e977"
integrity sha512-ACzBtm/PhXBDId6a6sDJfroT2pOWt/oOnk4/dElG5G33ZL776N3Y6/6bKZJBFpd+b05F3Ct9qDjMeJmRWtE2/g==
typescript@^3.8.2:
version "3.9.7"
resolved "https://registry.yarnpkg.com/typescript/-/typescript-3.9.7.tgz#98d600a5ebdc38f40cb277522f12dc800e9e25fa"
integrity sha512-BLbiRkiBzAwsjut4x/dsibSTB6yWpwT5qWmC2OfuCg3GgVQCSgMs4vEctYPhsaGtd0AeuuHMkjZ2h2WG8MSzRw==
ws@^7.0.0:
version "7.1.1"

18
demos/nim/.vscode/launch.json vendored Normal file
View File

@@ -0,0 +1,18 @@
{
"version": "0.2.0",
"configurations": [
{
"type": "lldb",
"request": "launch",
"name": "Run your Executable",
"program": "${workspaceFolder}/main",
"args": [],
"cwd": "${workspaceFolder}",
"initCommands": [
// To let LLDB know that NCSTRING is just like a C string
"type format add --format c-string NCSTRING"
],
"preLaunchTask": "build"
}
]
}

10
demos/nim/.vscode/tasks.json vendored Normal file
View File

@@ -0,0 +1,10 @@
{
"version": "2.0.0",
"tasks": [
{
"label": "build",
"type": "shell",
"command": "nim c main.nim"
}
]
}

21
demos/nim/LICENSE Executable file
View File

@@ -0,0 +1,21 @@
MIT License
Copyright (c) 2020 Danil Yarantsev
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

82
demos/nim/main.nim Normal file
View File

@@ -0,0 +1,82 @@
import random, json, options, sugar
type
Kind = object
array: bool
Label = object
label: Option[string]
Column = object
content: Option[string]
tag: Option[string]
color: Option[string]
Row = object
label: Option[string]
columns: seq[Column]
Marker = object
id: string
row: int
column: int
rows: Option[int]
columns: Option[int]
label: Option[string]
color: Option[string]
Grid = object
kind: Kind
columnLabels: Option[seq[Label]]
rows: seq[Row]
markers: Option[seq[Marker]]
proc showSeq[T](data: seq[T], markers: seq[(string, int)] = @[]): cstring =
# Need to use cstring string type for C-compatible strings
let labels = collect(newSeq):
# Labels are indexes of the seq
for x in 0 ..< data.len():
Label(label: some($x))
let columns = collect(newSeq):
# Columns contain values of the seq
for x in data:
Column(content: some($x), tag: some($x))
let marks = collect(newSeq):
# If there are any markers (id - string, index - int)
for marker in markers:
Marker(id: marker[0], row: 0, column: marker[1])
let grid = Grid(
kind: Kind(array: true),
rows: @[Row(columns: columns)],
columnLabels: some(labels),
markers: some(marks)
)
# Serialize to JSON
result = $ %grid
proc main =
# Random seed
randomize()
# Fill seq with 10 random values from 1 to 999
var a = newSeq[int]()
for x in 0..10:
a.add rand(1..999)
# Simple bubble sort with some markers
# Open the visualizer and type in "ress"
let n = a.len() - 1
var ress = showSeq(a, @[("i", 0), ("j", 0)])
for i in 0 .. n:
for j in 0 .. n - i - 1:
if a[j] > a[j+1]:
ress = showSeq(a, @[("i", i), ("j", j)]) # breakpoint
let temp = a[j]
a[j] = a[j + 1]
a[j + 1] = temp
ress = showSeq(a, @[("i", i), ("j", j)]) # breakpoint
main()

2
demos/nim/nim.cfg Normal file
View File

@@ -0,0 +1,2 @@
# For Nim source code lines
debugger:native

View File

@@ -1,5 +1,22 @@
# Change Log
## 2.0.0
- Support for the new JS debug adapters
- Moved visualizations to [github.com/hediet/visualization](https://github.com/hediet/visualization).
- New Visualizations / Improved design / Improved dark theme
- New Logo
## 1.1.0
- Fixes a bug that occurs when the debug visualizer is opened after the debug session has started.
- Fixes a bug that is caused by VS Code invoking the debug tracker multiple times for the same session.
- Adds support for the new `pwa-chrome` debug adapter.
- Uses `repl` mode as default evaluation context so that responses don't get truncated.
- Improves error messages when evaluating an expression or parsing the result goes wrong.
- Sets `webview.retainContextWhenHidden` to true, so that visualizer views load faster when they were already opened.
- Configures `webview.portMapping` so that this extension should work with remote development.
## 1.0.0
- "Debug Visualizer: Use Selection as Expression" will open a new view if no Debug Visualizer view was opened.

View File

@@ -3,12 +3,13 @@
[![](https://img.shields.io/twitter/follow/hediet_dev.svg?style=social)](https://twitter.com/intent/follow?screen_name=hediet_dev)
A VS Code extension for visualizing data structures while debugging.
Like the VS Code's watch view, but with custom visualizations.
![](../docs/demo.gif)
## Supported Languages
See [demos](./demos/) for demos. These languages and debuggers are verified to work with this extension:
See [demos](../demos/) for demos. These languages and debuggers are verified to work with this extension:
- JavaScript/TypeScript/... using `node`/`node2`/`extensionHost` or `chrome` debug adapter: [⭐ Full Support](../demos/js)
- Go using `go` (Delve) debug adapter: [✅ Rudimentary Support](../demos/golang)
@@ -28,8 +29,9 @@ Fully supported languages offer _Data Extractors_ which convert some well known
After installing this extension, use the command `Debug Visualizer: New View` to open a new visualizer view.
In this view you can enter an expression that is evaluated and visualized while stepping through your application.
This view works the same as the watch view of VS Code, except that the resulting value is presented visually rather than textually.
You can refresh the evaluation and pop out the current visualizer view into a new browser window by using the top right buttons.
You can refresh the evaluation and open the current visualizer view into a new browser window by using the top right buttons.
You can also unfold the details pane to select a _Data Extractor_ and a _Visualizer_.
Use the command `Debug Visualizer: Use Selection as Expression` (Shift + F1) to use the currently selected text as expression
@@ -39,7 +41,7 @@ in the most recently opened debug visualizer.
Visualizers consume specific JSON data. See [Integrated Visualizers](#Integrated%20Visualizers) for the schema of supported JSON data.
The currently visualized expression should evaluate to a JSON Object string,
The currently watched expression should evaluate to a JSON Object string,
matching the schema of one of the supported visualizers. This JSON string may be surrounded by single or double quotation marks (or none at all) and must not be escaped.
A valid example is `"{ "kind": { "text": true }, "text": "some text\nmore text" }"`.
@@ -217,7 +219,7 @@ interface DotGraph extends Text {
## JavaScript/TypeScript Integrated Data Extractors
Data extractors convert arbitrary values into data consumable by visualizers.
They live in the debugee. The following data extractors are injected automatically into the debugee by this extension when using the `node`, `node2`, `extensionHost` or `chrome` debug adapter.
They live in the debugee. The following data extractors are injected automatically into the debugee by this extension when using the `node`, `node2`, `extensionHost`, `chrome` or `pwa-chrome` debug adapter.
Custom data extractors can be registered too.
See the package `@hediet/debug-visualizer-data-extraction` and its [README](../data-extraction/README.md) for the implementation and its API.
Also, a global object of name `hedietDbgVis` with helper functions is injected.

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.8 KiB

View File

@@ -2,14 +2,27 @@
"name": "debug-visualizer",
"private": true,
"displayName": "Debug Visualizer",
"description": "An extension that visualizes values while debugging.",
"icon": "docs/logo.png",
"version": "1.0.0",
"description": "A visual watch window that lets you visualize your data structures while debugging.",
"icon": "docs/logo.drawio.png",
"version": "2.0.0",
"license": "GPL-3.0",
"engines": {
"vscode": "^1.35.0"
"vscode": "^1.46.0"
},
"publisher": "hediet",
"keywords": [
"debugger",
"debugging",
"debug",
"visual",
"javascript",
"graph",
"ast",
"visualization"
],
"bugs": {
"url": "https://github.com/hediet/vscode-debug-visualizer/issues"
},
"author": {
"email": "henning.dieterichs@live.de",
"name": "Henning Dieterichs"
@@ -95,7 +108,8 @@
"open": "^7.0.2",
"serve-static": "^1.14.1",
"ws": "^7.2.1",
"crypto-random-string": "^3.1.0"
"crypto-random-string": "^3.1.0",
"mobx-utils": "^5.6.1"
},
"devDependencies": {
"copy-webpack-plugin": "^5.1.1",
@@ -103,7 +117,7 @@
"@types/express": "^4.17.2",
"@types/serve-static": "^1.13.3",
"@types/node": "^13.7.4",
"@types/vscode": "1.35.0",
"@types/vscode": "1.46.0",
"tslint": "^6.0.0",
"typescript": "^3.8.2",
"webpack": "^4.41.6",

View File

@@ -1,55 +1,50 @@
import { workspace } from "vscode";
import { workspace, window, ColorThemeKind } from "vscode";
import { Disposable } from "@hediet/std/disposable";
import { observable } from "mobx";
import { DataExtractorId } from "@hediet/debug-visualizer-data-extraction";
const useChromeKioskModeKey = "debugVisualizer.useChromeKioskMode";
const debugAdapterConfigsKey = "debugVisualizer.debugAdapterConfigurations";
import { VsCodeSetting, serializerWithDefault } from "./utils/VsCodeSettings";
export class Config {
public dispose = Disposable.fn();
@observable
private _useChromeKioskMode!: boolean;
@observable
private _debugAdapterConfigs!: DebugAdapterConfigs;
private readonly _useChromeKioskMode = new VsCodeSetting(
"debugVisualizer.useChromeKioskMode",
{ serializer: serializerWithDefault<boolean>(true) }
);
public get useChromeKioskMode(): boolean {
return this._useChromeKioskMode;
return this._useChromeKioskMode.get();
}
public get debugAdapterConfigs(): DebugAdapterConfigs {
return this._debugAdapterConfigs;
private readonly _debugAdapterConfigs = new VsCodeSetting(
"debugVisualizer.debugAdapterConfigurations",
{ serializer: serializerWithDefault<DebugAdapterConfigs>({}) }
);
@observable
private _vsCodeTheme = window.activeColorTheme;
public get theme(): "light" | "dark" {
if (this._vsCodeTheme.kind === ColorThemeKind.Light) {
return "light";
} else if (this._vsCodeTheme.kind === ColorThemeKind.Dark) {
return "dark";
}
return "light";
}
constructor() {
this.updateConfig();
this.dispose.track(
workspace.onDidChangeConfiguration(() => {
this.updateConfig();
window.onDidChangeActiveColorTheme(() => {
this._vsCodeTheme = window.activeColorTheme;
})
);
}
private updateConfig(): void {
const c = workspace.getConfiguration();
this._useChromeKioskMode = mapUndefined(
c.get<boolean>(useChromeKioskModeKey),
true
);
this._debugAdapterConfigs = mapUndefined(
c.get<DebugAdapterConfigs>(debugAdapterConfigsKey),
{}
);
}
public getDebugAdapterConfig(
debugAdapterType: string
): DebugAdapterConfig | undefined {
const c = this.debugAdapterConfigs[debugAdapterType];
const c = this._debugAdapterConfigs.get()[debugAdapterType];
if (!c) {
return undefined;
}
@@ -65,13 +60,6 @@ export class Config {
}
}
function mapUndefined<T>(val: T | undefined, defaultVal: T) {
if (val === undefined) {
return defaultVal;
}
return val;
}
type DebugAdapterConfigs = {
[debugAdapter: string]: {
context?: "watch" | "repl";

View File

@@ -1,10 +1,10 @@
import { EvaluationEngine, Evaluator } from "./EvaluationEngine";
import { VsCodeDebugSession } from "../../VsCodeDebugger";
import { EnhancedDebugSession } from "../../debugger/EnhancedDebugSession";
export class ComposedEvaluationEngine implements EvaluationEngine {
constructor(public readonly engines: EvaluationEngine[]) {}
createEvaluator(session: VsCodeDebugSession): Evaluator | undefined {
createEvaluator(session: EnhancedDebugSession): Evaluator | undefined {
for (const f of this.engines) {
const evaluator = f.createEvaluator(session);
if (evaluator) {

View File

@@ -1,5 +1,5 @@
import { DataExtractorId } from "@hediet/debug-visualizer-data-extraction";
import { VsCodeDebugSession } from "../../VsCodeDebugger";
import { EnhancedDebugSession } from "../../debugger/EnhancedDebugSession";
import { EvaluationEngine, Evaluator } from "./EvaluationEngine";
import { Config, DebugAdapterConfig } from "../../Config";
import { GenericEvaluator } from "./GenericEvaluationEngine";
@@ -11,7 +11,7 @@ registerUpdateReconciler(module);
export class ConfiguredEvaluationEngine implements EvaluationEngine {
constructor(private readonly config: Config) {}
createEvaluator(session: VsCodeDebugSession): Evaluator | undefined {
createEvaluator(session: EnhancedDebugSession): Evaluator | undefined {
const config = this.config.getDebugAdapterConfig(session.session.type);
if (!config) {
return undefined;
@@ -22,7 +22,7 @@ export class ConfiguredEvaluationEngine implements EvaluationEngine {
class ConfiguredEvaluator extends GenericEvaluator {
constructor(
session: VsCodeDebugSession,
session: EnhancedDebugSession,
private readonly config: DebugAdapterConfig
) {
super(session);

View File

@@ -2,11 +2,11 @@ import {
DataExtractionResult,
DataExtractorId,
} from "@hediet/debug-visualizer-data-extraction";
import { VsCodeDebugSession } from "../../VsCodeDebugger";
import { FormattedMessage } from "../../contract";
import { EnhancedDebugSession } from "../../debugger/EnhancedDebugSession";
import { FormattedMessage } from "../../webviewContract";
export interface EvaluationEngine {
createEvaluator(session: VsCodeDebugSession): Evaluator | undefined;
createEvaluator(session: EnhancedDebugSession): Evaluator | undefined;
}
export interface Evaluator {

View File

@@ -2,21 +2,21 @@ import {
DataExtractionResult,
DataExtractorId,
} from "@hediet/debug-visualizer-data-extraction";
import { VsCodeDebugSession } from "../../VsCodeDebugger";
import { EnhancedDebugSession } from "../../debugger/EnhancedDebugSession";
import {
EvaluationEngine,
Evaluator,
EvaluationArgs,
} from "./EvaluationEngine";
import { parseEvaluationResultFromGenericDebugAdapter } from "./parseEvaluationResultFromGenericDebugAdapter";
import { FormattedMessage } from "../../contract";
import { FormattedMessage } from "../../webviewContract";
import { hotClass, registerUpdateReconciler } from "@hediet/node-reload";
registerUpdateReconciler(module);
@hotClass(module)
export class GenericEvaluationEngine implements EvaluationEngine {
createEvaluator(session: VsCodeDebugSession): Evaluator | undefined {
createEvaluator(session: EnhancedDebugSession): Evaluator | undefined {
return new GenericEvaluator(session);
}
}
@@ -24,7 +24,7 @@ export class GenericEvaluationEngine implements EvaluationEngine {
export class GenericEvaluator implements Evaluator {
public readonly languageId = "text";
constructor(private readonly session: VsCodeDebugSession) {}
constructor(private readonly session: EnhancedDebugSession) {}
public async evaluate({
expression,
@@ -51,6 +51,7 @@ export class GenericEvaluator implements Evaluator {
message: {
kind: "list",
items: [
"An error occurred while evaluating the expression:",
error.message,
`Used debug adapter: ${this.session.session.configuration.type}`,
{
@@ -65,7 +66,9 @@ export class GenericEvaluator implements Evaluator {
};
}
return parseEvaluationResultFromGenericDebugAdapter(reply.result);
return parseEvaluationResultFromGenericDebugAdapter(reply.result, {
debugAdapterType: this.session.session.configuration.type,
});
}
protected getFinalExpression(args: {
@@ -76,6 +79,7 @@ export class GenericEvaluator implements Evaluator {
}
protected getContext(): "watch" | "repl" {
return "watch";
// we will use "repl" as default so that results are not truncated.
return "repl";
}
}

View File

@@ -5,25 +5,27 @@ import {
getExpressionToInitializeDataExtractorApi,
DataExtractionResult,
} from "@hediet/debug-visualizer-data-extraction";
import { VsCodeDebugSession } from "../../VsCodeDebugger";
import { EnhancedDebugSession } from "../../debugger/EnhancedDebugSession";
import {
EvaluationEngine,
Evaluator,
EvaluationArgs,
} from "./EvaluationEngine";
import { FormattedMessage } from "../../contract";
import { FormattedMessage } from "../../webviewContract";
import { registerUpdateReconciler, hotClass } from "@hediet/node-reload";
registerUpdateReconciler(module);
@hotClass(module)
export class JsEvaluationEngine implements EvaluationEngine {
createEvaluator(session: VsCodeDebugSession): Evaluator | undefined {
createEvaluator(session: EnhancedDebugSession): Evaluator | undefined {
const supportedDebugAdapters = [
"node",
"node2",
"extensionHost",
"chrome",
"pwa-chrome",
"pwa-node",
];
if (supportedDebugAdapters.indexOf(session.session.type) !== -1) {
return new JsEvaluator(session);
@@ -35,7 +37,14 @@ export class JsEvaluationEngine implements EvaluationEngine {
class JsEvaluator implements Evaluator {
public readonly languageId = "javascript";
constructor(private readonly session: VsCodeDebugSession) {}
constructor(private readonly session: EnhancedDebugSession) {}
private getContext(): "copy" | "repl" {
if (this.session.session.type.startsWith("pwa-")) {
return "copy";
}
return "repl";
}
public async evaluate({
expression,
@@ -74,9 +83,13 @@ class JsEvaluator implements Evaluator {
const reply = await this.session.evaluate({
expression: wrappedExpr,
frameId,
context: this.getContext(),
});
const resultStr = reply.result;
const jsonData = resultStr.substr(1, resultStr.length - 2);
const jsonData =
this.getContext() === "copy"
? resultStr
: resultStr.substr(1, resultStr.length - 2);
const result = JSON.parse(jsonData) as DataResult;
if (result.kind === "NoExtractors") {
@@ -115,6 +128,7 @@ class JsEvaluator implements Evaluator {
await this.session.evaluate({
expression,
frameId,
context: this.getContext(),
});
return true;

View File

@@ -1,11 +1,16 @@
import {
DataExtractionResult,
isExtractedData,
isVisualizationData,
} from "@hediet/debug-visualizer-data-extraction";
import { FormattedMessage } from "../../contract";
import { FormattedMessage } from "../../webviewContract";
export interface ParseEvaluationResultContext {
debugAdapterType: string;
}
export function parseEvaluationResultFromGenericDebugAdapter(
resultText: string
resultText: string,
context: ParseEvaluationResultContext
):
| { kind: "data"; result: DataExtractionResult }
| { kind: "error"; message: FormattedMessage } {
@@ -26,16 +31,16 @@ export function parseEvaluationResultFromGenericDebugAdapter(
// Just in case no quoting is done.
jsonData2 = jsonData;
}
resultObj = parseJson(jsonData2);
resultObj = parseJson(jsonData2, context);
} catch (e) {
// in case of C++: `"{ \"kind\": { ... }, \"text\": \"some\\ntext\" }"`
const str = parseJson(jsonData);
const str = parseJson(jsonData, context);
// str is now `{ "kind": { ... }, "text": "some\ntext" }"`
resultObj = parseJson(str);
resultObj = parseJson(str, context);
// result is now { kind: { ... }, text: "some\ntext" }
}
if (!isExtractedData(resultObj)) {
if (!isVisualizationData(resultObj)) {
return {
kind: "error",
message: {
@@ -85,7 +90,7 @@ function isEnclosedWith(str: string, char: string): boolean {
return str.startsWith(char) && str.endsWith(char);
}
function parseJson(str: string) {
function parseJson(str: string, context: ParseEvaluationResultContext) {
try {
return JSON.parse(str);
} catch (error) {
@@ -104,6 +109,7 @@ function parseJson(str: string) {
},
],
},
`Used debug adapter: ${context.debugAdapterType}`,
],
});
}

View File

@@ -1,5 +1,5 @@
import { DataExtractorId } from "@hediet/debug-visualizer-data-extraction";
import { DataExtractionState, CompletionItem } from "../contract";
import { DataExtractionState, CompletionItem } from "../webviewContract";
export interface EvaluationWatchService {
createEvaluationWatcher(

View File

@@ -6,9 +6,9 @@ import {
import { observable, autorun, action } from "mobx";
import { Disposable } from "@hediet/std/disposable";
import { DataExtractorId } from "@hediet/debug-visualizer-data-extraction";
import { DataExtractionState, CompletionItem } from "../contract";
import { DataExtractionState, CompletionItem } from "../webviewContract";
import { hotClass } from "@hediet/node-reload";
import { VsCodeDebuggerView } from "../VsCodeDebugger";
import { VsCodeDebuggerView } from "../debugger/VsCodeDebuggerView";
import { EvaluationEngine } from "./EvaluationEngine/EvaluationEngine";
@hotClass(module)

View File

@@ -1,209 +0,0 @@
import { Disposable } from "@hediet/std/disposable";
import { debug, DebugSession } from "vscode";
import { EventEmitter } from "@hediet/std/events";
import { CompletionItem } from "./contract";
import { observable, runInAction } from "mobx";
export class VsCodeDebugger {
public readonly dispose = Disposable.fn();
private readonly sessions = new Map<DebugSession, VsCodeDebugSession>();
private readonly _onDidStartDebugSession = new EventEmitter<{
session: VsCodeDebugSession;
}>();
public readonly onDidStartDebugSession = this._onDidStartDebugSession.asEvent();
public getDebugSession(session: DebugSession): VsCodeDebugSession {
return this.sessions.get(session)!;
}
constructor() {
this.dispose.track([
debug.onDidStartDebugSession(session => {
const e = this.sessions.get(session)!;
this._onDidStartDebugSession.emit({ session: e });
}),
debug.onDidTerminateDebugSession(session => {
const e = this.sessions.get(session)!;
// TODO add proper event
this.sessions.delete(session);
}),
debug.registerDebugAdapterTrackerFactory("*", {
createDebugAdapterTracker: session => {
const extendedSession = new VsCodeDebugSession(session);
this.sessions.set(session, extendedSession);
return {
onWillReceiveMessage: msg => {
console.log(msg.type, msg.event, msg);
},
onDidSendMessage: async msg => {
type Message =
| StoppedEvent
| ThreadsResponse
| ContinueLikeResponse;
interface ContinueLikeResponse {
type: "response";
command:
| "continue"
| "stepIn"
| "stepOut"
| "next";
}
interface StoppedEvent {
type: "event";
event: "stopped";
body: {
threadId: number;
};
}
interface ThreadsResponse {
type: "response";
command: "threads";
success: boolean;
body: {
threads: ThreadInfo[];
};
}
interface ThreadInfo {
id: number;
name: string;
}
const m = msg as Message;
if (m.type === "event") {
if (m.event === "stopped") {
const threadId = m.body.threadId;
const r = await extendedSession[
"getStackTrace"
]({
threadId,
startFrame: 0,
levels: 1,
});
extendedSession["activeStackFrame"] =
r.stackFrames.length > 0
? r.stackFrames[0].id
: undefined;
}
} else if (m.type === "response") {
if (
m.command === "continue" ||
m.command === "next" ||
m.command === "stepIn" ||
m.command === "stepOut"
) {
extendedSession[
"activeStackFrame"
] = undefined;
}
}
},
};
},
}),
]);
}
}
interface StackFrame {
id: number;
name: string;
}
export class VsCodeDebugSession {
@observable protected activeStackFrame: number | undefined;
constructor(public readonly session: DebugSession) {}
protected async getStackTrace(args: {
threadId: number;
startFrame?: number;
levels?: number;
}): Promise<{ totalFrames?: number; stackFrames: StackFrame[] }> {
try {
const reply = (await this.session.customRequest("stackTrace", {
threadId: args.threadId,
levels: args.levels,
startFrame: args.startFrame || 0,
})) as { totalFrames?: number; stackFrames: StackFrame[] };
return reply;
} catch (e) {
console.error(e);
throw e;
}
}
public async getCompletions(args: {
text: string;
column: number;
frameId: number | undefined;
}): Promise<CompletionItem[]> {
try {
const reply = await this.session.customRequest("completions", {
text: args.text,
frameId: args.frameId,
column: args.column,
});
if (!reply) {
return [];
}
return reply.targets;
} catch (error) {
console.error(error);
return [];
}
}
public async evaluate(args: {
expression: string;
frameId: number | undefined;
context?: "watch" | "repl";
}): Promise<{ result: string }> {
const reply = await this.session.customRequest("evaluate", {
expression: args.expression,
frameId: args.frameId,
context: args.context || "watch",
});
return { result: reply.result };
}
}
export class VsCodeDebuggerView {
public readonly dispose = Disposable.fn();
@observable private _activeDebugSession: VsCodeDebugSession | undefined;
public get activeDebugSession(): VsCodeDebugSession | undefined {
return this._activeDebugSession;
}
public get activeFrameId(): number | undefined {
if (!this._activeDebugSession) {
return undefined;
} else {
return this._activeDebugSession["activeStackFrame"];
}
}
constructor(private vsCodeDebugger: VsCodeDebugger) {
this.dispose.track(
debug.onDidChangeActiveDebugSession(activeSession => {
runInAction("Update active debug session", () => {
if (!activeSession) {
this._activeDebugSession = undefined;
} else {
const s = this.vsCodeDebugger.getDebugSession(
activeSession
);
this._activeDebugSession = s;
}
});
})
);
}
}

View File

@@ -0,0 +1,70 @@
import { DebugSession } from "vscode";
import { CompletionItem } from "../webviewContract";
import { observable } from "mobx";
export class EnhancedDebugSession {
@observable protected activeStackFrame: number | undefined;
constructor(public readonly session: DebugSession) {}
protected async getStackTrace(args: {
threadId: number;
startFrame?: number;
levels?: number;
}): Promise<{ totalFrames?: number; stackFrames: StackFrame[] }> {
try {
const reply = (await this.session.customRequest("stackTrace", {
threadId: args.threadId,
levels: args.levels,
startFrame: args.startFrame || 0,
})) as { totalFrames?: number; stackFrames: StackFrame[] };
return reply;
} catch (e) {
console.error(e);
throw e;
}
}
public async getCompletions(args: {
text: string;
column: number;
frameId: number | undefined;
}): Promise<CompletionItem[]> {
try {
const reply = await this.session.customRequest("completions", {
text: args.text,
frameId: args.frameId,
column: args.column,
});
if (!reply) {
return [];
}
return reply.targets;
} catch (error) {
console.error(error);
return [];
}
}
/**
* Evaluates the given expression.
* If context is "watch", long results are usually shortened.
*/
public async evaluate(args: {
expression: string;
frameId: number | undefined;
context: "watch" | "repl" | "copy";
}): Promise<{ result: string }> {
const reply = await this.session.customRequest("evaluate", {
expression: args.expression,
frameId: args.frameId,
context: args.context,
});
return { result: reply.result };
}
}
interface StackFrame {
id: number;
name: string;
}

View File

@@ -0,0 +1,116 @@
import { Disposable } from "@hediet/std/disposable";
import { debug, DebugSession } from "vscode";
import { EventEmitter } from "@hediet/std/events";
import { runInAction } from "mobx";
import { EnhancedDebugSession } from "./EnhancedDebugSession";
/**
* Decorates DebugSession instances and emits an event for new debug sessions.
*/
export class VsCodeDebugger {
public readonly dispose = Disposable.fn();
private readonly sessions = new Map<DebugSession, EnhancedDebugSession>();
private readonly _onDidStartDebugSession = new EventEmitter<{
session: EnhancedDebugSession;
}>();
public readonly onDidStartDebugSession = this._onDidStartDebugSession.asEvent();
public getEnhancedDebugSession(
session: DebugSession
): EnhancedDebugSession {
let result = this.sessions.get(session);
if (!result) {
result = new EnhancedDebugSession(session);
this.sessions.set(session, result);
}
return result;
}
constructor() {
this.dispose.track([
debug.onDidStartDebugSession(session => {
const e = this.sessions.get(session)!;
this._onDidStartDebugSession.emit({ session: e });
}),
debug.onDidTerminateDebugSession(session => {
this.sessions.delete(session);
}),
debug.registerDebugAdapterTrackerFactory("*", {
createDebugAdapterTracker: session => {
const extendedSession = this.getEnhancedDebugSession(
session
);
return {
onDidSendMessage: async msg => {
type Message =
| StoppedEvent
| ThreadsResponse
| ContinueLikeResponse;
interface ContinueLikeResponse {
type: "response";
command:
| "continue"
| "stepIn"
| "stepOut"
| "next";
}
interface StoppedEvent {
type: "event";
event: "stopped";
body: {
threadId: number;
};
}
interface ThreadsResponse {
type: "response";
command: "threads";
success: boolean;
body: {
threads: ThreadInfo[];
};
}
interface ThreadInfo {
id: number;
name: string;
}
const m = msg as Message;
if (m.type === "event") {
if (m.event === "stopped") {
const threadId = m.body.threadId;
const r = await extendedSession[
"getStackTrace"
]({
threadId,
startFrame: 0,
levels: 1,
});
extendedSession["activeStackFrame"] =
r.stackFrames.length > 0
? r.stackFrames[0].id
: undefined;
}
} else if (m.type === "response") {
if (
m.command === "continue" ||
m.command === "next" ||
m.command === "stepIn" ||
m.command === "stepOut"
) {
extendedSession[
"activeStackFrame"
] = undefined;
}
}
},
};
},
}),
]);
}
}

View File

@@ -0,0 +1,48 @@
import { Disposable } from "@hediet/std/disposable";
import { debug, DebugSession } from "vscode";
import { observable, action } from "mobx";
import { VsCodeDebugger } from "./VsCodeDebugger";
import { EnhancedDebugSession } from "./EnhancedDebugSession";
/**
* Tracks the currently active debug session and its currently active stack frame.
* This is currently all faked. A proper API would be nice.
*/
export class VsCodeDebuggerView {
public readonly dispose = Disposable.fn();
@observable private _activeDebugSession: EnhancedDebugSession | undefined;
public get activeDebugSession(): EnhancedDebugSession | undefined {
return this._activeDebugSession;
}
public get activeFrameId(): number | undefined {
if (!this._activeDebugSession) {
return undefined;
} else {
return this._activeDebugSession["activeStackFrame"];
}
}
constructor(private vsCodeDebugger: VsCodeDebugger) {
this.dispose.track(
debug.onDidChangeActiveDebugSession(activeSession => {
this.updateActiveDebugSession(activeSession);
})
);
this.updateActiveDebugSession(debug.activeDebugSession);
}
@action
private updateActiveDebugSession(activeSession: DebugSession | undefined) {
if (!activeSession) {
this._activeDebugSession = undefined;
} else {
const s = this.vsCodeDebugger.getEnhancedDebugSession(
activeSession
);
this._activeDebugSession = s;
}
}
}

View File

@@ -12,10 +12,11 @@ if (process.env.HOT_RELOAD) {
}
registerUpdateReconciler(module);
import { WebViews } from "./WebViews";
import { Server } from "./Server";
import { InternalWebviewManager } from "./webview/InternalWebviewManager";
import { WebviewServer } from "./webview/WebviewServer";
import { Config } from "./Config";
import { VsCodeDebugger, VsCodeDebuggerView } from "./VsCodeDebugger";
import { VsCodeDebugger } from "./debugger/VsCodeDebugger";
import { VsCodeDebuggerView } from "./debugger/VsCodeDebuggerView";
import { EvaluationWatchServiceImpl } from "./EvaluationWatchService";
import {
ComposedEvaluationEngine,
@@ -24,11 +25,18 @@ import {
ConfiguredEvaluationEngine,
} from "./EvaluationWatchService/EvaluationEngine";
export function activate(context: ExtensionContext) {
context.subscriptions.push(
hotRequireExportedFn(module, Extension, Extension => new Extension())
);
}
export function deactivate() {}
export class Extension {
public readonly dispose = Disposable.fn();
private readonly config = new Config();
private readonly debugger = this.dispose.track(new VsCodeDebugger());
private readonly debuggerView = this.dispose.track(
new VsCodeDebuggerView(this.debugger)
@@ -43,8 +51,10 @@ export class Extension {
])
);
private readonly server = new Server(this.dataSource, this.config);
private readonly views = this.dispose.track(new WebViews(this.server));
private readonly server = new WebviewServer(this.dataSource, this.config);
private readonly views = this.dispose.track(
new InternalWebviewManager(this.server, this.config)
);
constructor() {
if (getReloadCount(module) > 0) {
@@ -79,10 +89,10 @@ export class Extension {
}
const connections = [...this.server.connections.values()];
if (connections.length > 0) {
const latestConnection =
connections[connections.length - 1];
const latestConnection =
connections[connections.length - 1];
if (latestConnection) {
latestConnection.setExpression(selectedText);
} else {
this.views.createNew(selectedText);
@@ -92,11 +102,3 @@ export class Extension {
);
}
}
export function activate(context: ExtensionContext) {
context.subscriptions.push(
hotRequireExportedFn(module, Extension, Extension => new Extension())
);
}
export function deactivate() {}

View File

@@ -0,0 +1,120 @@
import { Uri, workspace, ConfigurationTarget, Disposable } from "vscode";
import { fromResource } from "mobx-utils";
import { computed, runInAction } from "mobx";
import { EventEmitter } from "@hediet/std/events";
export interface Serializer<T> {
deserialize: (val: any) => T;
serializer: (val: T) => any;
}
export function serializerWithDefault<T>(defaultValue: T): Serializer<T> {
return {
deserialize: val => (val === undefined ? defaultValue : val),
serializer: val => val,
};
}
export class VsCodeSetting<T> {
public get T(): T {
throw new Error();
}
public readonly serializer: Serializer<T>;
public readonly scope: Uri | undefined;
private readonly settingResource: VsCodeSettingResource;
public constructor(
public readonly id: string,
options: {
serializer?: Serializer<T>;
scope?: Uri;
} = {}
) {
this.scope = options.scope;
this.serializer = options.serializer || {
deserialize: val => val,
serializer: val => val,
};
this.settingResource = new VsCodeSettingResource(this.id, this.scope);
}
public get(): T {
const result = this.settingResource.value;
return this.serializer.deserialize(result);
}
public async set(value: T): Promise<void> {
const value2 = this.serializer.serializer(value);
const c = workspace.getConfiguration(undefined, this.scope);
const result = c.inspect(this.id);
let target: ConfigurationTarget;
if (
result &&
[result.workspaceFolderValue].some(i => i !== undefined)
) {
target = ConfigurationTarget.WorkspaceFolder;
}
if (result && [result.workspaceValue].some(i => i !== undefined)) {
target = ConfigurationTarget.Workspace;
} else {
target = ConfigurationTarget.Global;
}
await c.update(this.id, value2, target);
}
}
class VsCodeSettingResource {
public static onConfigChange = new EventEmitter();
private subscription: Disposable | undefined;
private readonly resource = fromResource<any>(
update => {
update(this.readValue());
this.subscription = VsCodeSettingResource.onConfigChange.sub(() => {
update(this.readValue());
});
},
() => this.subscription!.dispose(),
this.readValue()
);
constructor(
private readonly id: string,
private readonly scope: Uri | undefined
) {}
private readValue(): any {
const val = workspace
.getConfiguration(undefined, this.scope)
.get(this.id);
return val;
}
/**
* This improves change detection.
*/
private readonly stringifiedSettingValue = computed(
() => JSON.stringify(this.resource.current()),
{
name: `VsCodeSettingResource[${this.id}].value`,
context: this,
}
);
public get value() {
const v = this.stringifiedSettingValue.get();
if (v === undefined) {
return undefined;
}
return JSON.parse(v);
}
}
workspace.onDidChangeConfiguration(() => {
runInAction("Update Configuration", () => {
VsCodeSettingResource.onConfigChange.emit();
});
});

View File

@@ -1,15 +1,23 @@
import { window, ViewColumn, WebviewPanel } from "vscode";
import { Server } from "./Server";
import { WebviewServer } from "./WebviewServer";
import { Disposable } from "@hediet/std/disposable";
import { WindowWithWebviewData } from "../webviewContract";
import { Config } from "../Config";
export const debugVisualizer = "debugVisualizer";
export class WebViews {
private readonly debugVisualizations = new Map<WebviewPanel, WebView>();
export class InternalWebviewManager {
private readonly openedWebviews = new Map<
WebviewPanel,
DebugVisualizerWebview
>();
public readonly dispose = Disposable.fn();
constructor(private readonly server: Server) {
constructor(
private readonly server: WebviewServer,
private readonly config: Config
) {
this.dispose.track(
window.registerWebviewPanelSerializer(debugVisualizer, {
deserializeWebviewPanel: async (panel, state) => {
@@ -20,7 +28,7 @@ export class WebViews {
this.dispose.track({
dispose: () => {
for (const panel of this.debugVisualizations.keys()) {
for (const panel of this.openedWebviews.keys()) {
panel.dispose();
}
},
@@ -28,40 +36,54 @@ export class WebViews {
}
public createNew(expression: string | undefined = undefined) {
const panel = window.createWebviewPanel(
const webviewPanel = window.createWebviewPanel(
debugVisualizer,
"Debug Visualizer",
ViewColumn.Two,
{ enableScripts: true }
{
enableScripts: true,
retainContextWhenHidden: true,
portMapping: [
{
webviewPort: this.server.port,
extensionHostPort: this.server.port,
},
],
}
);
this.setupView(panel, expression);
this.initializeView(webviewPanel, expression);
}
public restore(webviewPanel: WebviewPanel) {
this.setupView(webviewPanel);
private restore(webviewPanel: WebviewPanel) {
this.initializeView(webviewPanel);
}
private setupView(
private initializeView(
webviewPanel: WebviewPanel,
expression: string | undefined = undefined
) {
webviewPanel.webview.html = getHtml(this.server, expression);
const view = new WebView(webviewPanel);
this.debugVisualizations.set(webviewPanel, view);
webviewPanel.webview.html = getDebugVisualizerWebviewHtml(
this.server,
expression,
this.config
);
const view = new DebugVisualizerWebview(webviewPanel);
this.openedWebviews.set(webviewPanel, view);
webviewPanel.onDidDispose(() => {
this.debugVisualizations.delete(webviewPanel);
this.openedWebviews.delete(webviewPanel);
});
}
}
export class WebView {
export class DebugVisualizerWebview {
constructor(private readonly webviewPanel: WebviewPanel) {}
}
export function getHtml(
server: Server,
expression: string | undefined = undefined
function getDebugVisualizerWebviewHtml(
server: WebviewServer,
initialExpression: string | undefined = undefined,
config: Config
) {
const isDev = !!process.env.USE_DEV_UI;
return `
@@ -77,12 +99,15 @@ export function getHtml(
</head>
<body>
<script>
window.webViewData = ${JSON.stringify({
serverSecret: server.secret,
serverPort: server.port,
publicPath: server.publicPath,
expression,
})};
Object.assign(window, ${JSON.stringify({
webviewData: {
serverSecret: server.secret,
serverPort: server.port,
publicPath: server.publicPath,
expression: initialExpression,
theme: config.theme,
},
} as WindowWithWebviewData)});
const api = window.VsCodeApi = acquireVsCodeApi();
window.addEventListener('message', event => {
if (event.source === window.frames[0]) {
@@ -100,10 +125,13 @@ export function getHtml(
${
isDev
? `<iframe sandbox="allow-top-navigation allow-scripts allow-same-origin allow-popups allow-pointer-lock allow-forms" src="${server.getIndexUrl(
{ mode: "webViewIFrame", expression }
? `<iframe sandbox="allow-top-navigation allow-scripts allow-same-origin allow-popups allow-pointer-lock allow-forms" src="${server.getWebviewPageUrl(
{
mode: "webviewIFrame",
expression: initialExpression,
}
)}"></iframe>`
: `<script type="text/javascript" src="${server.mainBundleUrl}"></script>`
: `<script type="text/javascript" src="${server.webviewBundleUrl}"></script>`
}
</body>
</html>

View File

@@ -1,28 +1,28 @@
import { Disposable } from "@hediet/std/disposable";
import { debugVisualizerUIContract } from "./contract";
import { webviewContract } from "../webviewContract";
import { ConsoleRpcLogger, RequestHandlingError } from "@hediet/typed-json-rpc";
import {
EvaluationWatcher,
EvaluationWatchService,
} from "./EvaluationWatchService";
} from "../EvaluationWatchService";
import { WebSocketStream } from "@hediet/typed-json-rpc-websocket";
import { observable, autorun } from "mobx";
import { Server } from "./Server";
import { WebviewServer } from "./WebviewServer";
import * as open from "open";
import chromeLauncher = require("chrome-launcher");
import { Config } from "./Config";
import { Config } from "../Config";
export class ClientConnection {
export class WebviewConnection {
public readonly dispose = Disposable.fn();
@observable
private watcher: EvaluationWatcher | undefined = undefined;
private readonly client: typeof debugVisualizerUIContract["TClientInterface"];
private readonly client: typeof webviewContract["TClientInterface"];
constructor(
evaluationWatchService: EvaluationWatchService,
stream: WebSocketStream,
server: Server,
server: WebviewServer,
config: Config,
serverSecret: string
) {
@@ -34,10 +34,7 @@ export class ClientConnection {
}
}
const {
client,
channel,
} = debugVisualizerUIContract.registerServerToStream(
const { client, channel } = webviewContract.registerServerToStream(
stream,
new ConsoleRpcLogger(),
{
@@ -76,7 +73,7 @@ export class ClientConnection {
openInBrowser: async ({}) => {
throwIfNotAuthenticated();
const url = server.getIndexUrl({
const url = server.getWebviewPageUrl({
mode: "standalone",
expression: this.watcher
? this.watcher.expression
@@ -141,6 +138,10 @@ export class ClientConnection {
public setExpression(expression: string) {
this.client.setExpression({ expression });
}
public setTheme(theme: "light" | "dark"): void {
this.client.setTheme({ theme });
}
}
async function launchChrome(url: string): Promise<boolean> {

View File

@@ -1,22 +1,27 @@
import { WebSocketStream } from "@hediet/typed-json-rpc-websocket";
import { AddressInfo } from "net";
import WebSocket = require("ws");
import { ClientConnection } from "./ClientConnection";
import { WebviewConnection } from "./WebviewConnection";
import * as express from "express";
import * as http from "http";
import * as serveStatic from "serve-static";
import { Config } from "./Config";
import { Config } from "../Config";
import cryptoRandomString = require("crypto-random-string");
import { distPath } from "debug-visualizer-webview";
import { EvaluationWatchService } from "./EvaluationWatchService/EvaluationWatchService";
import { EvaluationWatchService } from "../EvaluationWatchService/EvaluationWatchService";
import { URLSearchParams } from "url";
import { autorun, reaction } from "mobx";
export class Server {
private server: http.Server;
public readonly secret = cryptoRandomString({ length: 20 });
export class WebviewServer {
private readonly server: http.Server;
public readonly secret = cryptoRandomString({ length: 30 });
public readonly connections = new Set<ClientConnection>();
public readonly connections = new Set<WebviewConnection>();
constructor(dataSource: EvaluationWatchService, config: Config) {
constructor(
dataSource: EvaluationWatchService,
private readonly config: Config
) {
const app = express();
app.use(serveStatic(distPath));
@@ -32,7 +37,7 @@ export class Server {
const wss = new WebSocket.Server({ server: this.server });
wss.on("connection", async ws => {
const stream = new WebSocketStream(ws);
const c = new ClientConnection(
const c = new WebviewConnection(
dataSource,
stream,
this,
@@ -43,27 +48,42 @@ export class Server {
await stream.onClosed;
this.connections.delete(c);
});
reaction(
() => config.theme,
theme => {
for (const c of this.connections) {
c.setTheme(theme);
}
}
);
}
public getIndexUrl(args: {
public getWebviewPageUrl(args: {
expression?: string;
mode: "standalone" | "webViewIFrame";
mode: "standalone" | "webviewIFrame";
}): string {
const port = process.env.USE_DEV_UI ? 8080 : this.port;
const expr =
args.expression !== undefined
? `&expression=${encodeURIComponent(args.expression)}`
: "";
const inWebView =
args.mode === "standalone" ? "" : "&mode=webViewIFrame";
return `http://localhost:${port}/index.html?serverPort=${this.port}&serverSecret=${this.secret}${inWebView}${expr}`;
const params: Record<string, string> = {
serverPort: this.port.toString(),
serverSecret: this.secret,
mode: args.mode,
theme: this.config.theme,
};
if (args.expression !== undefined) {
params.expression = args.expression;
}
return `http://localhost:${port}/index.html?${new URLSearchParams(
params
).toString()}`;
}
public get publicPath(): string {
return `http://localhost:${this.port}/`;
}
public get mainBundleUrl(): string {
public get webviewBundleUrl(): string {
return `${this.publicPath}main.js`;
}

View File

@@ -9,6 +9,83 @@ import {
DataExtractorId,
} from "@hediet/debug-visualizer-data-extraction";
interface WebviewConfig {
serverSecret: string;
serverPort: number;
publicPath: string;
expression?: string;
theme: "light" | "dark";
}
export interface WebviewUrlParams {
serverPort: string;
serverSecret: string;
mode: "standalone" | "webviewIFrame";
expression: string;
theme: "light" | "dark";
}
export interface WindowWithWebviewData {
webviewData?: WebviewConfig;
}
export const webviewContract = contract({
client: {
updateState: notificationContract({
params: types.type({
newState: unchecked<DataExtractionState>(),
}),
}),
updateLanguageId: notificationContract({
params: types.type({
languageId: types.union([types.null, types.string]),
}),
}),
setExpression: requestContract({
params: types.type({
expression: types.string,
}),
}),
setTheme: notificationContract({
params: types.type({
theme: types.union([
types.literal("light"),
types.literal("dark"),
]),
}),
}),
},
server: {
authenticate: requestContract({
params: types.type({
secret: types.string,
}),
}),
setPreferredDataExtractor: notificationContract({
params: types.type({
dataExtractorId: unchecked<DataExtractorId>(),
}),
}),
setExpression: notificationContract({
params: types.type({
newExpression: types.string,
}),
}),
refresh: notificationContract({}),
openInBrowser: notificationContract({}),
getCompletions: requestContract({
params: types.type({
text: types.string,
column: types.number,
}),
result: types.type({
completions: types.array(unchecked<CompletionItem>()),
}),
}),
},
});
function unchecked<T>(): types.Type<T, any, unknown> {
return new types.Type<T, T, unknown>(
"unchecked",
@@ -92,52 +169,3 @@ export interface CompletionItem {
*/
length?: number;
}
export const debugVisualizerUIContract = contract({
client: {
updateState: notificationContract({
params: types.type({
newState: unchecked<DataExtractionState>(),
}),
}),
updateLanguageId: notificationContract({
params: types.type({
languageId: types.union([types.null, types.string]),
}),
}),
setExpression: requestContract({
params: types.type({
expression: types.string,
}),
}),
},
server: {
authenticate: requestContract({
params: types.type({
secret: types.string,
}),
}),
setPreferredDataExtractor: notificationContract({
params: types.type({
dataExtractorId: unchecked<DataExtractorId>(),
}),
}),
setExpression: notificationContract({
params: types.type({
newExpression: types.string,
}),
}),
refresh: notificationContract({}),
openInBrowser: notificationContract({}),
getCompletions: requestContract({
params: types.type({
text: types.string,
column: types.number,
}),
result: types.type({
completions: types.array(unchecked<CompletionItem>()),
}),
}),
},
});

View File

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

View File

@@ -21,6 +21,7 @@ module.exports = {
"@hediet/debug-visualizer-data-extraction":
"@hediet/debug-visualizer-data-extraction",
"debug-visualizer-webview": "debug-visualizer-webview",
fsevents: "require('fsevents')"
},
resolve: {
extensions: [".ts", ".js"],

File diff suppressed because it is too large Load Diff

View File

@@ -3,15 +3,15 @@
"workspaces": [
"./data-extraction",
"./extension",
"./webview",
"./visualization",
"./visualization-playground"
"./webview"
],
"scripts": {
"build": "yarn build-data-extraction && yarn build-extension && yarn build-visualization && yarn build-ui",
"build": "yarn build-data-extraction && yarn build-ui && yarn build-extension",
"build-data-extraction": "yarn workspace @hediet/debug-visualizer-data-extraction build",
"build-extension": "yarn workspace debug-visualizer build",
"build-visualization": "yarn workspace @hediet/visualization build",
"build-ui": "yarn workspace debug-visualizer-webview build"
"build-ui": "yarn workspace debug-visualizer-webview build",
"build-extension": "yarn workspace debug-visualizer build"
},
"devDependencies": {
"prettier": "^1.19.1"
}
}

View File

@@ -1,52 +0,0 @@
{
"name": "visualization-playground",
"version": "0.0.1",
"license": "GPL-3.0",
"scripts": {
"dev": "webpack-dev-server --hot --port 8090",
"dev-fast": "webpack-dev-server --hot --port 8090 --fast",
"build": "webpack",
"pub": "gh-pages -d dist"
},
"dependencies": {
"@blueprintjs/core": "^3.23.1",
"@hediet/std": "^0.6.0",
"blueprintjs": "^0.0.8",
"classnames": "^2.2.6",
"mobx": "^5.15.4",
"mobx-react": "^6.1.8",
"monaco-editor": "^0.20.0",
"popper.js": "^1.16.1",
"react": "^16.12.0",
"react-dom": "^16.12.0",
"react-measure": "^2.3.0",
"@hediet/debug-visualizer-data-extraction": "*",
"react-autosize-textarea": "^7.0.0"
},
"devDependencies": {
"gh-pages": "^2.2.0",
"@types/html-webpack-plugin": "^3.2.2",
"@types/react-measure": "^2.0.6",
"@types/webpack": "^4.41.6",
"@types/classnames": "^2.2.9",
"@types/react": "^16.9.22",
"@types/react-dom": "^16.9.5",
"clean-webpack-plugin": "^3.0.0",
"css-loader": "^3.4.2",
"file-loader": "^5.1.0",
"fork-ts-checker-webpack-plugin": "^4.0.4",
"html-webpack-plugin": "^3.2.0",
"monaco-editor-webpack-plugin": "^1.9.0",
"node-sass": "^4.13.1",
"raw-loader": "^4.0.0",
"sass": "^1.25.0",
"sass-loader": "^8.0.2",
"style-loader": "^1.1.3",
"ts-loader": "^6.2.1",
"ts-node": "^8.6.2",
"typescript": "^3.8.2",
"webpack": "^4.41.6",
"webpack-cli": "^3.3.11",
"webpack-dev-server": "^3.10.3"
}
}

View File

@@ -1,45 +0,0 @@
import * as React from "react";
import { Button, Spinner } from "@blueprintjs/core";
import { observable, action } from "mobx";
import { observer } from "mobx-react";
import { Model } from "../model/Model";
import classnames = require("classnames");
import { knownVisualizations } from "@hediet/visualization";
import { CommonDataType } from "@hediet/debug-visualizer-data-extraction";
import { LightExpressionInput } from "./LightExpressionInput";
@observer
export class GUI extends React.Component<{ model: Model }> {
render() {
const m = this.props.model;
return (
<div
className="component-GUI"
style={{ display: "flex", flexDirection: "column" }}
>
<div className="part-header">
<LightExpressionInput model={m.jsonExpression} />
</div>
<div
className="part-visualization"
style={{ minHeight: 0, flex: 1 }}
>
{this.renderVisualization()}
</div>
</div>
);
}
renderVisualization() {
const m = this.props.model;
if (!m.visualizations) {
return <div>No/Invalid Data</div>;
}
if (!m.visualizations.visualization) {
return <div>Data cannot be visualized</div>;
}
return m.visualizations.visualization.render();
}
}

View File

@@ -1,76 +0,0 @@
import * as React from "react";
import { observer } from "mobx-react";
import { observable, action } from "mobx";
import TextareaAutosize from "react-autosize-textarea";
export class ExpressionModel {
@observable
private _value: string;
constructor(initialValue: string) {
this._value = initialValue;
}
public get value(): string {
return this._value;
}
public setValue(newValue: string): void {
this._value = newValue;
}
}
@observer
export class LightExpressionInput extends React.Component<{
model: ExpressionModel;
}> {
@observable localText: string | undefined = undefined;
get text(): string {
if (this.localText !== undefined) {
return this.localText;
}
return this.props.model.value;
}
render() {
return (
<div>
<TextareaAutosize
value={this.text}
spellCheck={false}
onChange={e => (this.localText = e.currentTarget.value)}
onKeyDown={e => {
if (e.keyCode == 9 || e.which == 9) {
// enable tab
const target = e.currentTarget;
e.preventDefault();
var s = target.selectionStart;
target.value =
target.value.substring(
0,
target.selectionStart
) +
"\t" +
target.value.substring(target.selectionEnd);
target.selectionEnd = s + 1;
}
}}
style={{
width: "100%",
resize: "none",
}}
onBlur={this.handleBlur}
/>
</div>
);
}
@action.bound
private handleBlur() {
if (this.localText !== undefined) {
this.props.model.setValue(this.localText);
}
this.localText = undefined;
}
}

View File

@@ -1,35 +0,0 @@
import * as React from "react";
import * as ReactDOM from "react-dom";
import "./style.scss";
import { Model } from "./model/Model";
import Components = require("./components/GUI");
const model = new Model();
if (model.theme === "light") {
require("./vscode-light.scss");
} else {
require("./vscode-dark.scss");
}
function render(target: HTMLDivElement) {
const c = require("./components/GUI") 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/GUI", () => {
render(target);
});
}

View File

@@ -1,95 +0,0 @@
import { observable, action, computed, when } from "mobx";
import {
Visualization,
VisualizationId,
knownVisualizations,
} from "@hediet/visualization";
import { Disposable } from "@hediet/std/disposable";
import { ExpressionModel } from "../components/LightExpressionInput";
import { ExtractedData } from "@hediet/debug-visualizer-data-extraction";
import { QueryController } from "./QueryController";
export class Model {
public readonly dispose = Disposable.fn();
@observable
public theme: "dark" | "light" = "light";
@observable private preferredVisualizationId:
| VisualizationId
| undefined = undefined;
@action
public setPreferredVisualizationId(id: VisualizationId) {
this.preferredVisualizationId = id;
}
public jsonExpression = new ExpressionModel(
JSON.stringify(
{
kind: { graph: true },
nodes: [
{ id: "1", label: "1" },
{ id: "2", label: "2" },
],
edges: [
{
from: "1",
to: "2",
},
],
},
undefined,
4
)
);
get state():
| { kind: "data"; data: ExtractedData }
| { kind: "parseError" } {
const json = this.jsonExpression.value;
let data: ExtractedData;
try {
data = JSON.parse(json);
} catch (e) {
return {
kind: "parseError",
};
}
return {
kind: "data",
data,
};
}
@computed get visualizations():
| {
visualization: Visualization | undefined;
allVisualizations: Visualization[];
}
| undefined {
if (this.state.kind === "data") {
const vis = knownVisualizations.getBestVisualization(
this.state.data,
this.preferredVisualizationId
);
return vis;
} else {
return undefined;
}
}
private readonly q = new QueryController(this);
constructor() {
const url = new URL(window.location.href);
const theme = url.searchParams.get("theme");
if (theme && theme === "dark") {
this.theme = "dark";
} else {
this.theme = "light";
}
}
}

View File

@@ -1,18 +0,0 @@
import * as monaco from "monaco-editor";
import { Model } from "./Model";
import { Disposable } from "@hediet/std/disposable";
import { autorun } from "mobx";
export class MonacoBridge {
public readonly dispose = Disposable.fn();
constructor(private readonly model: Model) {
this.dispose.track({
dispose: autorun(() => {
monaco.editor.setTheme(
model.theme === "light" ? "vs-light" : "vs-dark"
);
}),
});
}
}

View File

@@ -1,26 +0,0 @@
import { Model } from "./Model";
import { autorun } from "mobx";
export class QueryController {
constructor(private readonly model: Model) {
this.loadFromQuery();
autorun(() => {
this.updateQuery();
});
}
loadFromQuery() {
const url = new URL(window.location.href);
const val = url.searchParams.get("value");
if (val) {
this.model.jsonExpression.setValue(val);
}
}
updateQuery() {
const url = new URL(window.location.href);
url.searchParams.set("value", this.model.jsonExpression.value);
history.replaceState(null, document.title, url.toString());
}
}

View File

@@ -1,71 +0,0 @@
@import "~normalize.css";
@import "~@blueprintjs/core/lib/css/blueprint.css";
@import "~@blueprintjs/icons/lib/css/blueprint-icons.css";
@import "~@hediet/visualization/style.scss";
html,
body,
.target {
height: 100%;
}
body {
font-family: "Segoe UI", Tahoma, Geneva, Verdana, sans-serif;
margin: 0px;
padding: 0px;
border: 0px;
margin: 0;
padding: 0;
background-color: var(--vscode-editor-background);
}
.bp3-input,
.bp3-button {
border-radius: 0;
}
.bp3-icon {
svg {
color: lighten(black, 20);
width: 14px;
}
}
.component-GUI {
height: 100%;
& > .part-Header {
//padding: 6px 20px 6px 0;
//background-color: var(--vscode-sideBar-background);
// border-top: solid 1px var(--vscode-button-background);
// rgba(128, 128, 128, 0.15)
}
}
.component-monaco-editor {
width: 100%;
min-width: 0;
box-sizing: border-box;
display: flex;
padding: 2px;
border: 0.5px solid gray;
.part-editor {
min-width: 0;
min-height: 0;
flex: 1;
.current-line {
border: none !important;
}
.selectionHighlight {
background: transparent !important;
}
}
}
.bp3-transition-container {
z-index: 100000;
}

View File

@@ -1,288 +0,0 @@
:root {
--background-color: #263238;
--color: #eeffff;
--font-family: -apple-system, BlinkMacSystemFont, "Segoe WPC", "Segoe UI", HelveticaNeue-Light, Ubuntu, "Droid Sans", sans-serif;
--font-size: 13px;
--font-weight: normal;
--link-active-color: #eeffff;
--link-color: #80cbc4;
--vscode-activityBar-background: #192227;
--vscode-activityBar-border: rgba(0, 0, 0, 0.38);
--vscode-activityBar-dropBackground: rgba(255, 255, 255, 0.12);
--vscode-activityBar-foreground: #eeffff;
--vscode-activityBar-inactiveForeground: rgba(238, 255, 255, 0.6);
--vscode-activityBarBadge-background: #80cbc4;
--vscode-activityBarBadge-foreground: #000000;
--vscode-badge-background: rgba(0, 0, 0, 0.19);
--vscode-badge-foreground: #546e7a;
--vscode-breadcrumb-activeSelectionForeground: #80cbc4;
--vscode-breadcrumb-background: #263238;
--vscode-breadcrumb-focusForeground: #eeffff;
--vscode-breadcrumb-foreground: #5f7a87;
--vscode-breadcrumbPicker-background: #192227;
--vscode-button-background: rgba(128, 203, 196, 0.13);
--vscode-button-foreground: #ffffff;
--vscode-button-hoverBackground: rgba(175, 222, 218, 0.13);
--vscode-debugExceptionWidget-background: #420b0d;
--vscode-debugExceptionWidget-border: #a31515;
--vscode-debugToolBar-background: #263238;
--vscode-descriptionForeground: rgba(204, 204, 204, 0.7);
--vscode-diffEditor-insertedTextBackground: rgba(195, 232, 141, 0.08);
--vscode-diffEditor-removedTextBackground: rgba(255, 83, 112, 0.13);
--vscode-dropdown-background: #263238;
--vscode-dropdown-border: rgba(255, 255, 255, 0.06);
--vscode-dropdown-foreground: #f0f0f0;
--vscode-editor-background: #263238;
--vscode-editor-findMatchBackground: #515c6a;
--vscode-editor-findMatchHighlightBackground: rgba(234, 92, 0, 0.33);
--vscode-editor-findRangeHighlightBackground: rgba(58, 61, 65, 0.4);
--vscode-editor-focusedStackFrameHighlightBackground: rgba(122, 189, 122, 0.3);
--vscode-editor-font-family: -apple-system, BlinkMacSystemFont, "Segoe WPC", "Segoe UI", HelveticaNeue-Light, Ubuntu, "Droid Sans", sans-serif;
--vscode-editor-font-size: 13px;
--vscode-editor-font-weight: normal;
--vscode-editor-foreground: #eeffff;
--vscode-editor-hoverHighlightBackground: rgba(38, 79, 120, 0.25);
--vscode-editor-inactiveSelectionBackground: rgba(128, 203, 196, 0.06);
--vscode-editor-lineHighlightBackground: rgba(0, 0, 0, 0.31);
--vscode-editor-lineHighlightBorder: #282828;
--vscode-editor-rangeHighlightBackground: rgba(255, 255, 255, 0.04);
--vscode-editor-selectionBackground: rgba(128, 203, 196, 0.13);
--vscode-editor-selectionHighlightBackground: rgba(255, 204, 0, 0.13);
--vscode-editor-snippetFinalTabstopHighlightBorder: #525252;
--vscode-editor-snippetTabstopHighlightBackground: rgba(124, 124, 124, 0.3);
--vscode-editor-stackFrameHighlightBackground: rgba(255, 255, 0, 0.2);
--vscode-editor-wordHighlightBackground: rgba(87, 87, 87, 0.72);
--vscode-editor-wordHighlightStrongBackground: rgba(0, 73, 114, 0.72);
--vscode-editorActiveLineNumber-foreground: #c6c6c6;
--vscode-editorBracketMatch-background: #263238;
--vscode-editorBracketMatch-border: rgba(255, 204, 0, 0.31);
--vscode-editorCodeLens-foreground: #999999;
--vscode-editorCursor-foreground: #ffcc00;
--vscode-editorError-foreground: rgba(255, 83, 112, 0.44);
--vscode-editorGroup-border: rgba(0, 0, 0, 0.19);
--vscode-editorGroup-dropBackground: rgba(83, 89, 93, 0.5);
--vscode-editorGroupHeader-noTabsBackground: #263238;
--vscode-editorGroupHeader-tabsBackground: #263238;
--vscode-editorGutter-addedBackground: rgba(195, 232, 141, 0.38);
--vscode-editorGutter-background: #263238;
--vscode-editorGutter-commentRangeForeground: #c5c5c5;
--vscode-editorGutter-deletedBackground: rgba(255, 83, 112, 0.38);
--vscode-editorGutter-modifiedBackground: rgba(130, 170, 255, 0.38);
--vscode-editorHint-foreground: rgba(238, 238, 238, 0.7);
--vscode-editorHoverWidget-background: #263238;
--vscode-editorHoverWidget-border: rgba(255, 255, 255, 0.06);
--vscode-editorIndentGuide-activeBackground: #37474f;
--vscode-editorIndentGuide-background: rgba(55, 71, 79, 0.44);
--vscode-editorInfo-foreground: #008000;
--vscode-editorLineNumber-activeForeground: #5f7a87;
--vscode-editorLineNumber-foreground: #37474f;
--vscode-editorLink-activeForeground: #eeffff;
--vscode-editorMarkerNavigation-background: rgba(238, 255, 255, 0.02);
--vscode-editorMarkerNavigationError-background: rgba(255, 83, 112, 0.44);
--vscode-editorMarkerNavigationInfo-background: #008000;
--vscode-editorMarkerNavigationWarning-background: rgba(195, 232, 141, 0.44);
--vscode-editorOverviewRuler-addedForeground: rgba(0, 122, 204, 0.6);
--vscode-editorOverviewRuler-border: #263238;
--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(246, 185, 77, 0.7);
--vscode-editorOverviewRuler-incomingContentForeground: rgba(64, 166, 255, 0.5);
--vscode-editorOverviewRuler-infoForeground: rgba(18, 18, 136, 0.7);
--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: rgba(18, 136, 18, 0.7);
--vscode-editorOverviewRuler-wordHighlightForeground: rgba(160, 160, 160, 0.8);
--vscode-editorOverviewRuler-wordHighlightStrongForeground: rgba(192, 160, 192, 0.8);
--vscode-editorPane-background: #263238;
--vscode-editorRuler-foreground: #37474f;
--vscode-editorSuggestWidget-background: #263238;
--vscode-editorSuggestWidget-border: rgba(255, 255, 255, 0.06);
--vscode-editorSuggestWidget-foreground: #eeffff;
--vscode-editorSuggestWidget-highlightForeground: #80cbc4;
--vscode-editorSuggestWidget-selectedBackground: rgba(0, 0, 0, 0.31);
--vscode-editorUnnecessaryCode-opacity: rgba(0, 0, 0, 0.67);
--vscode-editorWarning-foreground: rgba(195, 232, 141, 0.44);
--vscode-editorWhitespace-foreground: rgba(238, 255, 255, 0.25);
--vscode-editorWidget-background: #192227;
--vscode-editorWidget-border: #ff0000;
--vscode-editorWidget-resizeBorder: #80cbc4;
--vscode-errorForeground: #f48771;
--vscode-extensionButton-prominentBackground: rgba(195, 232, 141, 0.56);
--vscode-extensionButton-prominentForeground: #ffffff;
--vscode-extensionButton-prominentHoverBackground: #c3e88d;
--vscode-focusBorder: rgba(255, 255, 255, 0);
--vscode-foreground: #cccccc;
--vscode-gitDecoration-addedResourceForeground: #81b88b;
--vscode-gitDecoration-conflictingResourceForeground: rgba(255, 203, 107, 0.56);
--vscode-gitDecoration-deletedResourceForeground: rgba(255, 83, 112, 0.56);
--vscode-gitDecoration-ignoredResourceForeground: rgba(95, 122, 135, 0.56);
--vscode-gitDecoration-modifiedResourceForeground: rgba(130, 170, 255, 0.56);
--vscode-gitDecoration-submoduleResourceForeground: #8db9e2;
--vscode-gitDecoration-untrackedResourceForeground: rgba(195, 232, 141, 0.56);
--vscode-gitlens-gutterBackgroundColor: rgba(255, 255, 255, 0.07);
--vscode-gitlens-gutterForegroundColor: #bebebe;
--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: #303c41;
--vscode-input-border: rgba(255, 255, 255, 0.06);
--vscode-input-foreground: #eeffff;
--vscode-input-placeholderForeground: rgba(238, 255, 255, 0.38);
--vscode-inputOption-activeBorder: #007acc;
--vscode-inputValidation-errorBackground: #5a1d1d;
--vscode-inputValidation-errorBorder: rgba(255, 83, 112, 0.31);
--vscode-inputValidation-infoBackground: #063b49;
--vscode-inputValidation-infoBorder: rgba(130, 170, 255, 0.31);
--vscode-inputValidation-warningBackground: #352a05;
--vscode-inputValidation-warningBorder: rgba(255, 203, 107, 0.31);
--vscode-list-activeSelectionBackground: #192227;
--vscode-list-activeSelectionForeground: #80cbc4;
--vscode-list-dropBackground: rgba(238, 255, 255, 0.13);
--vscode-list-errorForeground: #f88070;
--vscode-list-focusBackground: rgba(238, 255, 255, 0.13);
--vscode-list-focusForeground: #eeffff;
--vscode-list-highlightForeground: #80cbc4;
--vscode-list-hoverBackground: #192227;
--vscode-list-hoverForeground: #ffffff;
--vscode-list-inactiveFocusBackground: #313135;
--vscode-list-inactiveSelectionBackground: rgba(0, 0, 0, 0.19);
--vscode-list-inactiveSelectionForeground: #80cbc4;
--vscode-list-invalidItemForeground: #b89500;
--vscode-list-warningForeground: #4d9e4d;
--vscode-menu-background: #263238;
--vscode-menu-foreground: #eeffff;
--vscode-menu-selectionBackground: rgba(0, 0, 0, 0.31);
--vscode-menu-selectionBorder: rgba(0, 0, 0, 0.19);
--vscode-menu-selectionForeground: #80cbc4;
--vscode-menu-separatorBackground: #eeffff;
--vscode-menubar-selectionBackground: rgba(0, 0, 0, 0.19);
--vscode-menubar-selectionBorder: rgba(0, 0, 0, 0.19);
--vscode-menubar-selectionForeground: #80cbc4;
--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: #314149;
--vscode-notificationLink-foreground: #80cbc4;
--vscode-notifications-background: #263238;
--vscode-notifications-border: #314149;
--vscode-notifications-foreground: #eeffff;
--vscode-panel-background: #192227;
--vscode-panel-border: rgba(0, 0, 0, 0.38);
--vscode-panel-dropBackground: #eeffff;
--vscode-panelTitle-activeBorder: #80cbc4;
--vscode-panelTitle-activeForeground: #ffffff;
--vscode-panelTitle-inactiveForeground: #eeffff;
--vscode-peekView-border: rgba(0, 0, 0, 0.19);
--vscode-peekViewEditor-background: rgba(238, 255, 255, 0.02);
--vscode-peekViewEditor-matchHighlightBackground: rgba(128, 203, 196, 0.13);
--vscode-peekViewEditorGutter-background: rgba(238, 255, 255, 0.02);
--vscode-peekViewResult-background: rgba(238, 255, 255, 0.02);
--vscode-peekViewResult-fileForeground: #ffffff;
--vscode-peekViewResult-lineForeground: #bbbbbb;
--vscode-peekViewResult-matchHighlightBackground: rgba(128, 203, 196, 0.13);
--vscode-peekViewResult-selectionBackground: rgba(95, 122, 135, 0.44);
--vscode-peekViewResult-selectionForeground: #ffffff;
--vscode-peekViewTitle-background: rgba(238, 255, 255, 0.02);
--vscode-peekViewTitleDescription-foreground: rgba(238, 255, 255, 0.38);
--vscode-peekViewTitleLabel-foreground: #ffffff;
--vscode-pickerGroup-border: #3f3f46;
--vscode-pickerGroup-foreground: #80cbc4;
--vscode-progressBar-background: #80cbc4;
--vscode-scrollbar-shadow: rgba(38, 50, 56, 0);
--vscode-scrollbarSlider-activeBackground: #80cbc4;
--vscode-scrollbarSlider-background: rgba(238, 255, 255, 0.13);
--vscode-scrollbarSlider-hoverBackground: rgba(238, 255, 255, 0.06);
--vscode-selection-background: #eeffff;
--vscode-settings-checkboxBackground: #192227;
--vscode-settings-checkboxBorder: rgba(255, 255, 255, 0.06);
--vscode-settings-checkboxForeground: #eeffff;
--vscode-settings-dropdownBackground: #192227;
--vscode-settings-dropdownBorder: rgba(255, 255, 255, 0.06);
--vscode-settings-dropdownForeground: #eeffff;
--vscode-settings-dropdownListBorder: #ff0000;
--vscode-settings-headerForeground: #80cbc4;
--vscode-settings-modifiedItemIndicator: #80cbc4;
--vscode-settings-numberInputBackground: #192227;
--vscode-settings-numberInputBorder: rgba(255, 255, 255, 0.06);
--vscode-settings-numberInputForeground: #eeffff;
--vscode-settings-textInputBackground: #192227;
--vscode-settings-textInputBorder: rgba(255, 255, 255, 0.06);
--vscode-settings-textInputForeground: #eeffff;
--vscode-sideBar-background: #192227;
--vscode-sideBar-border: rgba(0, 0, 0, 0.38);
--vscode-sideBar-dropBackground: rgba(255, 255, 255, 0.12);
--vscode-sideBar-foreground: #5f7a87;
--vscode-sideBarSectionHeader-background: #192227;
--vscode-sideBarSectionHeader-border: rgba(0, 0, 0, 0.38);
--vscode-sideBarSectionHeader-foreground: #5f7a87;
--vscode-sideBarTitle-foreground: #eeffff;
--vscode-statusBar-background: #192227;
--vscode-statusBar-border: rgba(0, 0, 0, 0.38);
--vscode-statusBar-debuggingBackground: #c792ea;
--vscode-statusBar-debuggingBorder: rgba(0, 0, 0, 0.38);
--vscode-statusBar-debuggingForeground: #ffffff;
--vscode-statusBar-foreground: #546e7a;
--vscode-statusBar-noFolderBackground: #263238;
--vscode-statusBar-noFolderBorder: rgba(0, 0, 0, 0.38);
--vscode-statusBar-noFolderForeground: #546e7a;
--vscode-statusBarItem-activeBackground: rgba(255, 255, 255, 0.18);
--vscode-statusBarItem-hoverBackground: rgba(84, 110, 122, 0.13);
--vscode-statusBarItem-prominentBackground: #388a34;
--vscode-statusBarItem-prominentHoverBackground: #369432;
--vscode-tab-activeBackground: #263238;
--vscode-tab-activeBorder: #80cbc4;
--vscode-tab-activeForeground: #ffffff;
--vscode-tab-activeModifiedBorder: #5f7a87;
--vscode-tab-border: #263238;
--vscode-tab-inactiveBackground: #263238;
--vscode-tab-inactiveForeground: #5f7a87;
--vscode-tab-inactiveModifiedBorder: rgba(95, 122, 135, 0.5);
--vscode-tab-unfocusedActiveBorder: #546e7a;
--vscode-tab-unfocusedActiveForeground: #eeffff;
--vscode-tab-unfocusedActiveModifiedBorder: rgba(95, 122, 135, 0.5);
--vscode-tab-unfocusedInactiveForeground: rgba(95, 122, 135, 0.5);
--vscode-tab-unfocusedInactiveModifiedBorder: rgba(95, 122, 135, 0.25);
--vscode-terminal-ansiBlack: #000000;
--vscode-terminal-ansiBlue: #82aaff;
--vscode-terminal-ansiBrightBlack: #546e7a;
--vscode-terminal-ansiBrightBlue: #82aaff;
--vscode-terminal-ansiBrightCyan: #89ddff;
--vscode-terminal-ansiBrightGreen: #c3e88d;
--vscode-terminal-ansiBrightMagenta: #c792ea;
--vscode-terminal-ansiBrightRed: #ff5370;
--vscode-terminal-ansiBrightWhite: #ffffff;
--vscode-terminal-ansiBrightYellow: #ffcb6b;
--vscode-terminal-ansiCyan: #89ddff;
--vscode-terminal-ansiGreen: #c3e88d;
--vscode-terminal-ansiMagenta: #c792ea;
--vscode-terminal-ansiRed: #ff5370;
--vscode-terminal-ansiWhite: #ffffff;
--vscode-terminal-ansiYellow: #ffcb6b;
--vscode-terminal-background: #192227;
--vscode-terminal-border: rgba(0, 0, 0, 0.38);
--vscode-terminal-foreground: #cccccc;
--vscode-terminal-selectionBackground: rgba(255, 255, 255, 0.25);
--vscode-textBlockQuote-background: rgba(127, 127, 127, 0.1);
--vscode-textBlockQuote-border: rgba(0, 122, 204, 0.5);
--vscode-textCodeBlock-background: rgba(10, 10, 10, 0.4);
--vscode-textLink-activeForeground: #eeffff;
--vscode-textLink-foreground: #80cbc4;
--vscode-textPreformat-foreground: #d7ba7d;
--vscode-textSeparator-foreground: rgba(255, 255, 255, 0.18);
--vscode-titleBar-activeBackground: #192227;
--vscode-titleBar-activeForeground: #eeffff;
--vscode-titleBar-border: rgba(0, 0, 0, 0.38);
--vscode-titleBar-inactiveBackground: #192227;
--vscode-titleBar-inactiveForeground: #5f7a87;
--vscode-widget-shadow: rgba(0, 0, 0, 0.19);
}

View File

@@ -1,307 +0,0 @@
: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 +0,0 @@
{
"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/**/*"]
}

View File

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

View File

@@ -1,95 +0,0 @@
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");
import { CleanWebpackPlugin } from "clean-webpack-plugin";
import { EnabledVisualizers } from "@hediet/visualization";
const r = (file: string) => path.resolve(__dirname, file);
const mode = process.argv.some(v => v === "--fast") ? "fast" : "default";
const enabledVisualizers: EnabledVisualizers =
mode === "fast"
? {
VisJsGraphVisualizer: true,
TreeVisualizer: false,
GraphvizGraphVisualizer: false,
SvgVisualizer: false,
GraphvizDotVisualizer: false,
TextVisualizer: false,
PlotlyVisualizer: false,
GridVisualizer: false,
MonacoTextVisualizer: false,
AstVisualizer: false,
}
: {
VisJsGraphVisualizer: true,
TreeVisualizer: true,
GraphvizGraphVisualizer: true,
SvgVisualizer: true,
GraphvizDotVisualizer: true,
TextVisualizer: true,
PlotlyVisualizer: true,
GridVisualizer: true,
MonacoTextVisualizer: true,
AstVisualizer: true,
};
const stringifiedEnabledVisualizers: Record<string, string> = {};
for (const [key, val] of Object.entries(enabledVisualizers)) {
stringifiedEnabledVisualizers[key] = JSON.stringify(val);
}
module.exports = {
entry: [r("src/index.tsx")],
output: {
path: r("dist"),
filename: "[name].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 },
},
],
},
node: {
fs: "empty",
},
plugins: (() => {
const plugins: any[] = [
new HtmlWebpackPlugin({
title: "Debug Visualizer",
}),
new ForkTsCheckerWebpackPlugin(),
new CleanWebpackPlugin(),
new webpack.DefinePlugin({
ENABLED_VISUALIZERS: stringifiedEnabledVisualizers,
}),
];
plugins.push(
new MonacoWebpackPlugin({
// Add more languages here once webworker issues are solved.
languages: ["typescript"],
})
);
return plugins;
})(),
} as webpack.Configuration;

File diff suppressed because it is too large Load Diff

View File

@@ -1,57 +0,0 @@
{
"name": "@hediet/visualization",
"version": "0.0.1",
"license": "GPL-3.0",
"scripts": {
"dev": "tsc --watch",
"build": "tsc"
},
"main": "./dist/index.js",
"types": "./dist/index.d.ts",
"dependencies": {
"@hediet/std": "^0.6.0",
"classnames": "^2.2.6",
"mobx": "^5.15.4",
"mobx-react": "^6.1.8",
"monaco-editor": "^0.20.0",
"react": "^16.12.0",
"react-dom": "^16.12.0",
"react-measure": "^2.3.0",
"react-svg-pan-zoom": "^3.8.0",
"viz.js": "^2.1.2",
"line-column": "^1.0.2",
"vis-network": "^7.3.6",
"vis-data": "^6.4.1",
"react-plotly.js": "^2.4.0",
"plotly.js": "^1.52.2"
},
"devDependencies": {
"@types/plotly.js": "1.44.28",
"@types/react-plotly.js": "^2.2.4",
"@types/html-webpack-plugin": "^3.2.2",
"@types/react-measure": "^2.0.6",
"@types/react-svg-pan-zoom": "^3.3.0",
"@types/webpack": "^4.41.6",
"@types/classnames": "^2.2.9",
"@types/react": "^16.9.22",
"@types/react-dom": "^16.9.5",
"@types/line-column": "^1.0.0",
"clean-webpack-plugin": "^3.0.0",
"css-loader": "^3.4.2",
"file-loader": "^5.1.0",
"fork-ts-checker-webpack-plugin": "^4.0.4",
"html-webpack-plugin": "^3.2.0",
"monaco-editor-webpack-plugin": "^1.9.0",
"node-sass": "^4.13.1",
"raw-loader": "^4.0.0",
"sass": "^1.25.0",
"sass-loader": "^8.0.2",
"style-loader": "^1.1.3",
"ts-loader": "^6.2.1",
"ts-node": "^8.6.2",
"typescript": "^3.8.2",
"webpack": "^4.41.6",
"webpack-cli": "^3.3.11",
"webpack-dev-server": "^3.10.3"
}
}

View File

@@ -1,12 +0,0 @@
export interface EnabledVisualizers {
VisJsGraphVisualizer: boolean;
TreeVisualizer: boolean;
GraphvizGraphVisualizer: boolean;
SvgVisualizer: boolean;
GraphvizDotVisualizer: boolean;
TextVisualizer: boolean;
PlotlyVisualizer: boolean;
GridVisualizer: boolean;
MonacoTextVisualizer: boolean;
AstVisualizer: boolean;
}

View File

@@ -1,30 +0,0 @@
import * as React from "react";
import { observable, runInAction } from "mobx";
import { observer } from "mobx-react";
export function makeLazyLoadable<TProps>(
selector: () => Promise<React.ComponentClass<TProps>>
): React.ComponentClass<TProps> {
@observer
class MyComponent extends React.Component<TProps> {
@observable
component: React.ComponentClass<TProps> | undefined;
async componentDidMount() {
const result = await selector();
runInAction("Set Lazily Loaded Component", () => {
this.component = result;
});
}
render() {
if (!this.component) {
return <div>Loading...</div>;
}
const C = this.component;
return <C {...this.props} />;
}
}
return MyComponent;
}

View File

@@ -1,71 +0,0 @@
import { ExtractedData } from "@hediet/debug-visualizer-data-extraction";
export interface VisualizationId extends String {
__brand: "VisualizationId";
}
export function asVisualizationId(id: string): VisualizationId {
return (id as unknown) as VisualizationId;
}
export interface Visualization {
readonly name: string;
readonly id: VisualizationId;
readonly priority: number;
render(): React.ReactElement;
}
export interface VisualizationCollector {
addVisualization(visualization: Visualization): void;
}
export abstract class Visualizer {
abstract visualize(
data: ExtractedData,
collector: VisualizationCollector
): void;
getVisualizationsArray(data: ExtractedData): Visualization[] {
const result = new Array<Visualization>();
this.visualize(data, {
addVisualization(visualization) {
result.push(visualization);
},
});
return result;
}
getBestVisualization(
data: ExtractedData,
preferredVisualization: VisualizationId | undefined
): {
visualization: Visualization | undefined;
allVisualizations: Visualization[];
} {
const allVisualizations = this.getVisualizationsArray(data);
allVisualizations.sort((a, b) => b.priority - a.priority);
let visualization: Visualization | undefined = allVisualizations[0];
if (preferredVisualization) {
const preferred = allVisualizations.find(
vis => vis.id === preferredVisualization
);
if (preferred) {
visualization = preferred;
}
}
return { visualization, allVisualizations };
}
}
export class ComposedVisualizer extends Visualizer {
constructor(private readonly composedProviders: ReadonlyArray<Visualizer>) {
super();
}
visualize(data: ExtractedData, collector: VisualizationCollector): void {
for (const c of this.composedProviders) {
c.visualize(data, collector);
}
}
}

View File

@@ -1,7 +0,0 @@
import * as React from "react";
export class VisualizerView extends React.Component {
render(): React.ReactNode {
return null;
}
}

View File

@@ -1,4 +0,0 @@
export * from "./visualizers";
export * from "./Visualizer";
export * from "./VisualizerView";
export * from "./EnabledVisualizers";

View File

@@ -1,22 +0,0 @@
declare module "viz.js" {
const x: any;
export default x;
}
declare module "viz.js/full.render.js" {
export const render: any;
export const Module: any;
}
declare module "line-column" {
export = function(
text: string
): {
fromIndex(
idx: number
): {
line: number;
col: number;
};
} {};
}

View File

@@ -1,256 +0,0 @@
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

@@ -1,90 +0,0 @@
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"])}
/>
);
}

View File

@@ -1,219 +0,0 @@
import {
Visualizer,
VisualizationCollector,
asVisualizationId,
} from "../Visualizer";
import {
ExtractedData,
isCommonDataType,
CommonDataTypes,
} from "@hediet/debug-visualizer-data-extraction";
import { createTreeViewModelFromTreeNodeData } from "./TreeVisualizer/TreeVisualizer";
import { TreeWithPathView, TreeViewModel } from "./TreeVisualizer/Views";
import * as React from "react";
import { observer, disposeOnUnmount } from "mobx-react";
import { observable, autorun, trace } from "mobx";
import * as monaco from "monaco-editor";
import { getLanguageId } from "./text-visualizers/MonacoTextVisualizer";
import LineColumn from "line-column";
export class AstVisualizer extends Visualizer {
visualize(data: ExtractedData, collector: VisualizationCollector): void {
if (isCommonDataType(data, { ast: true })) {
collector.addVisualization({
id: asVisualizationId("ast"),
name: "AST",
priority: 110,
render() {
const m = createTreeViewModelFromTreeNodeData(data.root);
const l = LineColumn(data.text);
function translatePosition(pos: number): monaco.IPosition {
const r = l.fromIndex(pos);
return {
column: r.col,
lineNumber: r.line,
};
}
const nodeInfoToRange = (info: NodeInfo): monaco.IRange => {
const start = translatePosition(info.position);
const end = translatePosition(
info.position + info.length
);
const range = monaco.Range.fromPositions(start, end);
return range;
};
return (
<AstTree
model={m}
data={data}
nodeInfoToRange={nodeInfoToRange}
/>
);
},
});
}
}
}
interface NodeInfo {
position: number;
length: number;
}
@observer
export class AstTree extends React.Component<{
model: TreeViewModel<NodeInfo>;
data: CommonDataTypes.Ast;
nodeInfoToRange: (info: NodeInfo) => monaco.IRange;
}> {
render() {
const model = this.props.model;
const data = this.props.data;
let languageId = "text";
if (data.fileName) {
languageId = getLanguageId(data.fileName);
}
return (
<div className="component-AstTree">
<div className="part-tree2">
<TreeWithPathView model={model} />
</div>
<div className="part-editor">
<MonacoEditor
nodeInfoToRange={this.props.nodeInfoToRange}
model={model}
languageId={languageId}
text={data.text}
/>
</div>
</div>
);
}
}
@observer
export class MonacoEditor extends React.Component<{
text: string;
languageId: string;
model: TreeViewModel<NodeInfo>;
nodeInfoToRange: (info: NodeInfo) => monaco.IRange;
}> {
@observable private editor: monaco.editor.IStandaloneCodeEditor | undefined;
private modelNotObservable:
| monaco.editor.ITextModel
| undefined = undefined;
@observable private model: monaco.editor.ITextModel | undefined = undefined;
private markedDecorations: string[] = [];
private selectedDecorations: string[] = [];
componentWillUnmount() {
if (this.editor) {
this.editor.dispose();
}
}
@disposeOnUnmount
private _updateMarkedDecorations = autorun(
() => {
if (this.editor && this.model) {
const editorModel = this.model;
const ranges = this.props.model.marked.map(s =>
this.props.nodeInfoToRange(s.data)
);
this.markedDecorations = this.editor.deltaDecorations(
this.markedDecorations,
ranges.map(range => ({
range,
options: { className: "marked" },
}))
);
if (ranges.length > 0) {
this.editor.revealRange(
ranges[0],
monaco.editor.ScrollType.Smooth
);
}
}
},
{ name: "updateMarkedDecorations" }
);
@disposeOnUnmount
private _updateSelectedDecoration = autorun(
() => {
if (this.editor && this.model) {
const selected = this.props.model.selected;
if (selected) {
const range = this.props.nodeInfoToRange(selected.data);
this.selectedDecorations = this.editor.deltaDecorations(
this.selectedDecorations,
[
{
range,
options: { className: "selected" },
},
]
);
this.editor.revealRange(
range,
monaco.editor.ScrollType.Smooth
);
} else {
this.selectedDecorations = this.editor.deltaDecorations(
this.selectedDecorations,
[]
);
}
}
},
{ name: "updateDecorations" }
);
@disposeOnUnmount
private _updateText = autorun(() => {
if (this.editor) {
const model = monaco.editor.createModel(
this.props.text,
this.props.languageId,
undefined
);
this.editor.setModel(model);
if (this.modelNotObservable) {
this.modelNotObservable.dispose();
}
this.model = model;
}
});
private readonly setEditorDiv = (editorDiv: HTMLDivElement) => {
if (!editorDiv) {
return;
}
this.editor = monaco.editor.create(editorDiv, {
model: null,
automaticLayout: true,
scrollBeyondLastLine: false,
minimap: { enabled: false },
fixedOverflowWidgets: true,
readOnly: true,
lineDecorationsWidth: 0,
lineNumbersMinChars: 4,
glyphMargin: false,
smoothScrolling: true,
});
};
render() {
return (
<div className="component-monaco-editor">
<div className="part-editor" ref={this.setEditorDiv} />
</div>
);
}
}

View File

@@ -1,399 +0,0 @@
import {
Visualizer,
VisualizationCollector,
asVisualizationId,
} from "../Visualizer";
import {
ExtractedData,
isCommonDataType,
CommonDataTypes,
} from "@hediet/debug-visualizer-data-extraction";
import * as React from "react";
import { observer } from "mobx-react";
import { computed, action, observable } from "mobx";
import { Icon } from "@blueprintjs/core";
export class GridVisualizer extends Visualizer {
visualize(data: ExtractedData, collector: VisualizationCollector): void {
if (isCommonDataType(data, { array: true })) {
collector.addVisualization({
id: asVisualizationId("grid"),
name: "Grid",
priority: 90,
render: () => <DecoratedGridComponent data={data} />,
});
}
}
}
export class DecoratedGridComponent extends React.Component<{
data: CommonDataTypes.Grid;
}> {
render() {
const d = this.props.data;
const map = new Set<string>();
function getUniqueId(tag: string): string {
let n = 0;
while (true) {
const id = tag + (n === 0 ? "" : `-${n}`);
if (!map.has(id)) {
map.add(id);
return id;
}
n++;
}
}
const rows: GridComponent["props"]["rows"] = [];
rows.push();
let rowIdx = 0;
let columnCount = 0;
for (const row of d.rows) {
columnCount = Math.max(columnCount, row.columns.length);
rows.push({
columns: row.columns.map((c, colIdx) => ({
content: (
<Cell>
{c.content !== undefined ? c.content : c.tag || ""}
</Cell>
),
id:
c.tag !== undefined
? getUniqueId(c.tag)
: `${rowIdx}-${colIdx}`,
color: c.color,
kind: "data",
})),
});
rowIdx++;
}
rows.unshift({
columns: [...new Array(columnCount)].map((v, idx) => ({
id: `column-header-${idx}`,
content: <Cell>{idx}</Cell>,
kind: "header",
})),
});
if (d.markers) {
for (const m of d.markers) {
rows.push({
columns: [],
});
const r = rows[rows.length - 1];
for (let i = 0; i < m.column; i++) {
r.columns.push({
content: "",
kind: "empty",
id: `marker-spacer-${i}`,
});
}
r.columns.push({
id: m.id,
kind: "empty",
content: (
<Cell>
<Icon
icon="arrow-up"
color="var(--vscode-editor-foreground)"
/>
{m.label || m.id}
</Cell>
),
});
}
}
return (
<div
style={{
height: "100%",
justifyContent: "center",
alignItems: "center",
padding: 10,
display: "flex",
}}
>
<GridComponent rows={rows} markers={[]} />
</div>
);
}
}
function Cell(props: { children: React.ReactNode }) {
return (
<div
style={{
padding: 10,
display: "flex",
flexDirection: "column",
alignItems: "center",
}}
>
{props.children}
</div>
);
}
interface CellData {
content: React.ReactNode;
id: string;
color?: string;
kind: "data" | "header" | "empty";
}
@observer
class GridComponent extends React.Component<{
rows: {
columns: CellData[];
}[];
markers: {
id: string;
row: number;
column: number;
rows?: number;
columns?: number;
label?: string;
color?: string;
}[];
}> {
private readonly cells = new Map<string, CellInfo>();
@computed get cellsGrid(): { maxWidth: number; grid: CellInfo[][] } {
const unusedIds = new Set(this.cells.keys());
const rows = this.props.rows;
const grid = new Array<Array<CellInfo>>();
let maxWidth = 0;
for (let rowIdx = 0; rowIdx < rows.length; rowIdx++) {
const row = rows[rowIdx];
maxWidth = Math.max(maxWidth, row.columns.length);
const cellRow = new Array<CellInfo>();
grid.push(cellRow);
for (let colIdx = 0; colIdx < row.columns.length; colIdx++) {
const col = row.columns[colIdx];
const key = col.id;
unusedIds.delete(key);
let c = this.cells.get(key);
if (!c) {
// New cell
c = new CellInfo(col);
this.cells.set(key, c);
} else {
c.updateCellData(col);
}
cellRow.push(c);
}
}
for (const id in unusedIds.values()) {
this.cells.delete(id);
}
return { maxWidth, grid };
}
@computed
private get layout(): {
cells: {
cell: CellInfo;
top: number;
left: number;
width: number;
height: number;
}[];
width: number;
height: number;
} {
const { maxWidth, grid } = this.cellsGrid;
const maxWidths = new Array<number>();
for (let colIdx = 0; colIdx < maxWidth; colIdx++) {
let maxWidth = 30;
for (const row of grid) {
const col = row[colIdx];
if (col) {
maxWidth = Math.max(maxWidth, col.contentWidth || 0);
}
}
maxWidths.push(maxWidth);
}
const result = new Array<{
cell: CellInfo;
top: number;
left: number;
width: number;
height: number;
}>();
let width = 0;
let height = 0;
let top = 0;
let left = 0;
for (const row of grid) {
let maxHeight = 0;
for (const col of row) {
maxHeight = Math.max(maxHeight, col.contentHeight || 0);
}
for (let colIdx = 0; colIdx < row.length; colIdx++) {
const maxWidth = maxWidths[colIdx];
const col = row[colIdx];
result.push({
cell: col,
top,
left,
height: maxHeight,
width: maxWidth,
});
height = Math.max(height, top + maxHeight);
width = Math.max(width, left + maxWidth);
col.lastTop = col.top;
col.lastLeft = col.left;
col.top = top;
col.left = left;
col.lastContentArea = col.contentArea;
col.contentArea = maxHeight * maxWidth;
left += maxWidth;
}
left = 0;
top += maxHeight;
}
result.sort((a, b) => a.cell.id.localeCompare(b.cell.id));
return { cells: result, height, width };
}
render() {
const l = this.layout;
return (
<div
className="component-Grid"
style={{
position: "relative",
height: l.height,
width: l.width,
}}
>
{l.cells.map(i => (
<div
key={i.cell.id}
style={{
position: "absolute",
top: i.top,
left: i.left,
height: i.height,
width: i.width,
zIndex: i.cell.distance,
transition:
i.cell.lastContentArea !== 0 ? "all 1s" : "",
}}
>
<div
className={`part-${i.cell.kind}`}
style={
{
empty: {},
data: {
display: "flex",
overflow: "hidden",
justifyContent: "center",
margin: 2,
},
header: {
overflow: "hidden",
display: "flex",
justifyContent: "center",
margin: 2,
},
}[i.cell.kind]
}
>
<div
ref={i.cell.handleRef}
style={{
width: "fit-content",
height: "fit-content",
}}
>
{i.cell.content}
</div>
</div>
</div>
))}
</div>
);
}
componentDidMount() {
this.updateContentSize();
}
componentDidUpdate() {
this.updateContentSize();
}
@action
updateContentSize() {
for (const c of this.cells.values()) {
if (c.ref) {
const r = c.ref.getBoundingClientRect();
c.contentHeight = r.height + 6;
c.contentWidth = r.width + 4;
}
}
}
}
class CellInfo {
@observable public contentWidth: number | undefined = undefined;
@observable public contentHeight: number | undefined = undefined;
public contentArea = 0;
public lastContentArea = 0;
public lastTop = 0;
public top = 0;
public lastLeft = 0;
public left = 0;
public get distance(): number {
return Math.floor(
Math.pow(this.top - this.lastTop, 2) +
Math.pow(this.left - this.lastLeft, 2)
);
}
content!: React.ReactNode;
ref: HTMLDivElement | null = null;
public readonly id: string;
public readonly kind: CellData["kind"];
constructor(cellData: CellData) {
this.id = cellData.id;
this.kind = cellData.kind;
this.updateCellData(cellData);
}
public updateCellData(cellData: CellData) {
this.content = cellData.content;
}
public readonly handleRef = (ref: HTMLDivElement | null) => {
this.ref = ref;
};
}

View File

@@ -1,113 +0,0 @@
import Measure, { ContentRect } from "react-measure";
import { observer } from "mobx-react";
import * as React from "react";
import { observable, action } from "mobx";
import { Tool, ReactSVGPanZoom, Value } from "react-svg-pan-zoom";
import {
Visualizer,
VisualizationCollector,
asVisualizationId,
} from "../Visualizer";
import {
ExtractedData,
isCommonDataType,
} from "@hediet/debug-visualizer-data-extraction";
export class SvgVisualizer extends Visualizer {
visualize(data: ExtractedData, collector: VisualizationCollector): void {
if (isCommonDataType(data, { svg: true })) {
collector.addVisualization({
id: asVisualizationId("svg"),
name: "Svg",
priority: 100,
render() {
return <SvgViewer svgContent={data.text} />;
},
});
}
}
}
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 as any}
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>
);
}
}

View File

@@ -1,64 +0,0 @@
import {
TreeViewModel,
TreeNodeViewModel,
TreeView,
TreeWithPathView,
} from "./Views";
import {
ExtractedData,
isCommonDataType,
TreeNode,
} from "@hediet/debug-visualizer-data-extraction";
import {
Visualizer,
VisualizationCollector,
asVisualizationId,
} from "../../Visualizer";
import * as React from "react";
export class TreeVisualizer extends Visualizer {
visualize(data: ExtractedData, collector: VisualizationCollector): void {
if (isCommonDataType(data, { tree: true })) {
collector.addVisualization({
id: asVisualizationId("tree"),
name: "Tree",
priority: 100,
render() {
const m = createTreeViewModelFromTreeNodeData(data.root);
return <TreeWithPathView model={m} />;
},
});
}
}
}
export function createTreeViewModelFromTreeNodeData<TData>(
root: TreeNode<TData>
): TreeViewModel<TData> {
const m = new TreeViewModel<TData>();
m.root = recurse(root, m);
return m;
function recurse(
node: TreeNode<TData>,
viewModel: TreeViewModel<TData>
): TreeNodeViewModel<TData> {
const children: TreeNodeViewModel<TData>[] = node.children.map(c =>
recurse(c, viewModel)
);
const model = new TreeNodeViewModel<TData>(
viewModel,
node.id,
node.name,
node.value,
node.emphasizedValue,
children,
node.data
);
model.isMarked = !!node.isMarked;
for (const c of children) {
c.parent = model;
}
return model;
}
}

View File

@@ -1,530 +0,0 @@
import { observer, disposeOnUnmount } from "mobx-react";
import { observable, autorun, computed, action, runInAction } from "mobx";
import * as React from "react";
import * as classNames from "classnames";
import { Rectangle, Point } from "../../utils/Point";
import { SvgLine } from "../../utils/SvgElements";
// import "./style.scss";
export class TreeViewModel<TData = unknown> {
private started = true;
start() {
this.started = true;
this.updateOffsets();
}
stop() {
this.started = false;
}
private version = 0;
private lastUpdateVersion = -1;
@observable public root: TreeNodeViewModel<TData> | undefined = undefined;
public invalidateOffsets() {
this.version++;
}
@action
public updateOffsets() {
if (!this.started) {
return;
}
if (!this.root) {
return;
}
if (this.lastUpdateVersion === this.version) {
return;
}
this.lastUpdateVersion = this.version;
console.log("update");
this.root.updateOffsets();
}
public get marked(): TreeNodeViewModel<TData>[] {
const result = new Array<TreeNodeViewModel<TData>>();
function recurse(node: TreeNodeViewModel<TData>) {
if (node.isMarked) {
result.push(node);
}
for (const c of node.children) {
recurse(c);
}
}
if (this.root) {
recurse(this.root);
}
return result;
}
@observable selected: TreeNodeViewModel<TData> | undefined = undefined;
@action public select(current: TreeNodeViewModel<TData> | undefined) {
if (this.selected === current) {
return;
}
if (this.selected) {
this.selected.isSelected = false;
}
if (current) {
current.isSelected = true;
}
this.selected = current;
}
@action public toggleSelect(current: TreeNodeViewModel<TData>) {
if (this.selected === current) {
this.select(undefined);
} else {
this.select(current);
}
}
private selectOnHoverEnabled = false;
private selectedBeforeHover:
| TreeNodeViewModel<TData>
| undefined = undefined;
@action public handleHoverEvent(
current: TreeNodeViewModel<TData> | undefined
) {
if (this.selectOnHoverEnabled) {
if (current) {
this.select(current);
} else {
this.select(this.selectedBeforeHover);
}
}
}
setSelectOnHover(enable: boolean) {
if (this.selectOnHoverEnabled === enable) {
return;
}
if (enable) {
this.selectedBeforeHover = this.selected;
} else if (!this.selected) {
this.select(this.selectedBeforeHover);
}
this.selectOnHoverEnabled = enable;
}
}
let i = 0;
export class TreeNodeViewModel<TData = unknown> {
public parent: TreeNodeViewModel<TData> | null = null;
constructor(
public readonly treeViewModel: TreeViewModel<TData>,
public readonly id: string | undefined,
public readonly name: string,
public readonly value: string | undefined,
public readonly emphasizedValue: string | undefined,
public readonly children: TreeNodeViewModel<TData>[],
public readonly data: TData
) {}
@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;
}
}
}
const isValidFunctionName = (function() {
var validName = /^[$A-Z_][0-9A-Z_$]*$/i;
var reserved = {
abstract: true,
boolean: true,
// ...
with: true,
} as any;
return function(s: string) {
// Ensure a valid name and not reserved.
return validName.test(s) && !reserved[s];
};
})();
@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.reduce((acc, v) => {
acc = acc.slice();
function add() {
acc.push(
<span
className="part-path-item"
key={acc.length}
>
{v}
</span>
);
}
if (isValidFunctionName(v)) {
if (acc.length > 0) {
acc.push(
<span key={acc.length}>.</span>
);
}
add();
} else {
acc.push(
<span key={acc.length}>[</span>
);
add();
acc.push(
<span key={acc.length}>]</span>
);
}
return acc;
}, new Array<React.ReactElement>())}
</span>
) : (
"(Nothing Selected)"
)}
</span>
</div>
<div className="part-tree">
<TreeView model={model} />
</div>
</div>
);
}
}
@observer
export class TreeView extends React.Component<{ model: TreeViewModel }> {
componentWillUpdate(newProps: this["props"]) {
newProps.model.stop();
}
componentWillMount() {
this.props.model.stop();
}
componentDidUpdate() {
this.props.model.start();
}
componentDidMount() {
this.props.model.start();
}
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.setSelectOnHover(true);
}
}}
onKeyUp={e => {
if (e.keyCode === 18) {
// alt
e.preventDefault();
e.stopPropagation();
model.setSelectOnHover(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.toggleSelect(this.props.model);
}
private handleMouseEnterOrLeave(enter: boolean) {
this.props.model.treeViewModel.handleHoverEvent(
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;
console.log("render");
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}
>
<div
className="part-header"
onMouseOver={e => {
this.handleMouseEnterOrLeave(true);
e.stopPropagation();
}}
onMouseOut={e => {
this.handleMouseEnterOrLeave(false);
e.stopPropagation();
}}
>
<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>
{model.value && (
<span className="part-value">{model.value}</span>
)}
{model.emphasizedValue && (
<span className="part-emphasized-value">
{model.emphasizedValue}
</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

@@ -1,141 +0,0 @@
.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-value {
color: darkgreen;
text-decoration: underline;
/*&::before,
&::after {
content: '"';
}*/
padding: 1px 3px;
margin-left: 5px;
}
.part-emphasized-value {
color: darkred;
border: black solid 1px;
padding: 1px 3px;
margin-left: 5px;
}
}
}
.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;
color: lightgray;
.part-path-item {
color: black;
margin-left: 1px;
margin-right: 1px;
}
}
.part-tree {
overflow: auto;
flex: 1;
}
}
.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");
}
}

View File

@@ -1,72 +0,0 @@
import { observer, disposeOnUnmount } from "mobx-react";
import * as React from "react";
import { observable, autorun, runInAction } from "mobx";
import { SvgViewer } from "../SvgVisualizer";
import {
Visualizer,
VisualizationCollector,
asVisualizationId,
} from "../../Visualizer";
import {
ExtractedData,
isCommonDataType,
} from "@hediet/debug-visualizer-data-extraction";
export class GraphvizDotVisualizer extends Visualizer {
visualize(data: ExtractedData, collector: VisualizationCollector): void {
if (isCommonDataType(data, { dotGraph: true })) {
collector.addVisualization({
id: asVisualizationId("graphviz-dot"),
name: "Graphviz (Dot Data)",
priority: 100,
render() {
return <GraphvizDotViewer dotCode={data.text} />;
},
});
}
}
}
class VisLoader {
private result: any | undefined;
async getViz(): Promise<any> {
if (!this.result) {
const Viz = await import("viz.js");
const { Module, render } = await import("viz.js/full.render.js");
const viz = new Viz.default({
Module: () => Module({ TOTAL_MEMORY: 1 << 30 }),
render,
});
this.result = viz;
}
return this.result;
}
}
const vizLoader = new VisLoader();
@observer
export class GraphvizDotViewer 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 viz = await vizLoader.getViz();
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} />;
}
}

View File

@@ -1,65 +0,0 @@
import { observer } from "mobx-react";
import * as React from "react";
import { GraphvizDotViewer } from "./GraphvizDotVisualizer";
import {
Visualizer,
VisualizationCollector,
asVisualizationId,
} from "../../Visualizer";
import {
ExtractedData,
isCommonDataType,
} from "@hediet/debug-visualizer-data-extraction";
export class GraphvizGraphVisualizer extends Visualizer {
visualize(data: ExtractedData, collector: VisualizationCollector): void {
if (isCommonDataType(data, { graph: true })) {
collector.addVisualization({
id: asVisualizationId("graphviz-graph"),
name: "Graphviz",
priority: 100,
render() {
return (
<GraphvizGraphViewer
edges={data.edges}
nodes={data.nodes}
/>
);
},
});
}
}
}
@observer
export class GraphvizGraphViewer extends React.Component<{
nodes: { id: string; label?: string }[];
edges: { from: string; to: string; label?: string }[];
}> {
render() {
const { nodes, edges } = this.props;
const dotContent = `
digraph MyGraph {
${nodes
.map(
n =>
`"${n.id}" [ label = ${JSON.stringify(
n.label !== undefined ? n.label : n.id
)} ];`
)
.join("\n ")}
${edges
.map(
e =>
`"${e.from}" -> "${
e.to
}" [ label = ${JSON.stringify(
e.label !== undefined ? e.label : ""
)} ];`
)
.join("\n")}
}
`;
return <GraphvizDotViewer dotCode={dotContent} />;
}
}

View File

@@ -1,99 +0,0 @@
import { DataSet, Network, Options } from "vis-network";
import { observer } from "mobx-react";
import * as React from "react";
import {
NodeGraphData,
EdgeGraphData,
} from "@hediet/debug-visualizer-data-extraction";
@observer
export class VisJsGraphViewer extends React.Component<{
nodes: NodeGraphData[];
edges: EdgeGraphData[];
}> {
private readonly divRef = React.createRef<HTMLDivElement>();
private readonly nodes = new DataSet<{
id: string;
label?: string;
color?: string;
shape?: string;
}>();
private readonly edges = new DataSet<{
id: string;
from: string;
to: string;
label?: string;
color?: string;
dashes?: boolean;
shape?: boolean;
}>();
render() {
return <div style={{ height: "100%" }} ref={this.divRef} />;
}
synchronizeData() {
const newNodes = new Set<string>();
for (const n of this.props.nodes) {
newNodes.add(n.id);
this.nodes.update({
id: n.id,
label: n.label !== undefined ? n.label : n.id,
color: n.color,
shape: n.shape,
});
}
this.nodes.forEach(item => {
if (!newNodes.has(item.id)) {
this.nodes.remove(item);
}
});
function getIdOfEdge(e: EdgeGraphData): string {
if (e.id) {
return e.id;
}
return e.from + "####" + e.to + "|" + e.label;
}
const newEdges = new Set<string>();
for (const n of this.props.edges) {
const id = getIdOfEdge(n);
newEdges.add(id);
this.edges.update({
id: id,
label: n.label !== undefined ? n.label : "",
from: n.from,
to: n.to,
color: n.color,
dashes: n.dashes,
});
}
this.edges.forEach(item => {
if (!newEdges.has(item.id)) {
this.edges.remove(item);
}
});
}
componentDidUpdate() {
this.synchronizeData();
}
componentDidMount() {
this.synchronizeData();
const data = {
nodes: this.nodes,
edges: this.edges,
};
const options: Options = {
edges: {
arrows: {
to: { enabled: true, scaleFactor: 1, type: "arrow" },
},
},
};
const network = new Network(this.divRef.current!, data, options);
}
}

View File

@@ -1,36 +0,0 @@
import * as React from "react";
import {
Visualizer,
VisualizationCollector,
asVisualizationId,
} from "../../Visualizer";
import {
ExtractedData,
isCommonDataType,
} from "@hediet/debug-visualizer-data-extraction";
import { makeLazyLoadable } from "../../LazyLoadable";
// import { DataSet, Network, Options } from "vis-network";
const VisJsGraphViewerLazyLoadable = makeLazyLoadable(
async () => (await import("./VisJsGraphViewer")).VisJsGraphViewer
);
export class VisJsGraphVisualizer extends Visualizer {
visualize(data: ExtractedData, collector: VisualizationCollector): void {
if (isCommonDataType(data, { graph: true })) {
collector.addVisualization({
id: asVisualizationId("vis-js-graph"),
name: "vis.js",
priority: 1001,
render() {
return (
<VisJsGraphViewerLazyLoadable
edges={data.edges}
nodes={data.nodes}
/>
);
},
});
}
}
}

View File

@@ -1,33 +0,0 @@
import { ComposedVisualizer, Visualizer } from "../Visualizer";
import { EnabledVisualizers } from "../EnabledVisualizers";
declare const ENABLED_VISUALIZERS: EnabledVisualizers;
function isVisualizer(v: Visualizer | false): v is Visualizer {
return !!v;
}
export const knownVisualizations = new ComposedVisualizer(
[
ENABLED_VISUALIZERS.VisJsGraphVisualizer &&
new (require("./graph-visualizers/VisJsGraphVisualizer") as typeof import("./graph-visualizers/VisJsGraphVisualizer")).VisJsGraphVisualizer(),
ENABLED_VISUALIZERS.TreeVisualizer &&
new (require("./TreeVisualizer/TreeVisualizer") as typeof import("./TreeVisualizer/TreeVisualizer")).TreeVisualizer(),
ENABLED_VISUALIZERS.GraphvizGraphVisualizer &&
new (require("./graph-visualizers/GraphvizGraphVisualizer") as typeof import("./graph-visualizers/GraphvizGraphVisualizer")).GraphvizGraphVisualizer(),
ENABLED_VISUALIZERS.SvgVisualizer &&
new (require("./SvgVisualizer") as typeof import("./SvgVisualizer")).SvgVisualizer(),
ENABLED_VISUALIZERS.GraphvizDotVisualizer &&
new (require("./graph-visualizers/GraphvizDotVisualizer") as typeof import("./graph-visualizers/GraphvizDotVisualizer")).GraphvizDotVisualizer(),
ENABLED_VISUALIZERS.TextVisualizer &&
new (require("./text-visualizers/TextVisualizer") as typeof import("./text-visualizers/TextVisualizer")).TextVisualizer(),
ENABLED_VISUALIZERS.PlotlyVisualizer &&
new (require("./plotly") as typeof import("./plotly")).PlotlyVisualizer(),
ENABLED_VISUALIZERS.GridVisualizer &&
new (require("./GridVisualizer") as typeof import("./GridVisualizer")).GridVisualizer(),
ENABLED_VISUALIZERS.MonacoTextVisualizer &&
new (require("./text-visualizers/MonacoTextVisualizer") as typeof import("./text-visualizers/MonacoTextVisualizer")).MonacoTextVisualizer(),
ENABLED_VISUALIZERS.AstVisualizer &&
new (require("./AstVisualizer") as typeof import("./AstVisualizer")).AstVisualizer(),
].filter(isVisualizer)
);

View File

@@ -1,19 +0,0 @@
import { observer } from "mobx-react";
import * as React from "react";
import Plot from "react-plotly.js";
@observer
export class PlotlyViewer extends React.Component<{
data: Partial<Plotly.Data>[];
}> {
render() {
return (
<Plot
style={{ width: "100%" }}
data={this.props.data}
layout={{}}
config={{ responsive: true }}
/>
);
}
}

View File

@@ -1,30 +0,0 @@
import * as React from "react";
import {
Visualizer,
VisualizationCollector,
asVisualizationId,
} from "../../Visualizer";
import {
ExtractedData,
isCommonDataType,
} from "@hediet/debug-visualizer-data-extraction";
import { makeLazyLoadable } from "../../LazyLoadable";
const PlotlyViewerLazyLoadable = makeLazyLoadable(
async () => (await import("./PlotlyViewer")).PlotlyViewer
);
export class PlotlyVisualizer extends Visualizer {
visualize(data: ExtractedData, collector: VisualizationCollector): void {
if (isCommonDataType(data, { plotly: true })) {
collector.addVisualization({
id: asVisualizationId("plotly"),
name: "plotly",
priority: 1001,
render() {
return <PlotlyViewerLazyLoadable data={data.data} />;
},
});
}
}
}

View File

@@ -1,74 +0,0 @@
.component-monaco-editor {
min-height: 0;
min-width: 0;
height: 100%;
box-sizing: border-box;
display: flex;
// overflow: hidden;
.part-editor {
min-width: 0;
min-height: 0;
flex: 1;
}
}
.component-AstTree {
height: 100%;
display: flex;
flex-direction: column;
.part-tree2 {
flex: 2;
min-height: 0;
min-width: 0;
}
.part-editor {
flex: 1;
min-height: 0;
min-width: 0;
border-top: solid gray 1px;
.marked {
background: orange;
}
.selected {
background: yellow;
opacity: 0.7;
}
}
/*display: flex;
flex-direction: column;
min-height: 0;
min-width: 0;
.part-tree2 {
flex: 1 0;
}
.part-editor {
min-height: 0;
min-width: 0;
flex: 1;
background: red;
}*/
}
.component-Grid {
.part-data {
border: 1px solid lightgray;
background: #eeeeee;
}
.part-header {
background: transparentize(white, 0.5);
}
.part-empty {
color: var(--vscode-editor-foreground);
}
}

View File

@@ -1,128 +0,0 @@
import {
Visualizer,
VisualizationCollector,
asVisualizationId,
} from "../../Visualizer";
import {
ExtractedData,
isCommonDataType,
} from "@hediet/debug-visualizer-data-extraction";
import * as monaco from "monaco-editor";
import * as React from "react";
import { observer, disposeOnUnmount } from "mobx-react";
import { autorun, observable } from "mobx";
export class MonacoTextVisualizer extends Visualizer {
visualize(data: ExtractedData, collector: VisualizationCollector): void {
if (isCommonDataType(data, { text: true })) {
collector.addVisualization({
id: asVisualizationId("monaco-text"),
name: "Monaco Editor",
priority: 95,
render() {
let id = "text";
if (data.fileName) {
id = getLanguageId(data.fileName);
}
return <MonacoEditor text={data.text} languageId={id} />;
},
});
}
}
}
export function getLanguageId(fileName: string): string {
const l = monaco.languages.getLanguages();
const result = l.find(l => {
if (l.filenamePatterns) {
for (const p of l.filenamePatterns) {
if (new RegExp(p).test(fileName)) {
return true;
}
}
}
if (l.extensions) {
for (const p of l.extensions) {
if (fileName.endsWith(p)) {
return true;
}
}
}
return false;
});
if (result) {
return result.id;
}
return "text";
}
@observer
export class MonacoEditor extends React.Component<{
text: string;
languageId: string;
}> {
@observable private editor: monaco.editor.IStandaloneCodeEditor | undefined;
componentWillUnmount() {
if (this.editor) {
this.editor.dispose();
}
}
private model: monaco.editor.ITextModel | undefined = undefined;
@disposeOnUnmount
private _updateText = autorun(() => {
if (this.editor) {
const model = monaco.editor.createModel(
this.props.text,
this.props.languageId,
undefined
);
this.editor.setModel(model);
/* this.editor!.updateOptions({ readOnly: false });
setTimeout(() => {
const a = this.editor!.getAction(
"editor.action.formatDocument"
);
a.run().then(() => {
this.editor!.updateOptions({ readOnly: true });
});
}, 200);*/
if (this.model) {
this.model.dispose();
}
this.model = model;
}
});
private readonly setEditorDiv = (editorDiv: HTMLDivElement) => {
if (!editorDiv) {
return;
}
this.editor = monaco.editor.create(editorDiv, {
model: null,
automaticLayout: true,
scrollBeyondLastLine: false,
minimap: { enabled: false },
fixedOverflowWidgets: true,
readOnly: true,
});
};
render() {
return (
<div className="component-monaco-editor">
<div className="part-editor" ref={this.setEditorDiv} />
</div>
);
}
}

View File

@@ -1,29 +0,0 @@
import {
Visualizer,
VisualizationCollector,
asVisualizationId,
} from "../../Visualizer";
import {
ExtractedData,
isCommonDataType,
} from "@hediet/debug-visualizer-data-extraction";
import * as React from "react";
export class TextVisualizer extends Visualizer {
visualize(data: ExtractedData, collector: VisualizationCollector): void {
if (isCommonDataType(data, { text: true })) {
collector.addVisualization({
id: asVisualizationId("text"),
name: "Plain Text",
priority: 90,
render() {
return (
<pre style={{ marginLeft: 20, marginRight: 20 }}>
{data.text}
</pre>
);
},
});
}
}
}

View File

@@ -1,2 +0,0 @@
@import "./src/visualizers/style.scss";
@import "./src/visualizers/TreeVisualizer/style.scss";

View File

@@ -1,19 +0,0 @@
{
"compilerOptions": {
"target": "ES2020",
"module": "es2020",
"moduleResolution": "node",
"strict": true,
"outDir": "dist",
"skipLibCheck": true,
"rootDir": "./src",
"newLine": "LF",
"sourceMap": true,
"jsx": "react",
"experimentalDecorators": true,
"declaration": true,
"declarationMap": true,
"allowSyntheticDefaultImports": true
},
"include": ["src/**/*"]
}

View File

@@ -4,7 +4,7 @@
"license": "GPL-3.0",
"scripts": {
"dev": "webpack-dev-server --hot",
"build": "webpack"
"build": "node --max_old_space_size=4096 ../node_modules/webpack/bin/webpack.js --mode production"
},
"main": "./index.js",
"dependencies": {
@@ -19,7 +19,11 @@
"react": "^16.12.0",
"react-dom": "^16.12.0",
"react-measure": "^2.3.0",
"@hediet/debug-visualizer-data-extraction": "*"
"@hediet/debug-visualizer-data-extraction": "*",
"@hediet/visualization-bundle": "^0.1.1",
"@hediet/visualization-core": "^0.1.0",
"@knuddels/mobx-logger": "^1.1.1",
"less-loader": "^6.2.0"
},
"devDependencies": {
"@types/html-webpack-plugin": "^3.2.2",
@@ -34,9 +38,8 @@
"fork-ts-checker-webpack-plugin": "^4.0.4",
"html-webpack-plugin": "^3.2.0",
"monaco-editor-webpack-plugin": "^1.9.0",
"node-sass": "^4.13.1",
"raw-loader": "^4.0.0",
"sass": "^1.25.0",
"raw-loader": "^4.0.0",
"sass-loader": "^8.0.2",
"style-loader": "^1.1.3",
"ts-loader": "^6.2.1",

View File

@@ -0,0 +1,26 @@
import * as React from "react";
import { hotComponent } from "../hotComponent";
import { GUI } from "./GUI";
import { Model } from "../model/Model";
@hotComponent(module)
export class App extends React.Component {
private readonly model = new Model();
constructor(props: any) {
super(props);
if (this.model.runningMode !== "webview") {
if (this.model.theme === "light") {
require("../vscode-light.scss");
} else {
require("../vscode-dark.scss");
}
}
console.log(this.model.theme);
}
render() {
return <GUI model={this.model} />;
}
}

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