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