19 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
106 changed files with 7243 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"

View File

@@ -1,5 +1,12 @@
# 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.

View File

@@ -9,7 +9,7 @@ Like the VS Code's watch view, but with custom visualizations.
## Supported Languages
See [demos](./demos/) for demos. These languages and debuggers are verified to work with this extension:
See [demos](../demos/) for demos. These languages and debuggers are verified to work with this extension:
- JavaScript/TypeScript/... using `node`/`node2`/`extensionHost` or `chrome` debug adapter: [⭐ Full Support](../demos/js)
- Go using `go` (Delve) debug adapter: [✅ Rudimentary Support](../demos/golang)

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.8 KiB

View File

@@ -2,12 +2,12 @@
"name": "debug-visualizer",
"private": true,
"displayName": "Debug Visualizer",
"description": "An extension that visualizes values while debugging.",
"icon": "docs/logo.png",
"version": "1.1.0",
"description": "A visual watch window that lets you visualize your data structures while debugging.",
"icon": "docs/logo.drawio.png",
"version": "2.0.0",
"license": "GPL-3.0",
"engines": {
"vscode": "^1.35.0"
"vscode": "^1.46.0"
},
"publisher": "hediet",
"keywords": [
@@ -17,7 +17,8 @@
"visual",
"javascript",
"graph",
"ast"
"ast",
"visualization"
],
"bugs": {
"url": "https://github.com/hediet/vscode-debug-visualizer/issues"
@@ -107,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",
@@ -115,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,

View File

@@ -5,26 +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);
@@ -36,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,
@@ -75,10 +83,13 @@ class JsEvaluator implements Evaluator {
const reply = await this.session.evaluate({
expression: wrappedExpr,
frameId,
context: "repl",
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") {
@@ -117,7 +128,7 @@ class JsEvaluator implements Evaluator {
await this.session.evaluate({
expression,
frameId,
context: "repl",
context: this.getContext(),
});
return true;

View File

@@ -1,8 +1,8 @@
import {
DataExtractionResult,
isExtractedData,
isVisualizationData,
} from "@hediet/debug-visualizer-data-extraction";
import { FormattedMessage } from "../../contract";
import { FormattedMessage } from "../../webviewContract";
export interface ParseEvaluationResultContext {
debugAdapterType: string;
@@ -40,7 +40,7 @@ export function parseEvaluationResultFromGenericDebugAdapter(
// result is now { kind: { ... }, text: "some\ntext" }
}
if (!isExtractedData(resultObj)) {
if (!isVisualizationData(resultObj)) {
return {
kind: "error",
message: {

View File

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

View File

@@ -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,7 +36,7 @@ export class WebViews {
}
public createNew(expression: string | undefined = undefined) {
const panel = window.createWebviewPanel(
const webviewPanel = window.createWebviewPanel(
debugVisualizer,
"Debug Visualizer",
ViewColumn.Two,
@@ -44,33 +52,38 @@ export class WebViews {
}
);
this.setupView(panel, expression);
this.initializeView(webviewPanel, expression);
}
public restore(webviewPanel: WebviewPanel) {
this.setupView(webviewPanel);
private restore(webviewPanel: WebviewPanel) {
this.initializeView(webviewPanel);
}
private setupView(
private initializeView(
webviewPanel: WebviewPanel,
expression: string | undefined = undefined
) {
webviewPanel.webview.html = getHtml(this.server, expression);
const view = new WebView(webviewPanel);
this.debugVisualizations.set(webviewPanel, view);
webviewPanel.webview.html = getDebugVisualizerWebviewHtml(
this.server,
expression,
this.config
);
const view = new DebugVisualizerWebview(webviewPanel);
this.openedWebviews.set(webviewPanel, view);
webviewPanel.onDidDispose(() => {
this.debugVisualizations.delete(webviewPanel);
this.openedWebviews.delete(webviewPanel);
});
}
}
export class WebView {
export class DebugVisualizerWebview {
constructor(private readonly webviewPanel: WebviewPanel) {}
}
export function getHtml(
server: Server,
expression: string | undefined = undefined
function getDebugVisualizerWebviewHtml(
server: WebviewServer,
initialExpression: string | undefined = undefined,
config: Config
) {
const isDev = !!process.env.USE_DEV_UI;
return `
@@ -86,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]) {
@@ -109,10 +125,13 @@ export function getHtml(
${
isDev
? `<iframe sandbox="allow-top-navigation allow-scripts allow-same-origin allow-popups allow-pointer-lock allow-forms" src="${server.getIndexUrl(
{ mode: "webViewIFrame", expression }
? `<iframe sandbox="allow-top-navigation allow-scripts allow-same-origin allow-popups allow-pointer-lock allow-forms" src="${server.getWebviewPageUrl(
{
mode: "webviewIFrame",
expression: initialExpression,
}
)}"></iframe>`
: `<script type="text/javascript" src="${server.mainBundleUrl}"></script>`
: `<script type="text/javascript" src="${server.webviewBundleUrl}"></script>`
}
</body>
</html>

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 {
export class WebviewServer {
private readonly server: http.Server;
public readonly secret = cryptoRandomString({ length: 20 });
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,16 +3,13 @@
"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} />;
}
}

View File

@@ -103,19 +103,20 @@ export class VisualizerHeaderMain extends React.Component<{ model: Model }> {
<ExpressionInput model={m} />
</div>
<div style={{ width: 4 }} />
{m.loading ? (
<div style={{ padding: "0 4px" }}>
<Spinner size={Spinner.SIZE_SMALL} />
</div>
) : (
<Button
minimal
small
className="part-Icon"
icon="refresh"
onClick={() => m.refresh()}
/>
)}
{!m.isPolling &&
(m.loading ? (
<div style={{ padding: "0 4px" }}>
<Spinner size={Spinner.SIZE_SMALL} />
</div>
) : (
<Button
minimal
small
className="part-Icon"
icon="refresh"
onClick={() => m.refresh()}
/>
))}
<Button
minimal
small

View File

@@ -2,21 +2,19 @@ import * as React from "react";
import { Model } from "../model/Model";
import { observer } from "mobx-react";
import { NoData } from "./NoData";
import { FormattedMessage } from "debug-visualizer/src/contract";
import { FormattedMessage } from "debug-visualizer/src/webviewContract";
import { VisualizationView, Theme } from "@hediet/visualization-core";
@observer
export class Visualizer extends React.Component<{ model: Model }> {
render() {
return (
<div className="component-Visualizer">{this.renderContent()}</div>
<div style={{ width: "100%", height: "100%", overflow: "hidden" }}>
{this.renderContent()}
</div>
);
}
componentDidCatch() {
this.props.model.setVisualizationError({ kind: {} });
console.log("broken");
}
renderContent(): JSX.Element {
const s = this.props.model.state;
if (s.kind === "loading") {
@@ -38,20 +36,16 @@ export class Visualizer extends React.Component<{ model: Model }> {
if (!vis || !vis.visualization) {
return <NoData>No Visualization Available</NoData>;
}
try {
return vis.visualization.render();
} catch (e) {
console.error(e);
return (
<NoData>
<>
Visualization Error
<pre>{e.message}</pre>
</>
</NoData>
);
}
return (
<VisualizationView
theme={
this.props.model.theme === "dark"
? Theme.dark
: Theme.light
}
visualization={vis.visualization}
/>
);
} else {
const nvr: never = s;
return <div />;

View File

@@ -1,10 +1,10 @@
import * as React from "react";
import { Popover } from "@blueprintjs/core";
import { Popover, Checkbox } from "@blueprintjs/core";
import { DataExtractorInfo } from "@hediet/debug-visualizer-data-extraction";
import { computed } from "mobx";
import { observer } from "mobx-react";
import { Model } from "../model/Model";
import { VisualizationId } from "@hediet/visualization";
import { VisualizationId } from "@hediet/visualization-core";
@observer
export class VisualizerHeaderDetails extends React.Component<{ model: Model }> {
@@ -80,6 +80,12 @@ export class VisualizerHeaderDetails extends React.Component<{ model: Model }> {
: []
}
/>
<div style={{ width: 10 }} />
<Checkbox
label="Poll"
checked={m.isPolling}
onChange={e => m.setPolling(e.currentTarget.checked)}
/>
</div>
);
}

View File

@@ -0,0 +1,49 @@
import Module = require("module");
import { observer } from "mobx-react";
import * as React from "react";
import { observable, runInAction } from "mobx";
type ReactComponent<P = any> =
| React.ComponentClass<P>
| React.FunctionComponent<P>;
const allComponents = new Map<string, { component: ReactComponent }>();
export function hotComponent(
module: Module
): <T extends ReactComponent>(Component: T) => T {
return <T extends ReactComponent>(component: T): T => {
const key = JSON.stringify({ id: module.id, name: component.name });
let result = allComponents.get(key);
if (!result) {
result = observable({ component: component });
allComponents.set(key, result);
} else {
setTimeout(() => {
runInAction(`Update Component ${component.name}`, () => {
result!.component = component;
});
}, 0);
}
const m = module as {
hot?: {
accept: ((
componentName: string,
callback: () => void
) => void) &
((callback: () => void) => void);
};
};
if (m.hot) {
m.hot.accept(() => {});
}
return observer((props: any) => {
const C = result!.component;
return <C {...props} />;
}) as any;
};
}

View File

@@ -1,37 +1,9 @@
import * as React from "react";
import * as ReactDOM from "react-dom";
import "./style.scss";
import { Model } from "./model/Model";
import Components = require("./components/GUI");
import { App } from "./components/App";
const model = new Model();
if (model.runningMode !== "webView") {
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);
});
}
const elem = document.createElement("div");
elem.className = "react-root";
document.body.append(elem);
ReactDOM.render(<App />, elem);

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