1package highlight
2
3import (
4 "bytes"
5 "fmt"
6 "image/color"
7 "strings"
8
9 "github.com/alecthomas/chroma/v2"
10 "github.com/alecthomas/chroma/v2/formatters"
11 "github.com/alecthomas/chroma/v2/lexers"
12 "github.com/alecthomas/chroma/v2/styles"
13 "github.com/opencode-ai/opencode/internal/tui/theme"
14)
15
16func SyntaxHighlight(source, fileName string, bg color.Color) (string, error) {
17 t := theme.CurrentTheme()
18
19 // Determine the language lexer to use
20 l := lexers.Match(fileName)
21 if l == nil {
22 l = lexers.Analyse(source)
23 }
24 if l == nil {
25 l = lexers.Fallback
26 }
27 l = chroma.Coalesce(l)
28
29 // Get the formatter
30 f := formatters.Get("terminal16m")
31 if f == nil {
32 f = formatters.Fallback
33 }
34
35 // Dynamic theme based on current theme values
36 syntaxThemeXml := fmt.Sprintf(`
37 <style name="opencode-theme">
38 <!-- Base colors -->
39 <entry type="Text" style="%s"/>
40 <entry type="Other" style="%s"/>
41 <entry type="Error" style="%s"/>
42 <!-- Keywords -->
43 <entry type="Keyword" style="%s"/>
44 <entry type="KeywordConstant" style="%s"/>
45 <entry type="KeywordDeclaration" style="%s"/>
46 <entry type="KeywordNamespace" style="%s"/>
47 <entry type="KeywordPseudo" style="%s"/>
48 <entry type="KeywordReserved" style="%s"/>
49 <entry type="KeywordType" style="%s"/>
50 <!-- Names -->
51 <entry type="Name" style="%s"/>
52 <entry type="NameAttribute" style="%s"/>
53 <entry type="NameBuiltin" style="%s"/>
54 <entry type="NameBuiltinPseudo" style="%s"/>
55 <entry type="NameClass" style="%s"/>
56 <entry type="NameConstant" style="%s"/>
57 <entry type="NameDecorator" style="%s"/>
58 <entry type="NameEntity" style="%s"/>
59 <entry type="NameException" style="%s"/>
60 <entry type="NameFunction" style="%s"/>
61 <entry type="NameLabel" style="%s"/>
62 <entry type="NameNamespace" style="%s"/>
63 <entry type="NameOther" style="%s"/>
64 <entry type="NameTag" style="%s"/>
65 <entry type="NameVariable" style="%s"/>
66 <entry type="NameVariableClass" style="%s"/>
67 <entry type="NameVariableGlobal" style="%s"/>
68 <entry type="NameVariableInstance" style="%s"/>
69 <!-- Literals -->
70 <entry type="Literal" style="%s"/>
71 <entry type="LiteralDate" style="%s"/>
72 <entry type="LiteralString" style="%s"/>
73 <entry type="LiteralStringBacktick" style="%s"/>
74 <entry type="LiteralStringChar" style="%s"/>
75 <entry type="LiteralStringDoc" style="%s"/>
76 <entry type="LiteralStringDouble" style="%s"/>
77 <entry type="LiteralStringEscape" style="%s"/>
78 <entry type="LiteralStringHeredoc" style="%s"/>
79 <entry type="LiteralStringInterpol" style="%s"/>
80 <entry type="LiteralStringOther" style="%s"/>
81 <entry type="LiteralStringRegex" style="%s"/>
82 <entry type="LiteralStringSingle" style="%s"/>
83 <entry type="LiteralStringSymbol" style="%s"/>
84 <!-- Numbers -->
85 <entry type="LiteralNumber" style="%s"/>
86 <entry type="LiteralNumberBin" style="%s"/>
87 <entry type="LiteralNumberFloat" style="%s"/>
88 <entry type="LiteralNumberHex" style="%s"/>
89 <entry type="LiteralNumberInteger" style="%s"/>
90 <entry type="LiteralNumberIntegerLong" style="%s"/>
91 <entry type="LiteralNumberOct" style="%s"/>
92 <!-- Operators -->
93 <entry type="Operator" style="%s"/>
94 <entry type="OperatorWord" style="%s"/>
95 <entry type="Punctuation" style="%s"/>
96 <!-- Comments -->
97 <entry type="Comment" style="%s"/>
98 <entry type="CommentHashbang" style="%s"/>
99 <entry type="CommentMultiline" style="%s"/>
100 <entry type="CommentSingle" style="%s"/>
101 <entry type="CommentSpecial" style="%s"/>
102 <entry type="CommentPreproc" style="%s"/>
103 <!-- Generic styles -->
104 <entry type="Generic" style="%s"/>
105 <entry type="GenericDeleted" style="%s"/>
106 <entry type="GenericEmph" style="italic %s"/>
107 <entry type="GenericError" style="%s"/>
108 <entry type="GenericHeading" style="bold %s"/>
109 <entry type="GenericInserted" style="%s"/>
110 <entry type="GenericOutput" style="%s"/>
111 <entry type="GenericPrompt" style="%s"/>
112 <entry type="GenericStrong" style="bold %s"/>
113 <entry type="GenericSubheading" style="bold %s"/>
114 <entry type="GenericTraceback" style="%s"/>
115 <entry type="GenericUnderline" style="underline"/>
116 <entry type="TextWhitespace" style="%s"/>
117</style>
118`,
119 getColor(t.Text()), // Text
120 getColor(t.Text()), // Other
121 getColor(t.Error()), // Error
122
123 getColor(t.SyntaxKeyword()), // Keyword
124 getColor(t.SyntaxKeyword()), // KeywordConstant
125 getColor(t.SyntaxKeyword()), // KeywordDeclaration
126 getColor(t.SyntaxKeyword()), // KeywordNamespace
127 getColor(t.SyntaxKeyword()), // KeywordPseudo
128 getColor(t.SyntaxKeyword()), // KeywordReserved
129 getColor(t.SyntaxType()), // KeywordType
130
131 getColor(t.Text()), // Name
132 getColor(t.SyntaxVariable()), // NameAttribute
133 getColor(t.SyntaxType()), // NameBuiltin
134 getColor(t.SyntaxVariable()), // NameBuiltinPseudo
135 getColor(t.SyntaxType()), // NameClass
136 getColor(t.SyntaxVariable()), // NameConstant
137 getColor(t.SyntaxFunction()), // NameDecorator
138 getColor(t.SyntaxVariable()), // NameEntity
139 getColor(t.SyntaxType()), // NameException
140 getColor(t.SyntaxFunction()), // NameFunction
141 getColor(t.Text()), // NameLabel
142 getColor(t.SyntaxType()), // NameNamespace
143 getColor(t.SyntaxVariable()), // NameOther
144 getColor(t.SyntaxKeyword()), // NameTag
145 getColor(t.SyntaxVariable()), // NameVariable
146 getColor(t.SyntaxVariable()), // NameVariableClass
147 getColor(t.SyntaxVariable()), // NameVariableGlobal
148 getColor(t.SyntaxVariable()), // NameVariableInstance
149
150 getColor(t.SyntaxString()), // Literal
151 getColor(t.SyntaxString()), // LiteralDate
152 getColor(t.SyntaxString()), // LiteralString
153 getColor(t.SyntaxString()), // LiteralStringBacktick
154 getColor(t.SyntaxString()), // LiteralStringChar
155 getColor(t.SyntaxString()), // LiteralStringDoc
156 getColor(t.SyntaxString()), // LiteralStringDouble
157 getColor(t.SyntaxString()), // LiteralStringEscape
158 getColor(t.SyntaxString()), // LiteralStringHeredoc
159 getColor(t.SyntaxString()), // LiteralStringInterpol
160 getColor(t.SyntaxString()), // LiteralStringOther
161 getColor(t.SyntaxString()), // LiteralStringRegex
162 getColor(t.SyntaxString()), // LiteralStringSingle
163 getColor(t.SyntaxString()), // LiteralStringSymbol
164
165 getColor(t.SyntaxNumber()), // LiteralNumber
166 getColor(t.SyntaxNumber()), // LiteralNumberBin
167 getColor(t.SyntaxNumber()), // LiteralNumberFloat
168 getColor(t.SyntaxNumber()), // LiteralNumberHex
169 getColor(t.SyntaxNumber()), // LiteralNumberInteger
170 getColor(t.SyntaxNumber()), // LiteralNumberIntegerLong
171 getColor(t.SyntaxNumber()), // LiteralNumberOct
172
173 getColor(t.SyntaxOperator()), // Operator
174 getColor(t.SyntaxKeyword()), // OperatorWord
175 getColor(t.SyntaxPunctuation()), // Punctuation
176
177 getColor(t.SyntaxComment()), // Comment
178 getColor(t.SyntaxComment()), // CommentHashbang
179 getColor(t.SyntaxComment()), // CommentMultiline
180 getColor(t.SyntaxComment()), // CommentSingle
181 getColor(t.SyntaxComment()), // CommentSpecial
182 getColor(t.SyntaxKeyword()), // CommentPreproc
183
184 getColor(t.Text()), // Generic
185 getColor(t.Error()), // GenericDeleted
186 getColor(t.Text()), // GenericEmph
187 getColor(t.Error()), // GenericError
188 getColor(t.Text()), // GenericHeading
189 getColor(t.Success()), // GenericInserted
190 getColor(t.TextMuted()), // GenericOutput
191 getColor(t.Text()), // GenericPrompt
192 getColor(t.Text()), // GenericStrong
193 getColor(t.Text()), // GenericSubheading
194 getColor(t.Error()), // GenericTraceback
195 getColor(t.Text()), // TextWhitespace
196 )
197
198 r := strings.NewReader(syntaxThemeXml)
199 style := chroma.MustNewXMLStyle(r)
200
201 // Modify the style to use the provided background
202 s, err := style.Builder().Transform(
203 func(t chroma.StyleEntry) chroma.StyleEntry {
204 r, g, b, _ := bg.RGBA()
205 t.Background = chroma.NewColour(uint8(r>>8), uint8(g>>8), uint8(b>>8))
206 return t
207 },
208 ).Build()
209 if err != nil {
210 s = styles.Fallback
211 }
212
213 // Tokenize and format
214 it, err := l.Tokenise(nil, source)
215 if err != nil {
216 return "", err
217 }
218
219 var buf bytes.Buffer
220 err = f.Format(&buf, s, it)
221 return buf.String(), err
222}
223
224func getColor(c color.Color) string {
225 rgba := color.RGBAModel.Convert(c).(color.RGBA)
226 return fmt.Sprintf("#%02x%02x%02x", rgba.R, rgba.G, rgba.B)
227}