2025-03-21 18:20:28 +01:00
|
|
|
|
package styles
|
|
|
|
|
|
|
|
|
|
|
|
import (
|
2025-05-01 05:49:26 -05:00
|
|
|
|
"github.com/charmbracelet/glamour"
|
2025-03-21 18:20:28 +01:00
|
|
|
|
"github.com/charmbracelet/glamour/ansi"
|
|
|
|
|
|
"github.com/charmbracelet/lipgloss"
|
2025-05-01 05:49:26 -05:00
|
|
|
|
"github.com/opencode-ai/opencode/internal/tui/theme"
|
2025-03-21 18:20:28 +01:00
|
|
|
|
)
|
|
|
|
|
|
|
2025-03-27 22:35:48 +01:00
|
|
|
|
const defaultMargin = 1
|
2025-03-21 18:20:28 +01:00
|
|
|
|
|
|
|
|
|
|
// Helper functions for style pointers
|
|
|
|
|
|
func boolPtr(b bool) *bool { return &b }
|
|
|
|
|
|
func stringPtr(s string) *string { return &s }
|
|
|
|
|
|
func uintPtr(u uint) *uint { return &u }
|
|
|
|
|
|
|
2025-05-01 05:49:26 -05:00
|
|
|
|
// returns a glamour TermRenderer configured with the current theme
|
|
|
|
|
|
func GetMarkdownRenderer(width int) *glamour.TermRenderer {
|
|
|
|
|
|
r, _ := glamour.NewTermRenderer(
|
|
|
|
|
|
glamour.WithStyles(generateMarkdownStyleConfig()),
|
|
|
|
|
|
glamour.WithWordWrap(width),
|
|
|
|
|
|
)
|
|
|
|
|
|
return r
|
2025-03-21 18:20:28 +01:00
|
|
|
|
}
|
|
|
|
|
|
|
2025-05-01 05:49:26 -05:00
|
|
|
|
// creates an ansi.StyleConfig for markdown rendering
|
|
|
|
|
|
// using adaptive colors from the provided theme.
|
|
|
|
|
|
func generateMarkdownStyleConfig() ansi.StyleConfig {
|
|
|
|
|
|
t := theme.CurrentTheme()
|
|
|
|
|
|
|
|
|
|
|
|
return ansi.StyleConfig{
|
|
|
|
|
|
Document: ansi.StyleBlock{
|
2025-03-21 18:20:28 +01:00
|
|
|
|
StylePrimitive: ansi.StylePrimitive{
|
2025-05-01 05:49:26 -05:00
|
|
|
|
BlockPrefix: "",
|
|
|
|
|
|
BlockSuffix: "",
|
|
|
|
|
|
Color: stringPtr(adaptiveColorToString(t.MarkdownText())),
|
2025-03-21 18:20:28 +01:00
|
|
|
|
},
|
|
|
|
|
|
Margin: uintPtr(defaultMargin),
|
|
|
|
|
|
},
|
2025-05-01 05:49:26 -05:00
|
|
|
|
BlockQuote: ansi.StyleBlock{
|
|
|
|
|
|
StylePrimitive: ansi.StylePrimitive{
|
|
|
|
|
|
Color: stringPtr(adaptiveColorToString(t.MarkdownBlockQuote())),
|
2025-03-21 18:20:28 +01:00
|
|
|
|
Italic: boolPtr(true),
|
2025-05-01 05:49:26 -05:00
|
|
|
|
Prefix: "┃ ",
|
2025-03-21 18:20:28 +01:00
|
|
|
|
},
|
2025-05-01 05:49:26 -05:00
|
|
|
|
Indent: uintPtr(1),
|
|
|
|
|
|
IndentToken: stringPtr(BaseStyle().Render(" ")),
|
|
|
|
|
|
},
|
|
|
|
|
|
List: ansi.StyleList{
|
|
|
|
|
|
LevelIndent: defaultMargin,
|
|
|
|
|
|
StyleBlock: ansi.StyleBlock{
|
|
|
|
|
|
IndentToken: stringPtr(BaseStyle().Render(" ")),
|
|
|
|
|
|
StylePrimitive: ansi.StylePrimitive{
|
|
|
|
|
|
Color: stringPtr(adaptiveColorToString(t.MarkdownText())),
|
|
|
|
|
|
},
|
2025-03-21 18:20:28 +01:00
|
|
|
|
},
|
|
|
|
|
|
},
|
2025-05-01 05:49:26 -05:00
|
|
|
|
Heading: ansi.StyleBlock{
|
2025-03-21 18:20:28 +01:00
|
|
|
|
StylePrimitive: ansi.StylePrimitive{
|
|
|
|
|
|
BlockSuffix: "\n",
|
2025-05-01 05:49:26 -05:00
|
|
|
|
Color: stringPtr(adaptiveColorToString(t.MarkdownHeading())),
|
|
|
|
|
|
Bold: boolPtr(true),
|
2025-03-21 18:20:28 +01:00
|
|
|
|
},
|
|
|
|
|
|
},
|
2025-05-01 05:49:26 -05:00
|
|
|
|
H1: ansi.StyleBlock{
|
2025-03-21 18:20:28 +01:00
|
|
|
|
StylePrimitive: ansi.StylePrimitive{
|
2025-05-01 05:49:26 -05:00
|
|
|
|
Prefix: "# ",
|
|
|
|
|
|
Color: stringPtr(adaptiveColorToString(t.MarkdownHeading())),
|
|
|
|
|
|
Bold: boolPtr(true),
|
2025-03-21 18:20:28 +01:00
|
|
|
|
},
|
|
|
|
|
|
},
|
2025-05-01 05:49:26 -05:00
|
|
|
|
H2: ansi.StyleBlock{
|
2025-03-21 18:20:28 +01:00
|
|
|
|
StylePrimitive: ansi.StylePrimitive{
|
2025-05-01 05:49:26 -05:00
|
|
|
|
Prefix: "## ",
|
|
|
|
|
|
Color: stringPtr(adaptiveColorToString(t.MarkdownHeading())),
|
|
|
|
|
|
Bold: boolPtr(true),
|
2025-03-21 18:20:28 +01:00
|
|
|
|
},
|
|
|
|
|
|
},
|
2025-05-01 05:49:26 -05:00
|
|
|
|
H3: ansi.StyleBlock{
|
2025-03-21 18:20:28 +01:00
|
|
|
|
StylePrimitive: ansi.StylePrimitive{
|
2025-05-01 05:49:26 -05:00
|
|
|
|
Prefix: "### ",
|
|
|
|
|
|
Color: stringPtr(adaptiveColorToString(t.MarkdownHeading())),
|
|
|
|
|
|
Bold: boolPtr(true),
|
2025-03-21 18:20:28 +01:00
|
|
|
|
},
|
|
|
|
|
|
},
|
2025-05-01 05:49:26 -05:00
|
|
|
|
H4: ansi.StyleBlock{
|
2025-04-12 02:01:45 +02:00
|
|
|
|
StylePrimitive: ansi.StylePrimitive{
|
2025-05-01 05:49:26 -05:00
|
|
|
|
Prefix: "#### ",
|
|
|
|
|
|
Color: stringPtr(adaptiveColorToString(t.MarkdownHeading())),
|
|
|
|
|
|
Bold: boolPtr(true),
|
2025-04-12 02:01:45 +02:00
|
|
|
|
},
|
|
|
|
|
|
},
|
2025-05-01 05:49:26 -05:00
|
|
|
|
H5: ansi.StyleBlock{
|
2025-04-12 02:01:45 +02:00
|
|
|
|
StylePrimitive: ansi.StylePrimitive{
|
2025-05-01 05:49:26 -05:00
|
|
|
|
Prefix: "##### ",
|
|
|
|
|
|
Color: stringPtr(adaptiveColorToString(t.MarkdownHeading())),
|
|
|
|
|
|
Bold: boolPtr(true),
|
2025-04-12 02:01:45 +02:00
|
|
|
|
},
|
|
|
|
|
|
},
|
2025-05-01 05:49:26 -05:00
|
|
|
|
H6: ansi.StyleBlock{
|
2025-04-12 02:01:45 +02:00
|
|
|
|
StylePrimitive: ansi.StylePrimitive{
|
2025-05-01 05:49:26 -05:00
|
|
|
|
Prefix: "###### ",
|
|
|
|
|
|
Color: stringPtr(adaptiveColorToString(t.MarkdownHeading())),
|
|
|
|
|
|
Bold: boolPtr(true),
|
2025-04-12 02:01:45 +02:00
|
|
|
|
},
|
|
|
|
|
|
},
|
2025-05-01 05:49:26 -05:00
|
|
|
|
Strikethrough: ansi.StylePrimitive{
|
|
|
|
|
|
CrossedOut: boolPtr(true),
|
|
|
|
|
|
Color: stringPtr(adaptiveColorToString(t.TextMuted())),
|
2025-04-12 02:01:45 +02:00
|
|
|
|
},
|
2025-05-01 05:49:26 -05:00
|
|
|
|
Emph: ansi.StylePrimitive{
|
|
|
|
|
|
Color: stringPtr(adaptiveColorToString(t.MarkdownEmph())),
|
|
|
|
|
|
Italic: boolPtr(true),
|
2025-04-12 02:01:45 +02:00
|
|
|
|
},
|
2025-05-01 05:49:26 -05:00
|
|
|
|
Strong: ansi.StylePrimitive{
|
|
|
|
|
|
Bold: boolPtr(true),
|
|
|
|
|
|
Color: stringPtr(adaptiveColorToString(t.MarkdownStrong())),
|
2025-04-12 02:01:45 +02:00
|
|
|
|
},
|
2025-05-01 05:49:26 -05:00
|
|
|
|
HorizontalRule: ansi.StylePrimitive{
|
|
|
|
|
|
Color: stringPtr(adaptiveColorToString(t.MarkdownHorizontalRule())),
|
|
|
|
|
|
Format: "\n─────────────────────────────────────────\n",
|
2025-04-12 02:01:45 +02:00
|
|
|
|
},
|
2025-05-01 05:49:26 -05:00
|
|
|
|
Item: ansi.StylePrimitive{
|
|
|
|
|
|
BlockPrefix: "• ",
|
|
|
|
|
|
Color: stringPtr(adaptiveColorToString(t.MarkdownListItem())),
|
2025-04-12 02:01:45 +02:00
|
|
|
|
},
|
2025-05-01 05:49:26 -05:00
|
|
|
|
Enumeration: ansi.StylePrimitive{
|
|
|
|
|
|
BlockPrefix: ". ",
|
|
|
|
|
|
Color: stringPtr(adaptiveColorToString(t.MarkdownListEnumeration())),
|
2025-04-12 02:01:45 +02:00
|
|
|
|
},
|
2025-05-01 05:49:26 -05:00
|
|
|
|
Task: ansi.StyleTask{
|
|
|
|
|
|
StylePrimitive: ansi.StylePrimitive{},
|
|
|
|
|
|
Ticked: "[✓] ",
|
|
|
|
|
|
Unticked: "[ ] ",
|
2025-04-12 02:01:45 +02:00
|
|
|
|
},
|
2025-05-01 05:49:26 -05:00
|
|
|
|
Link: ansi.StylePrimitive{
|
|
|
|
|
|
Color: stringPtr(adaptiveColorToString(t.MarkdownLink())),
|
|
|
|
|
|
Underline: boolPtr(true),
|
2025-04-12 02:01:45 +02:00
|
|
|
|
},
|
2025-05-01 05:49:26 -05:00
|
|
|
|
LinkText: ansi.StylePrimitive{
|
|
|
|
|
|
Color: stringPtr(adaptiveColorToString(t.MarkdownLinkText())),
|
|
|
|
|
|
Bold: boolPtr(true),
|
2025-04-12 02:01:45 +02:00
|
|
|
|
},
|
2025-05-01 05:49:26 -05:00
|
|
|
|
Image: ansi.StylePrimitive{
|
|
|
|
|
|
Color: stringPtr(adaptiveColorToString(t.MarkdownImage())),
|
|
|
|
|
|
Underline: boolPtr(true),
|
|
|
|
|
|
Format: "🖼 {{.text}}",
|
2025-04-12 02:01:45 +02:00
|
|
|
|
},
|
2025-05-01 05:49:26 -05:00
|
|
|
|
ImageText: ansi.StylePrimitive{
|
|
|
|
|
|
Color: stringPtr(adaptiveColorToString(t.MarkdownImageText())),
|
|
|
|
|
|
Format: "{{.text}}",
|
2025-04-12 02:01:45 +02:00
|
|
|
|
},
|
2025-05-01 05:49:26 -05:00
|
|
|
|
Code: ansi.StyleBlock{
|
2025-04-12 02:01:45 +02:00
|
|
|
|
StylePrimitive: ansi.StylePrimitive{
|
2025-05-01 05:49:26 -05:00
|
|
|
|
Color: stringPtr(adaptiveColorToString(t.MarkdownCode())),
|
|
|
|
|
|
Prefix: "",
|
|
|
|
|
|
Suffix: "",
|
|
|
|
|
|
},
|
|
|
|
|
|
},
|
|
|
|
|
|
CodeBlock: ansi.StyleCodeBlock{
|
|
|
|
|
|
StyleBlock: ansi.StyleBlock{
|
|
|
|
|
|
StylePrimitive: ansi.StylePrimitive{
|
|
|
|
|
|
Prefix: " ",
|
|
|
|
|
|
Color: stringPtr(adaptiveColorToString(t.MarkdownCodeBlock())),
|
|
|
|
|
|
},
|
|
|
|
|
|
Margin: uintPtr(defaultMargin),
|
|
|
|
|
|
},
|
|
|
|
|
|
Chroma: &ansi.Chroma{
|
|
|
|
|
|
Text: ansi.StylePrimitive{
|
|
|
|
|
|
Color: stringPtr(adaptiveColorToString(t.MarkdownText())),
|
|
|
|
|
|
},
|
|
|
|
|
|
Error: ansi.StylePrimitive{
|
|
|
|
|
|
Color: stringPtr(adaptiveColorToString(t.Error())),
|
|
|
|
|
|
},
|
|
|
|
|
|
Comment: ansi.StylePrimitive{
|
|
|
|
|
|
Color: stringPtr(adaptiveColorToString(t.SyntaxComment())),
|
|
|
|
|
|
},
|
|
|
|
|
|
CommentPreproc: ansi.StylePrimitive{
|
|
|
|
|
|
Color: stringPtr(adaptiveColorToString(t.SyntaxKeyword())),
|
|
|
|
|
|
},
|
|
|
|
|
|
Keyword: ansi.StylePrimitive{
|
|
|
|
|
|
Color: stringPtr(adaptiveColorToString(t.SyntaxKeyword())),
|
|
|
|
|
|
},
|
|
|
|
|
|
KeywordReserved: ansi.StylePrimitive{
|
|
|
|
|
|
Color: stringPtr(adaptiveColorToString(t.SyntaxKeyword())),
|
|
|
|
|
|
},
|
|
|
|
|
|
KeywordNamespace: ansi.StylePrimitive{
|
|
|
|
|
|
Color: stringPtr(adaptiveColorToString(t.SyntaxKeyword())),
|
|
|
|
|
|
},
|
|
|
|
|
|
KeywordType: ansi.StylePrimitive{
|
|
|
|
|
|
Color: stringPtr(adaptiveColorToString(t.SyntaxType())),
|
|
|
|
|
|
},
|
|
|
|
|
|
Operator: ansi.StylePrimitive{
|
|
|
|
|
|
Color: stringPtr(adaptiveColorToString(t.SyntaxOperator())),
|
|
|
|
|
|
},
|
|
|
|
|
|
Punctuation: ansi.StylePrimitive{
|
|
|
|
|
|
Color: stringPtr(adaptiveColorToString(t.SyntaxPunctuation())),
|
|
|
|
|
|
},
|
|
|
|
|
|
Name: ansi.StylePrimitive{
|
|
|
|
|
|
Color: stringPtr(adaptiveColorToString(t.SyntaxVariable())),
|
|
|
|
|
|
},
|
|
|
|
|
|
NameBuiltin: ansi.StylePrimitive{
|
|
|
|
|
|
Color: stringPtr(adaptiveColorToString(t.SyntaxVariable())),
|
|
|
|
|
|
},
|
|
|
|
|
|
NameTag: ansi.StylePrimitive{
|
|
|
|
|
|
Color: stringPtr(adaptiveColorToString(t.SyntaxKeyword())),
|
|
|
|
|
|
},
|
|
|
|
|
|
NameAttribute: ansi.StylePrimitive{
|
|
|
|
|
|
Color: stringPtr(adaptiveColorToString(t.SyntaxFunction())),
|
|
|
|
|
|
},
|
|
|
|
|
|
NameClass: ansi.StylePrimitive{
|
|
|
|
|
|
Color: stringPtr(adaptiveColorToString(t.SyntaxType())),
|
|
|
|
|
|
},
|
|
|
|
|
|
NameConstant: ansi.StylePrimitive{
|
|
|
|
|
|
Color: stringPtr(adaptiveColorToString(t.SyntaxVariable())),
|
|
|
|
|
|
},
|
|
|
|
|
|
NameDecorator: ansi.StylePrimitive{
|
|
|
|
|
|
Color: stringPtr(adaptiveColorToString(t.SyntaxFunction())),
|
|
|
|
|
|
},
|
|
|
|
|
|
NameFunction: ansi.StylePrimitive{
|
|
|
|
|
|
Color: stringPtr(adaptiveColorToString(t.SyntaxFunction())),
|
|
|
|
|
|
},
|
|
|
|
|
|
LiteralNumber: ansi.StylePrimitive{
|
|
|
|
|
|
Color: stringPtr(adaptiveColorToString(t.SyntaxNumber())),
|
|
|
|
|
|
},
|
|
|
|
|
|
LiteralString: ansi.StylePrimitive{
|
|
|
|
|
|
Color: stringPtr(adaptiveColorToString(t.SyntaxString())),
|
|
|
|
|
|
},
|
|
|
|
|
|
LiteralStringEscape: ansi.StylePrimitive{
|
|
|
|
|
|
Color: stringPtr(adaptiveColorToString(t.SyntaxKeyword())),
|
|
|
|
|
|
},
|
|
|
|
|
|
GenericDeleted: ansi.StylePrimitive{
|
|
|
|
|
|
Color: stringPtr(adaptiveColorToString(t.DiffRemoved())),
|
|
|
|
|
|
},
|
|
|
|
|
|
GenericEmph: ansi.StylePrimitive{
|
|
|
|
|
|
Color: stringPtr(adaptiveColorToString(t.MarkdownEmph())),
|
|
|
|
|
|
Italic: boolPtr(true),
|
|
|
|
|
|
},
|
|
|
|
|
|
GenericInserted: ansi.StylePrimitive{
|
|
|
|
|
|
Color: stringPtr(adaptiveColorToString(t.DiffAdded())),
|
|
|
|
|
|
},
|
|
|
|
|
|
GenericStrong: ansi.StylePrimitive{
|
|
|
|
|
|
Color: stringPtr(adaptiveColorToString(t.MarkdownStrong())),
|
|
|
|
|
|
Bold: boolPtr(true),
|
|
|
|
|
|
},
|
|
|
|
|
|
GenericSubheading: ansi.StylePrimitive{
|
|
|
|
|
|
Color: stringPtr(adaptiveColorToString(t.MarkdownHeading())),
|
|
|
|
|
|
},
|
|
|
|
|
|
},
|
|
|
|
|
|
},
|
|
|
|
|
|
Table: ansi.StyleTable{
|
|
|
|
|
|
StyleBlock: ansi.StyleBlock{
|
|
|
|
|
|
StylePrimitive: ansi.StylePrimitive{
|
|
|
|
|
|
BlockPrefix: "\n",
|
|
|
|
|
|
BlockSuffix: "\n",
|
|
|
|
|
|
},
|
|
|
|
|
|
},
|
|
|
|
|
|
CenterSeparator: stringPtr("┼"),
|
|
|
|
|
|
ColumnSeparator: stringPtr("│"),
|
|
|
|
|
|
RowSeparator: stringPtr("─"),
|
|
|
|
|
|
},
|
|
|
|
|
|
DefinitionDescription: ansi.StylePrimitive{
|
|
|
|
|
|
BlockPrefix: "\n ❯ ",
|
|
|
|
|
|
Color: stringPtr(adaptiveColorToString(t.MarkdownLinkText())),
|
|
|
|
|
|
},
|
|
|
|
|
|
Text: ansi.StylePrimitive{
|
|
|
|
|
|
Color: stringPtr(adaptiveColorToString(t.MarkdownText())),
|
|
|
|
|
|
},
|
|
|
|
|
|
Paragraph: ansi.StyleBlock{
|
2025-04-12 02:01:45 +02:00
|
|
|
|
StylePrimitive: ansi.StylePrimitive{
|
2025-05-01 05:49:26 -05:00
|
|
|
|
Color: stringPtr(adaptiveColorToString(t.MarkdownText())),
|
2025-04-12 02:01:45 +02:00
|
|
|
|
},
|
|
|
|
|
|
},
|
2025-05-01 05:49:26 -05:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// adaptiveColorToString converts a lipgloss.AdaptiveColor to the appropriate
|
|
|
|
|
|
// hex color string based on the current terminal background
|
|
|
|
|
|
func adaptiveColorToString(color lipgloss.AdaptiveColor) string {
|
|
|
|
|
|
if lipgloss.HasDarkBackground() {
|
|
|
|
|
|
return color.Dark
|
|
|
|
|
|
}
|
|
|
|
|
|
return color.Light
|
2025-04-12 02:01:45 +02:00
|
|
|
|
}
|