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 "" } }