initial commit

This commit is contained in:
Henning Dieterichs
2020-08-12 23:31:12 +02:00
commit 7c4c9348f3
98 changed files with 15500 additions and 0 deletions

View File

@@ -0,0 +1,270 @@
import * as React from "react";
import { observer, disposeOnUnmount } from "mobx-react";
import { observable, autorun, trace, computed } from "mobx";
import * as monacoTypes from "monaco-editor";
import { getLanguageId } from "../monaco-text-visualizer/MonacoEditor";
import {
TreeWithPathView,
TreeViewModel,
TreeNodeViewModel,
} from "../tree-visualizer/Views";
import { sAstTree } from ".";
import Measure from "react-measure";
import { MonacoEditor, getLoadedMonaco } from "@hediet/monaco-editor-react";
import { Theme } from "@hediet/visualization-core";
export interface NodeInfo {
start: number;
length: number;
}
@observer
export class AstTree extends React.Component<{
model: TreeViewModel<NodeInfo>;
data: typeof sAstTree.T;
nodeInfoToRange: (info: NodeInfo) => monacoTypes.IRange;
posToIndex: (pos: monacoTypes.IPosition) => number;
theme: Theme;
}> {
private readonly editorRef = React.createRef<SourceCodeView>();
render() {
const { model, data, theme } = this.props;
let languageId = "text";
if (data.fileName) {
languageId = getLanguageId(data.fileName);
}
return (
<Measure client>
{({ measureRef, contentRect }) => {
return (
<div
ref={measureRef}
className="component-AstTree"
style={{
height: "100%",
display: "flex",
flexDirection:
contentRect.client &&
contentRect.client.width > 1100
? "row"
: "column",
}}
>
<div
className="part-tree2"
style={{ flex: 1, minHeight: 0, minWidth: 0 }}
>
<TreeWithPathView model={model} />
</div>
<div
className="part-editor"
style={{
flex: 1,
minHeight: 0,
minWidth: 0,
height: "100%",
}}
>
<SourceCodeView
theme={theme}
ref={this.editorRef}
nodeInfoToRange={this.props.nodeInfoToRange}
posToIndex={this.props.posToIndex}
model={model}
languageId={languageId}
text={data.text}
/>
</div>
</div>
);
}}
</Measure>
);
}
}
@observer
export class SourceCodeView extends React.Component<{
text: string;
languageId: string;
model: TreeViewModel<NodeInfo>;
theme: Theme;
nodeInfoToRange: (info: NodeInfo) => monacoTypes.IRange;
posToIndex: (pos: monacoTypes.IPosition) => number;
}> {
@observable private editor:
| monacoTypes.editor.IStandaloneCodeEditor
| undefined;
private markedDecorations: string[] = [];
private selectedDecorations: string[] = [];
constructor(props: any) {
super(props);
}
layout() {
if (this.editor) {
this.editor.layout();
}
}
@computed.struct
private get markedRanges(): monacoTypes.IRange[] {
if (!this.editor || !this.model) {
return [];
}
const editorModel = this.model;
const ranges = this.props.model.marked.map(s =>
this.props.nodeInfoToRange(s.data)
);
return ranges;
}
@disposeOnUnmount
private _updateMarkedDecorations = autorun(
() => {
if (!this.editor) {
return;
}
const ranges = this.markedRanges;
this.markedDecorations = this.editor.deltaDecorations(
this.markedDecorations,
ranges.map(range => ({
range,
options: { className: "marked" },
}))
);
if (ranges.length > 0) {
this.editor.revealRange(
ranges[0],
getLoadedMonaco().editor.ScrollType.Smooth
);
}
},
{ name: "updateMarkedDecorations" }
);
@computed.struct
private get selected(): monacoTypes.IRange | undefined {
if (this.editor && this.model) {
const selected = this.props.model.selected;
if (selected) {
const range = this.props.nodeInfoToRange(selected.data);
return range;
}
}
return undefined;
}
@disposeOnUnmount
private _updateSelectedDecoration = autorun(
() => {
console.log("_updateSelectedDecoration");
if (!this.editor) {
return;
}
const range = this.selected;
if (range) {
this.selectedDecorations = this.editor.deltaDecorations(
this.selectedDecorations,
[
{
range,
options: { className: "selected" },
},
]
);
this.editor.revealRange(
range,
getLoadedMonaco().editor.ScrollType.Smooth
);
} else {
this.selectedDecorations = this.editor.deltaDecorations(
this.selectedDecorations,
[]
);
}
},
{ name: "updateDecorations" }
);
@computed.struct
private get modelData(): { text: string; languageId: string } {
const { text, languageId } = this.props;
return { text, languageId };
}
private lastModel: monacoTypes.editor.ITextModel | undefined;
@computed get model(): monacoTypes.editor.ITextModel {
const last = this.lastModel;
this.lastModel = getLoadedMonaco().editor.createModel(
this.modelData.text,
this.modelData.languageId,
undefined
);
if (last) {
setTimeout(() => {
// we don't want to dispose the current model
last.dispose();
});
}
return this.lastModel;
}
render() {
return (
<MonacoEditor
model={this.model}
theme={this.props.theme.kind === "dark" ? "vs-dark" : "vs"}
onEditorLoaded={e => {
this.editor = e;
e.onDidChangeCursorSelection(e => {
const selectionStart = this.props.posToIndex(
e.selection.getStartPosition()
);
const selectionEnd = this.props.posToIndex(
e.selection.getEndPosition()
);
const findNode = (
m: TreeNodeViewModel<NodeInfo>
): TreeNodeViewModel<NodeInfo> | undefined => {
const nodeStart = m.data.start;
const nodeEnd = nodeStart + m.data.length;
if (
!(
nodeStart <= selectionStart &&
selectionEnd <= nodeEnd
)
) {
return undefined;
}
for (const c of m.children) {
const r = findNode(c);
if (r) {
return r;
}
}
return m;
};
if (this.props.model.root) {
const n = findNode(this.props.model.root);
if (n) {
this.props.model.select(n);
}
}
});
}}
readOnly={true}
/>
);
}
}

