1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
|
import {SynNode} from "cstea";
import {EditorState, EditorView, basicSetup} from "@codemirror/basic-setup"
import {Decoration, DecorationSet} from "@codemirror/view"
import {StateField, StateEffect} from "@codemirror/state"
import {oneDark, oneDarkTheme, oneDarkHighlightStyle} from "@codemirror/theme-one-dark"
import file from '!raw-loader!../flake.nix'
let cst = document.getElementById('cst');
let view = new EditorView({
state: EditorState.create({
doc: file,
extensions: [
basicSetup,
EditorView.updateListener.of((v) => {
if (v.docChanged) {
doRender()
}
}),
oneDark,
oneDarkTheme,
oneDarkHighlightStyle.extension
]
}),
parent: document.getElementById('source-code'),
})
const doHighlight = StateEffect.define()
const highlightField = StateField.define({
create() {
return Decoration.none;
},
update(highlight, tr) {
for (let e of tr.effects) if (e.is(doHighlight)) {
return (Decoration.none).update({
add: [hlMark.range(e.value.from, e.value.to)]
});
}
return Decoration.none;
},
provide: f => EditorView.decorations.from(f)
})
const hlMark = Decoration.mark({class: "cm-highlight"})
const hlTheme = EditorView.baseTheme({
".cm-highlight": {
backgroundColor: "#ff3299aa"
}
})
function highlightArea(view, textRange) {
let effects = [doHighlight.of({from: textRange.start(), to: textRange.end()})];
if (!view.state.field(highlightField, false)) {
effects.push(StateEffect.appendConfig.of([highlightField, hlTheme]));
}
view.dispatch({effects});
}
function render_cst(synRoot) {
let nodeDiv = document.createElement("div");
nodeDiv.className = "syntax-node";
let r = synRoot.range();
let synText = synTextHtml(synRoot);
synText.onmouseover = () => {
highlightArea(view, r);
let sourceFile = view.state.doc.toString();
view.scrollPosIntoView(r.start());
}
nodeDiv.appendChild(synText);
if (!synRoot.is_token()) {
synRoot.children().forEach(node => {
nodeDiv.appendChild(render_cst(node));
});
}
return nodeDiv;
}
function synTextHtml(node) {
let kind = document.createElement("span");
kind.innerText = ` ${node.kind()} `
kind.className = "kind";
let text = document.createElement("span");
text.innerText = ` ${node.text()} `
text.className = "token-text";
let range = document.createElement("span");
range.innerText = ` ${node.range().to_string()} `
range.className = "range";
let d = document.createElement("div");
d.appendChild(kind);
d.appendChild(text);
d.appendChild(range);
return d;
}
function wrap(s, tag) {
let t = document.createElement(tag);
t.innerText = s;
return t;
}
function render_err(errorList) {
let errDiv = document.createElement("div");
errDiv.className = "syntax-err";
errorList.forEach(err => {
let sourceFile = view.state.doc.toString();
errDiv.appendChild(wrap(err.to_string(), "pre"));
// highlightArea(view, err.range());
});
return errDiv;
}
function doRender() {
let sourceFile = view.state.doc.toString();
cst.innerHTML = "";
try {
let synRoot = SynNode.from_str(sourceFile);
cst.appendChild(render_cst(synRoot));
} catch (synError) {
cst.appendChild(render_err(synError));
}
}
doRender();
|