// render.go — source to HTML: tree-sitter highlighting, coverage classes and // note anchors baked into one
    per chunk. package main import ( "bytes" "html/template" "sort" "strconv" "strings" sitter "github.com/tree-sitter/go-tree-sitter" tsrust "github.com/tree-sitter/tree-sitter-rust/bindings/go" ) // Lines per content-visibility chunk: the browser skips layout and paint of // offscreen chunks wholesale, instead of managing one containment context // per line. const chunkLines = 800 // approximate rendered line height, for contain-intrinsic-size placeholders const lineHeightPx = 19 // render builds the code HTML:
      chunks of chunkLines lines, each line an //
    1. with syntax spans, coverage classes and note anchors. One pre-sized // builder, no fmt, minimal escaping — large files are browser-bound, so the // output is kept as small and flat as possible. func (f *File) render(src []byte, hl *highlighter) { classes := hl.classify(f.Path, src) f.Total = bytes.Count(src, []byte{'\n'}) if len(src) > 0 && src[len(src)-1] != '\n' { f.Total++ } f.Covered = 0 for i := 1; i < len(f.Cov) && i <= f.Total; i++ { if f.Cov[i] != 0 { f.Covered++ } } if f.Total > 0 { f.Pct = f.Covered * 100 / f.Total } sort.Slice(f.Notes, func(i, j int) bool { return f.Notes[i].Start < f.Notes[j].Start }) for ni, n := range f.Notes { for l := n.Start; l <= n.End && l > 0; l++ { if _, taken := f.NoteAt[l]; !taken { f.NoteAt[l] = ni } } } var b strings.Builder b.Grow(len(src) + len(src)/2 + f.Total*48) var num []byte writeInt := func(n int) { num = strconv.AppendInt(num[:0], int64(n), 10); b.Write(num) } start := 0 for ln := 1; ln <= f.Total; ln++ { end := start for end < len(src) && src[end] != '\n' { end++ } if (ln-1)%chunkLines == 0 { if ln > 1 { b.WriteString("
    ") } rem := f.Total - (ln - 1) if rem > chunkLines { rem = chunkLines } b.WriteString(`
      `) } b.WriteString(`
    1. ') emitLine(&b, src[start:end], classes[start:end], hl.classNames) b.WriteString("
    2. ") start = end + 1 } if f.Total > 0 { b.WriteString("
    ") } f.Body = template.HTML(b.String()) } // emitLine writes one line's spans, merging runs of the same class across // whitespace-only gaps ("pub fn" is one span, not two) to keep the DOM flat. func emitLine(b *strings.Builder, line []byte, cls []int16, names []string) { open := int16(0) i := 0 for i < len(line) { j := i c := cls[i] for j < len(line) && cls[j] == c { j++ } if c == 0 { allSpace := true for k := i; k < j; k++ { if line[k] != ' ' && line[k] != '\t' { allSpace = false break } } if !(allSpace && open != 0 && j < len(line) && cls[j] == open) { if open != 0 { b.WriteString("") open = 0 } } escapeTo(b, line[i:j]) } else { if open != c { if open != 0 { b.WriteString("") } b.WriteString(``) open = c } escapeTo(b, line[i:j]) } i = j } if open != 0 { b.WriteString("") } } // escapeTo escapes the three characters that matter in text content. func escapeTo(b *strings.Builder, s []byte) { last := 0 for i, c := range s { var rep string switch c { case '&': rep = "&" case '<': rep = "<" case '>': rep = ">" default: continue } b.Write(s[last:i]) b.WriteString(rep) last = i + 1 } b.Write(s[last:]) } // ── highlighting ────────────────────────────────────────────────────────── type highlighter struct { lang *sitter.Language query *sitter.Query captureCls []int16 // capture index -> class id classNames []string } // resolveKey finds the theme syntax key for a capture name by stripping dot // segments; "" when the theme has no entry at all. func resolveKey(t *Theme, name string) string { for key := name; key != ""; { if s, ok := t.Syntax[key]; ok && s.Color != "" { return key } if i := strings.LastIndex(key, "."); i >= 0 { key = key[:i] } else { break } } return "" } // nearFg reports whether a capture would render indistinguishably from plain // foreground text in the theme. Spans for such captures are pure DOM weight. func nearFg(t *Theme, name string) bool { key := resolveKey(t, name) if key == "" { return true // no color -> falls through to fg anyway } return colorDist(hex6(t.Syntax[key].Color), hex6(t.Fg)) < 2000 } func newHighlighter(dark, light *Theme) *highlighter { h := &highlighter{classNames: []string{""}} h.lang = sitter.NewLanguage(tsrust.Language()) scm, _ := tfs.ReadFile("assets/rust.scm") q, qerr := sitter.NewQuery(h.lang, string(scm)) if qerr != nil { fatal("rust query: %v", qerr) } h.query = q for _, name := range q.CaptureNames() { // skip captures that both themes paint (nearly) as foreground — // punctuation and friends are the bulk of all spans if nearFg(dark, name) && nearFg(light, name) { h.captureCls = append(h.captureCls, 0) continue } key := resolveKey(dark, name) if key == "" { key = resolveKey(light, name) } cls := strings.ReplaceAll(key, ".", "-") id := int16(0) for i, n := range h.classNames { if n == cls { id = int16(i) } } if id == 0 { h.classNames = append(h.classNames, cls) id = int16(len(h.classNames) - 1) } h.captureCls = append(h.captureCls, id) } return h } type paint struct { start, end uint pattern uint cls int16 } // classify is safe for concurrent use: the shared query is immutable, and // parser and cursor are per-call. func (h *highlighter) classify(path string, src []byte) []int16 { classes := make([]int16, len(src)) if !strings.HasSuffix(path, ".rs") { return classes } parser := sitter.NewParser() defer parser.Close() parser.SetLanguage(h.lang) tree := parser.Parse(src, nil) defer tree.Close() qc := sitter.NewQueryCursor() defer qc.Close() matches := qc.Matches(h.query, tree.RootNode(), src) var paints []paint for m := matches.Next(); m != nil; m = matches.Next() { for _, c := range m.Captures { if cls := h.captureCls[c.Index]; cls != 0 { paints = append(paints, paint{c.Node.StartByte(), c.Node.EndByte(), m.PatternIndex, cls}) } } } // later patterns in the query paint over earlier ones sort.Slice(paints, func(i, j int) bool { return paints[i].pattern < paints[j].pattern }) for _, p := range paints { for i := p.start; i < p.end && int(i) < len(classes); i++ { classes[i] = p.cls } } return classes }