package modules
import (
"html/template"
"strings"
"github.com/alecthomas/chroma/v2"
"github.com/alecthomas/chroma/v2/lexers"
)
// maxHighlightSize bounds how much source chroma will tokenise. Past this,
// a file falls back to plain (still viewable, just uncoloured) text rather
// than risk a slow lex on something pathological.
const maxHighlightSize = 512 * 1024
// highlightLines lexes src by the language implied by path and returns one
// escaped, span-wrapped HTML fragment per line. ok is false when path has no
// recognised language or src is too large to bother lexing; callers should
// fall back to plain escaped lines in that case.
func highlightLines(path string, src []byte) (lines []template.HTML, ok bool) {
if len(src) > maxHighlightSize {
return nil, false
}
lexer := lexers.Match(path)
if lexer == nil {
return nil, false
}
it, err := chroma.Coalesce(lexer).Tokenise(nil, string(src))
if err != nil {
return nil, false
}
for _, toks := range chroma.SplitTokensIntoLines(it.Tokens()) {
var b strings.Builder
for i, tok := range toks {
// SplitTokensIntoLines keeps each line's newline attached to
// its last token; drop it so it doesn't render as a blank
// row inside the (white-space: pre) table cell.
if i == len(toks)-1 {
tok.Value = strings.TrimSuffix(tok.Value, "\n")
}
esc := template.HTMLEscapeString(tok.Value)
if cls := tokenClass(tok.Type); cls != "" {
b.WriteString(``)
b.WriteString(esc)
b.WriteString(``)
} else {
b.WriteString(esc)
}
}
lines = append(lines, template.HTML(b.String()))
}
return lines, true
}
// tokenClass buckets chroma's ~70 token types down to the handful of colours
// the theme actually defines (see --syn-* in base.css). Punctuation,
// operators and plain names are left uncoloured on purpose -- a source line
// with every token tinted reads as noise, not signal.
func tokenClass(t chroma.TokenType) string {
switch {
case t.InCategory(chroma.Comment):
return "tok-c"
case t.InCategory(chroma.Keyword):
return "tok-k"
case t.InSubCategory(chroma.LiteralString):
return "tok-s"
case t.InSubCategory(chroma.LiteralNumber):
return "tok-n"
case t == chroma.NameFunction, t == chroma.NameFunctionMagic,
t == chroma.NameClass, t == chroma.NameNamespace,
t == chroma.NameBuiltin, t == chroma.NameBuiltinPseudo,
t == chroma.NameTag, t == chroma.NameDecorator:
return "tok-fn"
default:
return ""
}
}