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