package mdprint import ( "fmt" "strings" "hub.gaomia.site/titor/YunShu/pkg/style" ) func renderNode(node Node) string { switch n := node.(type) { case Heading: return renderHeading(n) case Paragraph: return renderParagraph(n) case CodeBlock: return renderCodeBlock(n) case Blockquote: return renderBlockquote(n) case List: return renderList(n) case Table: return renderTable(n) case ThematicBreak: return "\n" + style.Dim.Render("────────────────────────────") + "\n" case Text: return style.New().Render(n.Text) case Bold: return renderBold(n) case Italic: return renderItalic(n) case Code: return renderInlineCode(n) case Link: return renderLink(n) default: return fmt.Sprintf("%v", n) } } func renderInline(nodes []Node) string { var b strings.Builder for _, child := range nodes { b.WriteString(renderNode(child)) } return b.String() } var headingConfig = []struct { symbol string style *style.Style }{ {"▪ ", style.New().Bold().FgHex("#6B8E9B")}, {"▪ ", style.New().Bold().FgHex("#89A894")}, {"▪ ", style.New().Bold().FgHex("#A6C0B5")}, {"▫ ", style.New().Bold().FgHex("#C3B1BD")}, {"▫ ", style.New().Bold().FgHex("#7B8E8A")}, {"", style.New().Bold().Dim()}, } func renderHeading(h Heading) string { cfg := headingConfig[0] if h.Level-1 < len(headingConfig) { cfg = headingConfig[h.Level-1] } suffix := "" if h.Level == 1 { suffix = "\n" } return "\n" + cfg.style.Render(cfg.symbol+renderInline(h.Content)) + suffix } func renderParagraph(p Paragraph) string { return renderInline(p.Content) } func renderCodeBlock(c CodeBlock) string { var b strings.Builder if c.Lang != "" { b.WriteString(style.Dim.Render(c.Lang + " ") + "\n") } for _, line := range strings.Split(c.Body, "\n") { b.WriteString(" " + style.New().Fg(style.ColorYellow).Render(line) + "\n") } return strings.TrimRight(b.String(), "\n") } func renderBlockquote(q Blockquote) string { var b strings.Builder for i, child := range q.Children { if i > 0 { b.WriteString("\n") } rendered := renderNode(child) for _, line := range strings.Split(rendered, "\n") { b.WriteString(style.Dim.Render("│ ") + line + "\n") } } return strings.TrimRight(b.String(), "\n") } func renderList(l List) string { var b strings.Builder for i, item := range l.Items { if i > 0 { b.WriteString("\n") } prefix := "- " if l.Ordered { prefix = fmt.Sprintf("%d. ", i+1) } if item.Checked != nil { check := " " if *item.Checked { check = "x" } prefix = fmt.Sprintf("- [%s] ", check) } b.WriteString(style.Dim.Render(prefix) + renderInline(item.Content)) } return b.String() } func renderTable(t Table) string { if len(t.Headers) == 0 { return "" } var b strings.Builder colWidths := make([]int, len(t.Headers)) for i, h := range t.Headers { colWidths[i] = len(h) } for _, row := range t.Rows { for i, cell := range row { if i < len(colWidths) && len(cell) > colWidths[i] { colWidths[i] = len(cell) } } } renderRow := func(cells []string, isHeader bool) { b.WriteString("| ") for i, cell := range cells { if i > 0 { b.WriteString(" | ") } if i < len(colWidths) { cell = fmt.Sprintf("%-*s", colWidths[i], cell) } if isHeader { b.WriteString(style.Bold.Render(cell)) } else { b.WriteString(cell) } } b.WriteString(" |") } renderRow(t.Headers, true) sep := "| " for i, w := range colWidths { if i > 0 { sep += " | " } sep += strings.Repeat("-", w) } sep += " |" b.WriteString("\n" + style.Dim.Render(sep)) for _, row := range t.Rows { b.WriteString("\n") renderRow(row, false) } return b.String() } func renderBold(b Bold) string { return style.New().Bold().Render(renderInline(b.Content)) } func renderItalic(i Italic) string { return style.New().Italic().Render(renderInline(i.Content)) } func renderInlineCode(c Code) string { return style.New().Fg(style.ColorYellow).Render("`" + c.Text + "`") } func renderLink(l Link) string { text := renderInline(l.Content) url := l.URL if text == "" { text = url url = "" } styled := style.New().Underline().Fg(style.ColorBlue).Render(text) if url != "" { styled += style.Dim.Render(" (" + url + ")") } return styled }