highlight.go

  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}