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