forked from argotorg/fe
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathfe-highlighter.js
More file actions
159 lines (137 loc) · 4.96 KB
/
Copy pathfe-highlighter.js
File metadata and controls
159 lines (137 loc) · 4.96 KB
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
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
// fe-highlighter.js — Client-side tree-sitter syntax highlighting for Fe code.
//
// Provides window.FeHighlighter singleton:
// init() — async, loads WASM + compiles query
// isReady() — synchronous readiness check
// highlightFe(source) — returns highlighted HTML string (pure syntax coloring)
//
// WASM binaries and highlights.scm are injected as template placeholders
// by the Rust build (base64-encoded). No network fetches needed.
//
// Type linking and hover interactivity are handled separately by
// fe-code-block.js using ScipStore — the highlighter only does coloring.
(function () {
"use strict";
var TS_WASM_B64 = "%%TS_WASM_B64%%";
var FE_WASM_B64 = "%%FE_WASM_B64%%";
var HIGHLIGHTS_SCM = "%%HIGHLIGHTS_SCM%%";
var parser = null;
var query = null;
var ready = false;
function b64ToUint8(b64) {
var bin = atob(b64);
var arr = new Uint8Array(bin.length);
for (var i = 0; i < bin.length; i++) arr[i] = bin.charCodeAt(i);
return arr;
}
function escHtml(s) {
return s.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">");
}
async function init() {
if (ready) return;
var tsWasm = b64ToUint8(TS_WASM_B64);
await TreeSitter.init({ wasmBinary: tsWasm });
parser = new TreeSitter();
var feWasm = b64ToUint8(FE_WASM_B64);
var feLang = await TreeSitter.Language.load(feWasm);
parser.setLanguage(feLang);
query = feLang.query(HIGHLIGHTS_SCM);
ready = true;
document.dispatchEvent(new CustomEvent("fe-highlighter-ready"));
}
function isReady() {
return ready;
}
/**
* Pad a code fragment with stub syntax so tree-sitter can produce a proper
* AST instead of ERROR nodes. The caller only uses captures within the
* original source length, so the padding is invisible in the output.
*
* Returns { source: paddedString, offset: charsAddedBefore }.
*/
function padForParse(source) {
var s = source.trimEnd();
if (s.indexOf("{") !== -1) return { source: source, offset: 0 };
// fn signatures containing Self need an impl wrapper so tree-sitter
// recognizes Self as self_type rather than a plain identifier.
if (/\bfn\b/.test(s) && /\bSelf\b/.test(s)) {
var prefix = "impl X { ";
return { source: prefix + s + " {} }", offset: prefix.length };
}
// Other signatures (trait, struct, enum, impl, fn) just need a body
if (/\b(trait|struct|enum|contract|impl|fn)\b/.test(s)) {
return { source: s + " {}", offset: 0 };
}
return { source: source, offset: 0 };
}
/**
* Parse and highlight Fe source code (pure syntax coloring).
*
* @param {string} source — raw Fe code
* @returns {string} HTML with <span class="hl-*"> elements
*/
function highlightFe(source) {
if (!ready) return escHtml(source);
var padded = padForParse(source);
var tree = parser.parse(padded.source);
var captures = query.captures(tree.rootNode);
var offset = padded.offset;
// Eagerly read startIndex/endIndex from each capture node BEFORE deleting
// the tree. In web-tree-sitter, endIndex is a lazy getter that reads WASM
// memory — it returns garbage after tree.delete().
var capData = new Array(captures.length);
for (var ci = 0; ci < captures.length; ci++) {
var cap = captures[ci];
capData[ci] = {
si: cap.node.startIndex - offset,
ei: cap.node.endIndex - offset,
name: cap.name
};
}
tree.delete();
// Sort captures by startIndex, then by length descending (outermost first).
// For overlapping captures, innermost (shortest) wins — we process outermost
// first but let innermost overwrite.
capData.sort(function (a, b) {
var d = a.si - b.si;
if (d !== 0) return d;
return (b.ei - b.si) - (a.ei - a.si);
});
// Build an array of character-level capture assignments.
// Only covers original source length — padding captures are ignored.
var len = source.length;
var charCapture = new Array(len);
for (var ci = 0; ci < capData.length; ci++) {
var cd = capData[ci];
for (var k = Math.max(0, cd.si); k < cd.ei && k < len; k++) {
charCapture[k] = cd.name;
}
}
// Walk through source, grouping contiguous runs of the same capture.
var html = "";
var pos = 0;
while (pos < len) {
var capName = charCapture[pos];
var runEnd = pos + 1;
while (runEnd < len && charCapture[runEnd] === capName) runEnd++;
var text = source.slice(pos, runEnd);
if (!capName) {
html += escHtml(text);
} else {
var cssClass = "hl-" + capName.replace(/\./g, "-");
html += '<span class="' + cssClass + '">' + escHtml(text) + "</span>";
}
pos = runEnd;
}
return html;
}
window.FeHighlighter = {
init: init,
isReady: isReady,
highlightFe: highlightFe,
};
// Auto-init on load
init().catch(function (e) {
console.error("[fe-highlighter] init failed:", e);
});
})();