Compare commits
19 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
453e0ae5a8 | ||
|
|
08d97c2a82 | ||
|
|
e487dead95 | ||
|
|
8ce8e7dad8 | ||
|
|
41747c5dd0 | ||
|
|
1dd0349483 | ||
|
|
62415f1775 | ||
|
|
b23b90c8bf | ||
|
|
24aa6857e5 | ||
|
|
7ba2428819 | ||
|
|
f09e345908 | ||
|
|
c56f21d8ab | ||
|
|
5964c1c503 | ||
|
|
e5707a0f2e | ||
|
|
d0f1a0144e | ||
|
|
83b93b1369 | ||
|
|
39de71521b | ||
|
|
51a42eb497 | ||
|
|
ac90389aa1 |
1
.github/FUNDING.yml
vendored
Normal file
1
.github/FUNDING.yml
vendored
Normal file
@@ -0,0 +1 @@
|
||||
github: hediet
|
||||
23
.github/workflows/ci.yml
vendored
Normal file
23
.github/workflows/ci.yml
vendored
Normal 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
|
||||
15
.github/workflows/opened-issues-triage.yml
vendored
Normal file
15
.github/workflows/opened-issues-triage.yml
vendored
Normal 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
8
.vscode/launch.json
vendored
@@ -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": "",
|
||||
|
||||
3
.vscode/settings.json
vendored
3
.vscode/settings.json
vendored
@@ -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"
|
||||
}
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
# VS Code Debug Visualizer
|
||||
|
||||
[](https://github.com/sponsors/hediet)
|
||||
[](https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=ZP5F38L4C88UY&source=url)
|
||||
[](https://twitter.com/intent/follow?screen_name=hediet_dev)
|
||||
|
||||
See [README.md](./extension/README.md) for the readme of the extension.
|
||||
|
||||
@@ -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": {
|
||||
|
||||
@@ -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;
|
||||
};
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
@@ -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 })) }],
|
||||
};
|
||||
},
|
||||
}),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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;
|
||||
},
|
||||
}),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
};
|
||||
});
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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(),
|
||||
]) {
|
||||
|
||||
@@ -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,
|
||||
},
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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,
|
||||
|
||||
3
data-extraction/src/util.ts
Normal file
3
data-extraction/src/util.ts
Normal file
@@ -0,0 +1,3 @@
|
||||
export function expect<T>(data: T): T {
|
||||
return data;
|
||||
}
|
||||
@@ -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"
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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)
|
||||
|
||||
BIN
extension/docs/logo.drawio.png
Normal file
BIN
extension/docs/logo.drawio.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 5.0 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 2.8 KiB |
@@ -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",
|
||||
|
||||
@@ -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";
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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: {
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
70
extension/src/debugger/EnhancedDebugSession.ts
Normal file
70
extension/src/debugger/EnhancedDebugSession.ts
Normal 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;
|
||||
}
|
||||
116
extension/src/debugger/VsCodeDebugger.ts
Normal file
116
extension/src/debugger/VsCodeDebugger.ts
Normal 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;
|
||||
}
|
||||
}
|
||||
},
|
||||
};
|
||||
},
|
||||
}),
|
||||
]);
|
||||
}
|
||||
}
|
||||
48
extension/src/debugger/VsCodeDebuggerView.ts
Normal file
48
extension/src/debugger/VsCodeDebuggerView.ts
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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() {}
|
||||
|
||||
120
extension/src/utils/VsCodeSettings.ts
Normal file
120
extension/src/utils/VsCodeSettings.ts
Normal 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();
|
||||
});
|
||||
});
|
||||
@@ -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>
|
||||
@@ -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> {
|
||||
@@ -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`;
|
||||
}
|
||||
|
||||
@@ -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>()),
|
||||
}),
|
||||
}),
|
||||
},
|
||||
});
|
||||
@@ -1,2 +0,0 @@
|
||||
require("ts-node").register();
|
||||
module.exports = require("./webpack.config.ts");
|
||||
@@ -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"],
|
||||
|
||||
3813
extension/yarn.lock
3813
extension/yarn.lock
File diff suppressed because it is too large
Load Diff
11
package.json
11
package.json
@@ -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"
|
||||
|
||||
@@ -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"
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
});
|
||||
}
|
||||
@@ -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";
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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"
|
||||
);
|
||||
}),
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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/**/*"]
|
||||
}
|
||||
@@ -1,2 +0,0 @@
|
||||
require("ts-node").register();
|
||||
module.exports = require("./webpack.config.ts");
|
||||
@@ -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
@@ -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"
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,7 +0,0 @@
|
||||
import * as React from "react";
|
||||
|
||||
export class VisualizerView extends React.Component {
|
||||
render(): React.ReactNode {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -1,4 +0,0 @@
|
||||
export * from "./visualizers";
|
||||
export * from "./Visualizer";
|
||||
export * from "./VisualizerView";
|
||||
export * from "./EnabledVisualizers";
|
||||
22
visualization/src/types.d.ts
vendored
22
visualization/src/types.d.ts
vendored
@@ -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;
|
||||
};
|
||||
} {};
|
||||
}
|
||||
@@ -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)
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -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"])}
|
||||
/>
|
||||
);
|
||||
}
|
||||
@@ -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>
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
};
|
||||
}
|
||||
@@ -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>
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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>
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -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");
|
||||
}
|
||||
}
|
||||
@@ -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} />;
|
||||
}
|
||||
}
|
||||
@@ -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} />;
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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}
|
||||
/>
|
||||
);
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
);
|
||||
@@ -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 }}
|
||||
/>
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -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} />;
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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>
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -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>
|
||||
);
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,2 +0,0 @@
|
||||
@import "./src/visualizers/style.scss";
|
||||
@import "./src/visualizers/TreeVisualizer/style.scss";
|
||||
@@ -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/**/*"]
|
||||
}
|
||||
@@ -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",
|
||||
|
||||
26
webview/src/components/App.tsx
Normal file
26
webview/src/components/App.tsx
Normal 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} />;
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
|
||||
@@ -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 />;
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
}
|
||||
|
||||
49
webview/src/hotComponent.tsx
Normal file
49
webview/src/hotComponent.tsx
Normal 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;
|
||||
};
|
||||
}
|
||||
@@ -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
Reference in New Issue
Block a user