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