markdown.go

  1package styles
  2
  3import (
  4	"fmt"
  5	"image/color"
  6
  7	"github.com/charmbracelet/glamour/v2"
  8	"github.com/charmbracelet/glamour/v2/ansi"
  9	"github.com/opencode-ai/opencode/internal/tui/theme"
 10)
 11
 12const defaultMargin = 1
 13
 14// Helper functions for style pointers
 15func boolPtr(b bool) *bool       { return &b }
 16func stringPtr(s string) *string { return &s }
 17func uintPtr(u uint) *uint       { return &u }
 18
 19// returns a glamour TermRenderer configured with the current theme
 20func GetMarkdownRenderer(width int) *glamour.TermRenderer {
 21	r, _ := glamour.NewTermRenderer(
 22		glamour.WithStyles(generateMarkdownStyleConfig()),
 23		glamour.WithWordWrap(width),
 24	)
 25	return r
 26}
 27
 28// creates an ansi.StyleConfig for markdown rendering
 29// using adaptive colors from the provided theme.
 30func generateMarkdownStyleConfig() ansi.StyleConfig {
 31	t := theme.CurrentTheme()
 32
 33	return ansi.StyleConfig{
 34		Document: ansi.StyleBlock{
 35			StylePrimitive: ansi.StylePrimitive{
 36				BlockPrefix: "",
 37				BlockSuffix: "",
 38				Color:       stringPtr(colorToString(t.MarkdownText())),
 39			},
 40			Margin: uintPtr(defaultMargin),
 41		},
 42		BlockQuote: ansi.StyleBlock{
 43			StylePrimitive: ansi.StylePrimitive{
 44				Color:  stringPtr(colorToString(t.MarkdownBlockQuote())),
 45				Italic: boolPtr(true),
 46				Prefix: "ā”ƒ ",
 47			},
 48			Indent:      uintPtr(1),
 49			IndentToken: stringPtr(BaseStyle().Render(" ")),
 50		},
 51		List: ansi.StyleList{
 52			LevelIndent: defaultMargin,
 53			StyleBlock: ansi.StyleBlock{
 54				IndentToken: stringPtr(BaseStyle().Render(" ")),
 55				StylePrimitive: ansi.StylePrimitive{
 56					Color: stringPtr(colorToString(t.MarkdownText())),
 57				},
 58			},
 59		},
 60		Heading: ansi.StyleBlock{
 61			StylePrimitive: ansi.StylePrimitive{
 62				BlockSuffix: "\n",
 63				Color:       stringPtr(colorToString(t.MarkdownHeading())),
 64				Bold:        boolPtr(true),
 65			},
 66		},
 67		H1: ansi.StyleBlock{
 68			StylePrimitive: ansi.StylePrimitive{
 69				Prefix: "# ",
 70				Color:  stringPtr(colorToString(t.MarkdownHeading())),
 71				Bold:   boolPtr(true),
 72			},
 73		},
 74		H2: ansi.StyleBlock{
 75			StylePrimitive: ansi.StylePrimitive{
 76				Prefix: "## ",
 77				Color:  stringPtr(colorToString(t.MarkdownHeading())),
 78				Bold:   boolPtr(true),
 79			},
 80		},
 81		H3: ansi.StyleBlock{
 82			StylePrimitive: ansi.StylePrimitive{
 83				Prefix: "### ",
 84				Color:  stringPtr(colorToString(t.MarkdownHeading())),
 85				Bold:   boolPtr(true),
 86			},
 87		},
 88		H4: ansi.StyleBlock{
 89			StylePrimitive: ansi.StylePrimitive{
 90				Prefix: "#### ",
 91				Color:  stringPtr(colorToString(t.MarkdownHeading())),
 92				Bold:   boolPtr(true),
 93			},
 94		},
 95		H5: ansi.StyleBlock{
 96			StylePrimitive: ansi.StylePrimitive{
 97				Prefix: "##### ",
 98				Color:  stringPtr(colorToString(t.MarkdownHeading())),
 99				Bold:   boolPtr(true),
100			},
101		},
102		H6: ansi.StyleBlock{
103			StylePrimitive: ansi.StylePrimitive{
104				Prefix: "###### ",
105				Color:  stringPtr(colorToString(t.MarkdownHeading())),
106				Bold:   boolPtr(true),
107			},
108		},
109		Strikethrough: ansi.StylePrimitive{
110			CrossedOut: boolPtr(true),
111			Color:      stringPtr(colorToString(t.TextMuted())),
112		},
113		Emph: ansi.StylePrimitive{
114			Color:  stringPtr(colorToString(t.MarkdownEmph())),
115			Italic: boolPtr(true),
116		},
117		Strong: ansi.StylePrimitive{
118			Bold:  boolPtr(true),
119			Color: stringPtr(colorToString(t.MarkdownStrong())),
120		},
121		HorizontalRule: ansi.StylePrimitive{
122			Color:  stringPtr(colorToString(t.MarkdownHorizontalRule())),
123			Format: "\n─────────────────────────────────────────\n",
124		},
125		Item: ansi.StylePrimitive{
126			BlockPrefix: "• ",
127			Color:       stringPtr(colorToString(t.MarkdownListItem())),
128		},
129		Enumeration: ansi.StylePrimitive{
130			BlockPrefix: ". ",
131			Color:       stringPtr(colorToString(t.MarkdownListEnumeration())),
132		},
133		Task: ansi.StyleTask{
134			StylePrimitive: ansi.StylePrimitive{},
135			Ticked:         "[āœ“] ",
136			Unticked:       "[ ] ",
137		},
138		Link: ansi.StylePrimitive{
139			Color:     stringPtr(colorToString(t.MarkdownLink())),
140			Underline: boolPtr(true),
141		},
142		LinkText: ansi.StylePrimitive{
143			Color: stringPtr(colorToString(t.MarkdownLinkText())),
144			Bold:  boolPtr(true),
145		},
146		Image: ansi.StylePrimitive{
147			Color:     stringPtr(colorToString(t.MarkdownImage())),
148			Underline: boolPtr(true),
149			Format:    "šŸ–¼ {{.text}}",
150		},
151		ImageText: ansi.StylePrimitive{
152			Color:  stringPtr(colorToString(t.MarkdownImageText())),
153			Format: "{{.text}}",
154		},
155		Code: ansi.StyleBlock{
156			StylePrimitive: ansi.StylePrimitive{
157				Color:  stringPtr(colorToString(t.MarkdownCode())),
158				Prefix: "",
159				Suffix: "",
160			},
161		},
162		CodeBlock: ansi.StyleCodeBlock{
163			StyleBlock: ansi.StyleBlock{
164				StylePrimitive: ansi.StylePrimitive{
165					Prefix: " ",
166					Color:  stringPtr(colorToString(t.MarkdownCodeBlock())),
167				},
168				Margin: uintPtr(defaultMargin),
169			},
170			Chroma: &ansi.Chroma{
171				Text: ansi.StylePrimitive{
172					Color: stringPtr(colorToString(t.MarkdownText())),
173				},
174				Error: ansi.StylePrimitive{
175					Color: stringPtr(colorToString(t.Error())),
176				},
177				Comment: ansi.StylePrimitive{
178					Color: stringPtr(colorToString(t.SyntaxComment())),
179				},
180				CommentPreproc: ansi.StylePrimitive{
181					Color: stringPtr(colorToString(t.SyntaxKeyword())),
182				},
183				Keyword: ansi.StylePrimitive{
184					Color: stringPtr(colorToString(t.SyntaxKeyword())),
185				},
186				KeywordReserved: ansi.StylePrimitive{
187					Color: stringPtr(colorToString(t.SyntaxKeyword())),
188				},
189				KeywordNamespace: ansi.StylePrimitive{
190					Color: stringPtr(colorToString(t.SyntaxKeyword())),
191				},
192				KeywordType: ansi.StylePrimitive{
193					Color: stringPtr(colorToString(t.SyntaxType())),
194				},
195				Operator: ansi.StylePrimitive{
196					Color: stringPtr(colorToString(t.SyntaxOperator())),
197				},
198				Punctuation: ansi.StylePrimitive{
199					Color: stringPtr(colorToString(t.SyntaxPunctuation())),
200				},
201				Name: ansi.StylePrimitive{
202					Color: stringPtr(colorToString(t.SyntaxVariable())),
203				},
204				NameBuiltin: ansi.StylePrimitive{
205					Color: stringPtr(colorToString(t.SyntaxVariable())),
206				},
207				NameTag: ansi.StylePrimitive{
208					Color: stringPtr(colorToString(t.SyntaxKeyword())),
209				},
210				NameAttribute: ansi.StylePrimitive{
211					Color: stringPtr(colorToString(t.SyntaxFunction())),
212				},
213				NameClass: ansi.StylePrimitive{
214					Color: stringPtr(colorToString(t.SyntaxType())),
215				},
216				NameConstant: ansi.StylePrimitive{
217					Color: stringPtr(colorToString(t.SyntaxVariable())),
218				},
219				NameDecorator: ansi.StylePrimitive{
220					Color: stringPtr(colorToString(t.SyntaxFunction())),
221				},
222				NameFunction: ansi.StylePrimitive{
223					Color: stringPtr(colorToString(t.SyntaxFunction())),
224				},
225				LiteralNumber: ansi.StylePrimitive{
226					Color: stringPtr(colorToString(t.SyntaxNumber())),
227				},
228				LiteralString: ansi.StylePrimitive{
229					Color: stringPtr(colorToString(t.SyntaxString())),
230				},
231				LiteralStringEscape: ansi.StylePrimitive{
232					Color: stringPtr(colorToString(t.SyntaxKeyword())),
233				},
234				GenericDeleted: ansi.StylePrimitive{
235					Color: stringPtr(colorToString(t.DiffRemoved())),
236				},
237				GenericEmph: ansi.StylePrimitive{
238					Color:  stringPtr(colorToString(t.MarkdownEmph())),
239					Italic: boolPtr(true),
240				},
241				GenericInserted: ansi.StylePrimitive{
242					Color: stringPtr(colorToString(t.DiffAdded())),
243				},
244				GenericStrong: ansi.StylePrimitive{
245					Color: stringPtr(colorToString(t.MarkdownStrong())),
246					Bold:  boolPtr(true),
247				},
248				GenericSubheading: ansi.StylePrimitive{
249					Color: stringPtr(colorToString(t.MarkdownHeading())),
250				},
251			},
252		},
253		Table: ansi.StyleTable{
254			StyleBlock: ansi.StyleBlock{
255				StylePrimitive: ansi.StylePrimitive{
256					BlockPrefix: "\n",
257					BlockSuffix: "\n",
258				},
259			},
260			CenterSeparator: stringPtr("┼"),
261			ColumnSeparator: stringPtr("│"),
262			RowSeparator:    stringPtr("─"),
263		},
264		DefinitionDescription: ansi.StylePrimitive{
265			BlockPrefix: "\n āÆ ",
266			Color:       stringPtr(colorToString(t.MarkdownLinkText())),
267		},
268		Text: ansi.StylePrimitive{
269			Color: stringPtr(colorToString(t.MarkdownText())),
270		},
271		Paragraph: ansi.StyleBlock{
272			StylePrimitive: ansi.StylePrimitive{
273				Color: stringPtr(colorToString(t.MarkdownText())),
274			},
275		},
276	}
277}
278
279func colorToString(c color.Color) string {
280	rgba := color.RGBAModel.Convert(c).(color.RGBA)
281	return fmt.Sprintf("#%02x%02x%02x", rgba.R, rgba.G, rgba.B)
282}