package main import ( "strings" "sync" "github.com/charmbracelet/glamour" "github.com/charmbracelet/glamour/styles" "github.com/charmbracelet/lipgloss" ) var commentMarkdownRenderers sync.Map var quoteRailStyle = lipgloss.NewStyle().Foreground(lipgloss.Color("#777777")) var markdownStyleName = "dark" func renderCommentMarkdown(markdown string, width int) []string { if strings.TrimSpace(markdown) == "" { return nil } width = max(10, width) markdown = normalizeGitHubAlerts(markdown) var ( result []string block []string blockQuote bool haveBlock bool ) flush := func() { if len(block) == 0 { return } blockWidth := width if blockQuote { blockWidth = max(8, width-2) } lines := renderMarkdownFragment(strings.Join(block, "\n"), blockWidth) if haveBlock && len(lines) > 0 { result = append(result, "") } if blockQuote { rail := quoteRailStyle.Render("│ ") for i := range lines { lines[i] = rail + lines[i] } } result = append(result, lines...) haveBlock = haveBlock || len(lines) > 0 block = nil } for _, line := range strings.Split(markdown, "\n") { content, quoted := stripQuoteMarker(line) if len(block) > 0 && quoted != blockQuote { flush() } blockQuote = quoted block = append(block, content) } flush() return trimMarkdownLines(result) } func renderMarkdownFragment(markdown string, width int) []string { if strings.TrimSpace(markdown) == "" { return nil } renderer, err := commentMarkdownRenderer(width) if err != nil { return fallbackCommentLines(markdown, width) } rendered, err := renderer.Render(markdown) if err != nil { return fallbackCommentLines(markdown, width) } return trimMarkdownLines(strings.Split(strings.Trim(rendered, "\n"), "\n")) } func commentMarkdownRenderer(width int) (*glamour.TermRenderer, error) { if cached, ok := commentMarkdownRenderers.Load(width); ok { return cached.(*glamour.TermRenderer), nil } style := styles.DarkStyleConfig if markdownStyleName == "light" { style = styles.LightStyleConfig } zero := uint(0) style.Document.Margin = &zero style.Code.Prefix = "" style.Code.Suffix = "" renderer, err := glamour.NewTermRenderer( glamour.WithStyles(style), glamour.WithWordWrap(width), glamour.WithTableWrap(true), glamour.WithPreservedNewLines(), glamour.WithEmoji(), ) if err != nil { return nil, err } actual, _ := commentMarkdownRenderers.LoadOrStore(width, renderer) return actual.(*glamour.TermRenderer), nil } func stripQuoteMarker(line string) (string, bool) { trimmed := strings.TrimLeft(line, " \t") if !strings.HasPrefix(trimmed, ">") { return line, false } content := strings.TrimPrefix(trimmed, ">") content = strings.TrimPrefix(content, " ") return content, true } func normalizeGitHubAlerts(markdown string) string { alerts := map[string]string{ "[!NOTE]": "ℹ **Note**", "[!TIP]": "◆ **Tip**", "[!IMPORTANT]": "❗ **Important**", "[!WARNING]": "⚠ **Warning**", "[!CAUTION]": "⛔ **Caution**", } lines := strings.Split(markdown, "\n") for i, line := range lines { trimmed := strings.TrimSpace(line) if !strings.HasPrefix(trimmed, ">") { continue } label := strings.TrimSpace(strings.TrimPrefix(trimmed, ">")) if replacement, ok := alerts[strings.ToUpper(label)]; ok { prefix := line[:strings.Index(line, ">")+1] lines[i] = prefix + " " + replacement } } return strings.Join(lines, "\n") } func fallbackCommentLines(markdown string, width int) []string { return strings.Split(wrap(markdown, max(10, width)), "\n") } func trimMarkdownLines(lines []string) []string { for len(lines) > 0 && strings.TrimSpace(lines[0]) == "" { lines = lines[1:] } for len(lines) > 0 && strings.TrimSpace(lines[len(lines)-1]) == "" { lines = lines[:len(lines)-1] } return lines }