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  }