package editor import ( "strings" "glint/internal/theme" "github.com/alecthomas/chroma/v2" "github.com/alecthomas/chroma/v2/lexers" "github.com/charmbracelet/lipgloss" ) // ScanCode styles source code minimally (Alabaster philosophy): only strings, // comments, and numbers/literals get color; keywords, names, and operators stay // at base Text; punctuation is Muted. It returns one []Span per input line whose // concatenated text equals the line exactly (the markup-visible invariant), with // multi-line tokens (block comments, multi-line strings) split across lines. // // filename selects the chroma lexer; an unrecognized file falls back to a plain // lexer (everything base). On a tokenizer error it degrades to the prose scanner. func ScanCode(lines []string, filename string, th theme.Theme) [][]Span { lexer := lexers.Match(filename) if lexer == nil { lexer = lexers.Fallback } lexer = chroma.Coalesce(lexer) it, err := lexer.Tokenise(nil, strings.Join(lines, "\n")) if err != nil { return ScanLines(lines, th) } out := make([][]Span, len(lines)) li := 0 var cur []Span emit := func(text string, style lipgloss.Style) { if text != "" { cur = append(cur, Span{Text: text, Style: style}) } } for _, tok := range it.Tokens() { style := codeStyle(tok.Type, th) val := tok.Value for { nl := strings.IndexByte(val, '\n') if nl < 0 { emit(val, style) break } emit(val[:nl], style) if li < len(out) { out[li] = cur } cur = nil li++ val = val[nl+1:] } } if li < len(out) { out[li] = cur } return out } // codeStyle maps a chroma token type to a theme color, coloring only strings, // comments, and numbers/literals; everything else stays base, punctuation muted. func codeStyle(tt chroma.TokenType, th theme.Theme) lipgloss.Style { switch { case tt.SubCategory() == chroma.String: return lipgloss.NewStyle().Foreground(th.Code) case tt.Category() == chroma.Comment: return lipgloss.NewStyle().Foreground(th.Comment) case tt.SubCategory() == chroma.Number || tt.Category() == chroma.Literal: return lipgloss.NewStyle().Foreground(th.Accent) case tt.Category() == chroma.Punctuation: return lipgloss.NewStyle().Foreground(th.Muted) default: return lipgloss.NewStyle().Foreground(th.Text) } }