1package ansi
2
3import (
4 "fmt"
5 "io"
6 "net/url"
7 "strings"
8
9 east "github.com/yuin/goldmark-emoji/ast"
10 "github.com/yuin/goldmark/ast"
11 astext "github.com/yuin/goldmark/extension/ast"
12 "github.com/yuin/goldmark/renderer"
13 "github.com/yuin/goldmark/util"
14)
15
16// Options is used to configure an ANSIRenderer.
17type Options struct {
18 BaseURL string
19 WordWrap int
20 TableWrap *bool
21 InlineTableLinks bool
22 PreserveNewLines bool
23 Styles StyleConfig
24 ChromaFormatter string
25}
26
27// ANSIRenderer renders markdown content as ANSI escaped sequences.
28type ANSIRenderer struct { //nolint: revive
29 context RenderContext
30}
31
32// NewRenderer returns a new ANSIRenderer with style and options set.
33func NewRenderer(options Options) *ANSIRenderer {
34 return &ANSIRenderer{
35 context: NewRenderContext(options),
36 }
37}
38
39// RegisterFuncs implements NodeRenderer.RegisterFuncs.
40func (r *ANSIRenderer) RegisterFuncs(reg renderer.NodeRendererFuncRegisterer) {
41 // blocks
42 reg.Register(ast.KindDocument, r.renderNode)
43 reg.Register(ast.KindHeading, r.renderNode)
44 reg.Register(ast.KindBlockquote, r.renderNode)
45 reg.Register(ast.KindCodeBlock, r.renderNode)
46 reg.Register(ast.KindFencedCodeBlock, r.renderNode)
47 reg.Register(ast.KindHTMLBlock, r.renderNode)
48 reg.Register(ast.KindList, r.renderNode)
49 reg.Register(ast.KindListItem, r.renderNode)
50 reg.Register(ast.KindParagraph, r.renderNode)
51 reg.Register(ast.KindTextBlock, r.renderNode)
52 reg.Register(ast.KindThematicBreak, r.renderNode)
53
54 // inlines
55 reg.Register(ast.KindAutoLink, r.renderNode)
56 reg.Register(ast.KindCodeSpan, r.renderNode)
57 reg.Register(ast.KindEmphasis, r.renderNode)
58 reg.Register(ast.KindImage, r.renderNode)
59 reg.Register(ast.KindLink, r.renderNode)
60 reg.Register(ast.KindRawHTML, r.renderNode)
61 reg.Register(ast.KindText, r.renderNode)
62 reg.Register(ast.KindString, r.renderNode)
63
64 // tables
65 reg.Register(astext.KindTable, r.renderNode)
66 reg.Register(astext.KindTableHeader, r.renderNode)
67 reg.Register(astext.KindTableRow, r.renderNode)
68 reg.Register(astext.KindTableCell, r.renderNode)
69
70 // definitions
71 reg.Register(astext.KindDefinitionList, r.renderNode)
72 reg.Register(astext.KindDefinitionTerm, r.renderNode)
73 reg.Register(astext.KindDefinitionDescription, r.renderNode)
74
75 // footnotes
76 reg.Register(astext.KindFootnote, r.renderNode)
77 reg.Register(astext.KindFootnoteList, r.renderNode)
78 reg.Register(astext.KindFootnoteLink, r.renderNode)
79 reg.Register(astext.KindFootnoteBacklink, r.renderNode)
80
81 // checkboxes
82 reg.Register(astext.KindTaskCheckBox, r.renderNode)
83
84 // strikethrough
85 reg.Register(astext.KindStrikethrough, r.renderNode)
86
87 // emoji
88 reg.Register(east.KindEmoji, r.renderNode)
89}
90
91func (r *ANSIRenderer) renderNode(w util.BufWriter, source []byte, node ast.Node, entering bool) (ast.WalkStatus, error) {
92 writeTo := io.Writer(w)
93 bs := r.context.blockStack
94
95 // children get rendered by their parent
96 if isChild(node) {
97 return ast.WalkContinue, nil
98 }
99
100 e := r.NewElement(node, source)
101 if entering { //nolint: nestif
102 // everything below the Document element gets rendered into a block buffer
103 if bs.Len() > 0 {
104 writeTo = io.Writer(bs.Current().Block)
105 }
106
107 _, _ = io.WriteString(writeTo, e.Entering)
108 if e.Renderer != nil {
109 err := e.Renderer.Render(writeTo, r.context)
110 if err != nil {
111 return ast.WalkStop, fmt.Errorf("glamour: error rendering: %w", err)
112 }
113 }
114 } else {
115 // everything below the Document element gets rendered into a block buffer
116 if bs.Len() > 0 {
117 writeTo = io.Writer(bs.Parent().Block)
118 }
119
120 // if we're finished rendering the entire document,
121 // flush to the real writer
122 if node.Type() == ast.TypeDocument {
123 writeTo = w
124 }
125
126 if e.Finisher != nil {
127 err := e.Finisher.Finish(writeTo, r.context)
128 if err != nil {
129 return ast.WalkStop, fmt.Errorf("glamour: error finishing render: %w", err)
130 }
131 }
132
133 _, _ = io.WriteString(bs.Current().Block, e.Exiting)
134 }
135
136 return ast.WalkContinue, nil
137}
138
139func isChild(node ast.Node) bool {
140 for n := node.Parent(); n != nil; n = n.Parent() {
141 // These types are already rendered by their parent
142 switch n.Kind() {
143 case ast.KindCodeSpan, ast.KindAutoLink, ast.KindLink, ast.KindImage, ast.KindEmphasis, astext.KindStrikethrough, astext.KindTableCell:
144 return true
145 }
146 }
147
148 return false
149}
150
151func resolveRelativeURL(baseURL string, rel string) string {
152 u, err := url.Parse(rel)
153 if err != nil {
154 return rel
155 }
156 if u.IsAbs() {
157 return rel
158 }
159 u.Path = strings.TrimPrefix(u.Path, "/")
160
161 base, err := url.Parse(baseURL)
162 if err != nil {
163 return rel
164 }
165 return base.ResolveReference(u).String()
166}