markdown.go

  1package styles
  2
  3import (
  4	"github.com/charmbracelet/glamour"
  5	"github.com/charmbracelet/glamour/ansi"
  6	"github.com/charmbracelet/lipgloss"
  7	"github.com/opencode-ai/opencode/internal/tui/theme"
  8)
  9
 10const defaultMargin = 1
 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				BlockPrefix: "",
 35				BlockSuffix: "",
 36				Color:       stringPtr(adaptiveColorToString(t.MarkdownText())),
 37			},
 38			Margin: uintPtr(defaultMargin),
 39		},
 40		BlockQuote: ansi.StyleBlock{
 41			StylePrimitive: ansi.StylePrimitive{
 42				Color:  stringPtr(adaptiveColorToString(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(adaptiveColorToString(t.MarkdownText())),
 55				},
 56			},
 57		},
 58		Heading: ansi.StyleBlock{
 59			StylePrimitive: ansi.StylePrimitive{
 60				BlockSuffix: "\n",
 61				Color:       stringPtr(adaptiveColorToString(t.MarkdownHeading())),
 62				Bold:        boolPtr(true),
 63			},
 64		},
 65		H1: ansi.StyleBlock{
 66			StylePrimitive: ansi.StylePrimitive{
 67				Prefix: "# ",
 68				Color:  stringPtr(adaptiveColorToString(t.MarkdownHeading())),
 69				Bold:   boolPtr(true),
 70			},
 71		},
 72		H2: ansi.StyleBlock{
 73			StylePrimitive: ansi.StylePrimitive{
 74				Prefix: "## ",
 75				Color:  stringPtr(adaptiveColorToString(t.MarkdownHeading())),
 76				Bold:   boolPtr(true),
 77			},
 78		},
 79		H3: ansi.StyleBlock{
 80			StylePrimitive: ansi.StylePrimitive{
 81				Prefix: "### ",
 82				Color:  stringPtr(adaptiveColorToString(t.MarkdownHeading())),
 83				Bold:   boolPtr(true),
 84			},
 85		},
 86		H4: ansi.StyleBlock{
 87			StylePrimitive: ansi.StylePrimitive{
 88				Prefix: "#### ",
 89				Color:  stringPtr(adaptiveColorToString(t.MarkdownHeading())),
 90				Bold:   boolPtr(true),
 91			},
 92		},
 93		H5: ansi.StyleBlock{
 94			StylePrimitive: ansi.StylePrimitive{
 95				Prefix: "##### ",
 96				Color:  stringPtr(adaptiveColorToString(t.MarkdownHeading())),
 97				Bold:   boolPtr(true),
 98			},
 99		},
100		H6: ansi.StyleBlock{
101			StylePrimitive: ansi.StylePrimitive{
102				Prefix: "###### ",
103				Color:  stringPtr(adaptiveColorToString(t.MarkdownHeading())),
104				Bold:   boolPtr(true),
105			},
106		},
107		Strikethrough: ansi.StylePrimitive{
108			CrossedOut: boolPtr(true),
109			Color:      stringPtr(adaptiveColorToString(t.TextMuted())),
110		},
111		Emph: ansi.StylePrimitive{
112			Color:  stringPtr(adaptiveColorToString(t.MarkdownEmph())),
113			Italic: boolPtr(true),
114		},
115		Strong: ansi.StylePrimitive{
116			Bold:  boolPtr(true),
117			Color: stringPtr(adaptiveColorToString(t.MarkdownStrong())),
118		},
119		HorizontalRule: ansi.StylePrimitive{
120			Color:  stringPtr(adaptiveColorToString(t.MarkdownHorizontalRule())),
121			Format: "\n─────────────────────────────────────────\n",
122		},
123		Item: ansi.StylePrimitive{
124			BlockPrefix: "• ",
125			Color:       stringPtr(adaptiveColorToString(t.MarkdownListItem())),
126		},
127		Enumeration: ansi.StylePrimitive{
128			BlockPrefix: ". ",
129			Color:       stringPtr(adaptiveColorToString(t.MarkdownListEnumeration())),
130		},
131		Task: ansi.StyleTask{
132			StylePrimitive: ansi.StylePrimitive{},
133			Ticked:         "[āœ“] ",
134			Unticked:       "[ ] ",
135		},
136		Link: ansi.StylePrimitive{
137			Color:     stringPtr(adaptiveColorToString(t.MarkdownLink())),
138			Underline: boolPtr(true),
139		},
140		LinkText: ansi.StylePrimitive{
141			Color: stringPtr(adaptiveColorToString(t.MarkdownLinkText())),
142			Bold:  boolPtr(true),
143		},
144		Image: ansi.StylePrimitive{
145			Color:     stringPtr(adaptiveColorToString(t.MarkdownImage())),
146			Underline: boolPtr(true),
147			Format:    "šŸ–¼ {{.text}}",
148		},
149		ImageText: ansi.StylePrimitive{
150			Color:  stringPtr(adaptiveColorToString(t.MarkdownImageText())),
151			Format: "{{.text}}",
152		},
153		Code: ansi.StyleBlock{
154			StylePrimitive: ansi.StylePrimitive{
155				Color:  stringPtr(adaptiveColorToString(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(adaptiveColorToString(t.MarkdownCodeBlock())),
165				},
166				Margin: uintPtr(defaultMargin),
167			},
168			Chroma: &ansi.Chroma{
169				Text: ansi.StylePrimitive{
170					Color: stringPtr(adaptiveColorToString(t.MarkdownText())),
171				},
172				Error: ansi.StylePrimitive{
173					Color: stringPtr(adaptiveColorToString(t.Error())),
174				},
175				Comment: ansi.StylePrimitive{
176					Color: stringPtr(adaptiveColorToString(t.SyntaxComment())),
177				},
178				CommentPreproc: ansi.StylePrimitive{
179					Color: stringPtr(adaptiveColorToString(t.SyntaxKeyword())),
180				},
181				Keyword: ansi.StylePrimitive{
182					Color: stringPtr(adaptiveColorToString(t.SyntaxKeyword())),
183				},
184				KeywordReserved: ansi.StylePrimitive{
185					Color: stringPtr(adaptiveColorToString(t.SyntaxKeyword())),
186				},
187				KeywordNamespace: ansi.StylePrimitive{
188					Color: stringPtr(adaptiveColorToString(t.SyntaxKeyword())),
189				},
190				KeywordType: ansi.StylePrimitive{
191					Color: stringPtr(adaptiveColorToString(t.SyntaxType())),
192				},
193				Operator: ansi.StylePrimitive{
194					Color: stringPtr(adaptiveColorToString(t.SyntaxOperator())),
195				},
196				Punctuation: ansi.StylePrimitive{
197					Color: stringPtr(adaptiveColorToString(t.SyntaxPunctuation())),
198				},
199				Name: ansi.StylePrimitive{
200					Color: stringPtr(adaptiveColorToString(t.SyntaxVariable())),
201				},
202				NameBuiltin: ansi.StylePrimitive{
203					Color: stringPtr(adaptiveColorToString(t.SyntaxVariable())),
204				},
205				NameTag: ansi.StylePrimitive{
206					Color: stringPtr(adaptiveColorToString(t.SyntaxKeyword())),
207				},
208				NameAttribute: ansi.StylePrimitive{
209					Color: stringPtr(adaptiveColorToString(t.SyntaxFunction())),
210				},
211				NameClass: ansi.StylePrimitive{
212					Color: stringPtr(adaptiveColorToString(t.SyntaxType())),
213				},
214				NameConstant: ansi.StylePrimitive{
215					Color: stringPtr(adaptiveColorToString(t.SyntaxVariable())),
216				},
217				NameDecorator: ansi.StylePrimitive{
218					Color: stringPtr(adaptiveColorToString(t.SyntaxFunction())),
219				},
220				NameFunction: ansi.StylePrimitive{
221					Color: stringPtr(adaptiveColorToString(t.SyntaxFunction())),
222				},
223				LiteralNumber: ansi.StylePrimitive{
224					Color: stringPtr(adaptiveColorToString(t.SyntaxNumber())),
225				},
226				LiteralString: ansi.StylePrimitive{
227					Color: stringPtr(adaptiveColorToString(t.SyntaxString())),
228				},
229				LiteralStringEscape: ansi.StylePrimitive{
230					Color: stringPtr(adaptiveColorToString(t.SyntaxKeyword())),
231				},
232				GenericDeleted: ansi.StylePrimitive{
233					Color: stringPtr(adaptiveColorToString(t.DiffRemoved())),
234				},
235				GenericEmph: ansi.StylePrimitive{
236					Color:  stringPtr(adaptiveColorToString(t.MarkdownEmph())),
237					Italic: boolPtr(true),
238				},
239				GenericInserted: ansi.StylePrimitive{
240					Color: stringPtr(adaptiveColorToString(t.DiffAdded())),
241				},
242				GenericStrong: ansi.StylePrimitive{
243					Color: stringPtr(adaptiveColorToString(t.MarkdownStrong())),
244					Bold:  boolPtr(true),
245				},
246				GenericSubheading: ansi.StylePrimitive{
247					Color: stringPtr(adaptiveColorToString(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(adaptiveColorToString(t.MarkdownLinkText())),
265		},
266		Text: ansi.StylePrimitive{
267			Color: stringPtr(adaptiveColorToString(t.MarkdownText())),
268		},
269		Paragraph: ansi.StyleBlock{
270			StylePrimitive: ansi.StylePrimitive{
271				Color: stringPtr(adaptiveColorToString(t.MarkdownText())),
272			},
273		},
274	}
275}
276
277// adaptiveColorToString converts a lipgloss.AdaptiveColor to the appropriate
278// hex color string based on the current terminal background
279func adaptiveColorToString(color lipgloss.AdaptiveColor) string {
280	if lipgloss.HasDarkBackground() {
281		return color.Dark
282	}
283	return color.Light
284}