aboutsummaryrefslogtreecommitdiff
path: root/www/index.js
blob: 8a4971d1ef163c45f74f6b5c628da6d02c7d469f (plain)
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();