View File

@@ -0,0 +1,126 @@
import {
Serializer,
sLazy,
sOpenObject,
sArrayOf,
sString,
sOptionalProp,
sUnion,
sLiteral,
sBoolean,
sProp,
sNumber,
} from "@hediet/semantic-json";
import { visualizationNs } from "../../consts";
import {
createVisualizer,
ReactVisualization,
globalVisualizationFactory,
} from "@hediet/visualization-core";
import { createTreeViewModelFromTreeNodeData } from "../tree-visualizer";
import { AstTree, NodeInfo } from "./AstVisualizer";
import React from "react";
import LineColumn from "line-column";
import * as monacoTypes from "monaco-editor";
import { getLoadedMonaco } from "@hediet/monaco-editor-react";
interface AstTreeNode {
children: AstTreeNode[];
items: Item[];
segment?: string; // like "root", ".name" or "[0]"
isMarked?: boolean;
span: NodeInfo;
}
interface Item {
text: string;
emphasis?: "style1" | "style2" | "style3" | string;
}
export const sAstTreeNode: Serializer<AstTreeNode> = sLazy(() =>
sOpenObject({
children: sArrayOf(sAstTreeNode),
items: sArrayOf(
sOpenObject({
text: sString(),
emphasis: sOptionalProp(
sUnion(
[
sLiteral("style1"),
sLiteral("style2"),
sLiteral("style3"),
sString(),
],
{ inclusive: true }
)
),
}).defineAs(visualizationNs("TreeNodeItem"))
),
segment: sOptionalProp(sString()),
isMarked: sOptionalProp(sBoolean()),
span: sOpenObject({
start: sNumber(),
length: sNumber(),
}),
}).defineAs(visualizationNs("TreeNode"))
);
export const sAstTree = sOpenObject({
kind: sOpenObject({
tree: sLiteral(true),
text: sLiteral(true),
}),
root: sAstTreeNode,
text: sString(),
fileName: sOptionalProp(sString()),
});
export const astVisualizer = createVisualizer({
id: "ast",
name: "AST",
serializer: sAstTree,
getVisualization: (data, self) =>
new ReactVisualization(self, { priority: 1500 }, theme => {
const m = createTreeViewModelFromTreeNodeData(
data.root,
node => node.span
);
const l = LineColumn(data.text);
function translatePosition(pos: number): monacoTypes.IPosition {
let r = l.fromIndex(pos);
if (!r) {
r = l.fromIndex(data.text.length - 1);
r.col++;
}
return {
column: r.col,
lineNumber: r.line,
};
}
const nodeInfoToRange = (info: NodeInfo): monacoTypes.IRange => {
const start = translatePosition(info.start);
const end = translatePosition(info.start + info.length);
const range = getLoadedMonaco().Range.fromPositions(start, end);
return range;
};
function posToIndex(pos: monacoTypes.IPosition): number {
const i = l.toIndex(pos.lineNumber, pos.column);
if (i == -1) {
return data.text.length;
}
return i;
}
return (
<AstTree
model={m}
theme={theme}
data={data}
nodeInfoToRange={nodeInfoToRange}
posToIndex={posToIndex}
/>
);
}),
});
globalVisualizationFactory.addVisualizer(astVisualizer);

View File

@@ -0,0 +1,23 @@
.theme-dark {
--visualizer-ast-marked-background: rgba(234, 92, 0, 0.33);
--visualizer-ast-selected-background: #515c6a;
}
.theme-light {
--visualizer-ast-marked-background: orange;
--visualizer-ast-selected-background: yellow;
}
.component-AstTree {
.part-editor {
//border-top: solid gray 1px;
.marked {
background: var(--visualizer-ast-marked-background);
}
.selected {
background: var(--visualizer-ast-selected-background);
opacity: 0.7;
}
}
}

View File

@@ -0,0 +1,13 @@
declare module "line-column" {
export = function(
text: string
): {
fromIndex(
idx: number
): {
line: number;
col: number;
};
toIndex(line: number, column: number): number;
} {};
}