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