github.com/charmbracelet/glamour@v0.7.0/ansi/elements.go (about)

     1  package ansi
     2  
     3  import (
     4  	"bytes"
     5  	"fmt"
     6  	"html"
     7  	"io"
     8  	"strings"
     9  
    10  	east "github.com/yuin/goldmark-emoji/ast"
    11  	"github.com/yuin/goldmark/ast"
    12  	astext "github.com/yuin/goldmark/extension/ast"
    13  )
    14  
    15  // ElementRenderer is called when entering a markdown node.
    16  type ElementRenderer interface {
    17  	Render(w io.Writer, ctx RenderContext) error
    18  }
    19  
    20  // ElementFinisher is called when leaving a markdown node.
    21  type ElementFinisher interface {
    22  	Finish(w io.Writer, ctx RenderContext) error
    23  }
    24  
    25  // An Element is used to instruct the renderer how to handle individual markdown
    26  // nodes.
    27  type Element struct {
    28  	Entering string
    29  	Exiting  string
    30  	Renderer ElementRenderer
    31  	Finisher ElementFinisher
    32  }
    33  
    34  // NewElement returns the appropriate render Element for a given node.
    35  func (tr *ANSIRenderer) NewElement(node ast.Node, source []byte) Element {
    36  	ctx := tr.context
    37  	// fmt.Print(strings.Repeat("  ", ctx.blockStack.Len()), node.Type(), node.Kind())
    38  	// defer fmt.Println()
    39  
    40  	switch node.Kind() {
    41  	// Document
    42  	case ast.KindDocument:
    43  		e := &BlockElement{
    44  			Block:  &bytes.Buffer{},
    45  			Style:  ctx.options.Styles.Document,
    46  			Margin: true,
    47  		}
    48  		return Element{
    49  			Renderer: e,
    50  			Finisher: e,
    51  		}
    52  
    53  	// Heading
    54  	case ast.KindHeading:
    55  		n := node.(*ast.Heading)
    56  		he := &HeadingElement{
    57  			Level: n.Level,
    58  			First: node.PreviousSibling() == nil,
    59  		}
    60  		return Element{
    61  			Exiting:  "",
    62  			Renderer: he,
    63  			Finisher: he,
    64  		}
    65  
    66  	// Paragraph
    67  	case ast.KindParagraph:
    68  		if node.Parent() != nil && node.Parent().Kind() == ast.KindListItem {
    69  			return Element{}
    70  		}
    71  		return Element{
    72  			Renderer: &ParagraphElement{
    73  				First: node.PreviousSibling() == nil,
    74  			},
    75  			Finisher: &ParagraphElement{},
    76  		}
    77  
    78  	// Blockquote
    79  	case ast.KindBlockquote:
    80  		e := &BlockElement{
    81  			Block:   &bytes.Buffer{},
    82  			Style:   cascadeStyle(ctx.blockStack.Current().Style, ctx.options.Styles.BlockQuote, false),
    83  			Margin:  true,
    84  			Newline: true,
    85  		}
    86  		return Element{
    87  			Entering: "\n",
    88  			Renderer: e,
    89  			Finisher: e,
    90  		}
    91  
    92  	// Lists
    93  	case ast.KindList:
    94  		s := ctx.options.Styles.List.StyleBlock
    95  		if s.Indent == nil {
    96  			var i uint
    97  			s.Indent = &i
    98  		}
    99  		n := node.Parent()
   100  		for n != nil {
   101  			if n.Kind() == ast.KindList {
   102  				i := ctx.options.Styles.List.LevelIndent
   103  				s.Indent = &i
   104  				break
   105  			}
   106  			n = n.Parent()
   107  		}
   108  
   109  		e := &BlockElement{
   110  			Block:   &bytes.Buffer{},
   111  			Style:   cascadeStyle(ctx.blockStack.Current().Style, s, false),
   112  			Margin:  true,
   113  			Newline: true,
   114  		}
   115  		return Element{
   116  			Entering: "\n",
   117  			Renderer: e,
   118  			Finisher: e,
   119  		}
   120  
   121  	case ast.KindListItem:
   122  		var l uint
   123  		var e uint
   124  		l = 1
   125  		n := node
   126  		for n.PreviousSibling() != nil && (n.PreviousSibling().Kind() == ast.KindListItem) {
   127  			l++
   128  			n = n.PreviousSibling()
   129  		}
   130  		if node.Parent().(*ast.List).IsOrdered() {
   131  			e = l
   132  			if node.Parent().(*ast.List).Start != 1 {
   133  				e += uint(node.Parent().(*ast.List).Start) - 1
   134  			}
   135  		}
   136  
   137  		post := "\n"
   138  		if (node.LastChild() != nil && node.LastChild().Kind() == ast.KindList) ||
   139  			node.NextSibling() == nil {
   140  			post = ""
   141  		}
   142  
   143  		if node.FirstChild() != nil &&
   144  			node.FirstChild().FirstChild() != nil &&
   145  			node.FirstChild().FirstChild().Kind() == astext.KindTaskCheckBox {
   146  			nc := node.FirstChild().FirstChild().(*astext.TaskCheckBox)
   147  
   148  			return Element{
   149  				Exiting: post,
   150  				Renderer: &TaskElement{
   151  					Checked: nc.IsChecked,
   152  				},
   153  			}
   154  		}
   155  
   156  		return Element{
   157  			Exiting: post,
   158  			Renderer: &ItemElement{
   159  				IsOrdered:   node.Parent().(*ast.List).IsOrdered(),
   160  				Enumeration: e,
   161  			},
   162  		}
   163  
   164  	// Text Elements
   165  	case ast.KindText:
   166  		n := node.(*ast.Text)
   167  		s := string(n.Segment.Value(source))
   168  
   169  		if n.HardLineBreak() || (n.SoftLineBreak()) {
   170  			s += "\n"
   171  		}
   172  		return Element{
   173  			Renderer: &BaseElement{
   174  				Token: html.UnescapeString(s),
   175  				Style: ctx.options.Styles.Text,
   176  			},
   177  		}
   178  
   179  	case ast.KindEmphasis:
   180  		n := node.(*ast.Emphasis)
   181  		s := string(n.Text(source))
   182  		style := ctx.options.Styles.Emph
   183  		if n.Level > 1 {
   184  			style = ctx.options.Styles.Strong
   185  		}
   186  
   187  		return Element{
   188  			Renderer: &BaseElement{
   189  				Token: html.UnescapeString(s),
   190  				Style: style,
   191  			},
   192  		}
   193  
   194  	case astext.KindStrikethrough:
   195  		n := node.(*astext.Strikethrough)
   196  		s := string(n.Text(source))
   197  		style := ctx.options.Styles.Strikethrough
   198  
   199  		return Element{
   200  			Renderer: &BaseElement{
   201  				Token: html.UnescapeString(s),
   202  				Style: style,
   203  			},
   204  		}
   205  
   206  	case ast.KindThematicBreak:
   207  		return Element{
   208  			Entering: "",
   209  			Exiting:  "",
   210  			Renderer: &BaseElement{
   211  				Style: ctx.options.Styles.HorizontalRule,
   212  			},
   213  		}
   214  
   215  	// Links
   216  	case ast.KindLink:
   217  		n := node.(*ast.Link)
   218  		return Element{
   219  			Renderer: &LinkElement{
   220  				Text:    textFromChildren(node, source),
   221  				BaseURL: ctx.options.BaseURL,
   222  				URL:     string(n.Destination),
   223  			},
   224  		}
   225  	case ast.KindAutoLink:
   226  		n := node.(*ast.AutoLink)
   227  		u := string(n.URL(source))
   228  		label := string(n.Label(source))
   229  		if n.AutoLinkType == ast.AutoLinkEmail && !strings.HasPrefix(strings.ToLower(u), "mailto:") {
   230  			u = "mailto:" + u
   231  		}
   232  
   233  		return Element{
   234  			Renderer: &LinkElement{
   235  				Text:    label,
   236  				BaseURL: ctx.options.BaseURL,
   237  				URL:     u,
   238  			},
   239  		}
   240  
   241  	// Images
   242  	case ast.KindImage:
   243  		n := node.(*ast.Image)
   244  		text := string(n.Text(source))
   245  		return Element{
   246  			Renderer: &ImageElement{
   247  				Text:    text,
   248  				BaseURL: ctx.options.BaseURL,
   249  				URL:     string(n.Destination),
   250  			},
   251  		}
   252  
   253  	// Code
   254  	case ast.KindFencedCodeBlock:
   255  		n := node.(*ast.FencedCodeBlock)
   256  		l := n.Lines().Len()
   257  		s := ""
   258  		for i := 0; i < l; i++ {
   259  			line := n.Lines().At(i)
   260  			s += string(line.Value(source))
   261  		}
   262  		return Element{
   263  			Entering: "\n",
   264  			Renderer: &CodeBlockElement{
   265  				Code:     s,
   266  				Language: string(n.Language(source)),
   267  			},
   268  		}
   269  
   270  	case ast.KindCodeBlock:
   271  		n := node.(*ast.CodeBlock)
   272  		l := n.Lines().Len()
   273  		s := ""
   274  		for i := 0; i < l; i++ {
   275  			line := n.Lines().At(i)
   276  			s += string(line.Value(source))
   277  		}
   278  		return Element{
   279  			Entering: "\n",
   280  			Renderer: &CodeBlockElement{
   281  				Code: s,
   282  			},
   283  		}
   284  
   285  	case ast.KindCodeSpan:
   286  		// n := node.(*ast.CodeSpan)
   287  		e := &BlockElement{
   288  			Block: &bytes.Buffer{},
   289  			Style: cascadeStyle(ctx.blockStack.Current().Style, ctx.options.Styles.Code, false),
   290  		}
   291  		return Element{
   292  			Renderer: e,
   293  			Finisher: e,
   294  		}
   295  
   296  	// Tables
   297  	case astext.KindTable:
   298  		table := node.(*astext.Table)
   299  		te := &TableElement{table: table}
   300  		return Element{
   301  			Entering: "\n",
   302  			Renderer: te,
   303  			Finisher: te,
   304  		}
   305  
   306  	case astext.KindTableCell:
   307  		s := ""
   308  		n := node.FirstChild()
   309  		for n != nil {
   310  			switch t := n.(type) {
   311  			case *ast.AutoLink:
   312  				s += string(t.Label(source))
   313  			default:
   314  				s += string(n.Text(source))
   315  			}
   316  
   317  			n = n.NextSibling()
   318  		}
   319  
   320  		return Element{
   321  			Renderer: &TableCellElement{
   322  				Text: s,
   323  				Head: node.Parent().Kind() == astext.KindTableHeader,
   324  			},
   325  		}
   326  
   327  	case astext.KindTableHeader:
   328  		return Element{
   329  			Finisher: &TableHeadElement{},
   330  		}
   331  	case astext.KindTableRow:
   332  		return Element{
   333  			Finisher: &TableRowElement{},
   334  		}
   335  
   336  	// HTML Elements
   337  	case ast.KindHTMLBlock:
   338  		n := node.(*ast.HTMLBlock)
   339  		return Element{
   340  			Renderer: &BaseElement{
   341  				Token: ctx.SanitizeHTML(string(n.Text(source)), true),
   342  				Style: ctx.options.Styles.HTMLBlock.StylePrimitive,
   343  			},
   344  		}
   345  	case ast.KindRawHTML:
   346  		n := node.(*ast.RawHTML)
   347  		return Element{
   348  			Renderer: &BaseElement{
   349  				Token: ctx.SanitizeHTML(string(n.Text(source)), true),
   350  				Style: ctx.options.Styles.HTMLSpan.StylePrimitive,
   351  			},
   352  		}
   353  
   354  	// Definition Lists
   355  	case astext.KindDefinitionList:
   356  		e := &BlockElement{
   357  			Block:   &bytes.Buffer{},
   358  			Style:   cascadeStyle(ctx.blockStack.Current().Style, ctx.options.Styles.DefinitionList, false),
   359  			Margin:  true,
   360  			Newline: true,
   361  		}
   362  		return Element{
   363  			Entering: "\n",
   364  			Renderer: e,
   365  			Finisher: e,
   366  		}
   367  
   368  	case astext.KindDefinitionTerm:
   369  		return Element{
   370  			Renderer: &BaseElement{
   371  				Style: ctx.options.Styles.DefinitionTerm,
   372  			},
   373  		}
   374  
   375  	case astext.KindDefinitionDescription:
   376  		return Element{
   377  			Renderer: &BaseElement{
   378  				Style: ctx.options.Styles.DefinitionDescription,
   379  			},
   380  		}
   381  
   382  	// Handled by parents
   383  	case astext.KindTaskCheckBox:
   384  		// handled by KindListItem
   385  		return Element{}
   386  	case ast.KindTextBlock:
   387  		return Element{}
   388  
   389  	case east.KindEmoji:
   390  		n := node.(*east.Emoji)
   391  		return Element{
   392  			Renderer: &BaseElement{
   393  				Token: string(n.Value.Unicode),
   394  			},
   395  		}
   396  
   397  	// Unknown case
   398  	default:
   399  		fmt.Println("Warning: unhandled element", node.Kind().String())
   400  		return Element{}
   401  	}
   402  }
   403  
   404  func textFromChildren(node ast.Node, source []byte) string {
   405  	var s string
   406  	for c := node.FirstChild(); c != nil; c = c.NextSibling() {
   407  		if c.Kind() == ast.KindText {
   408  			cn := c.(*ast.Text)
   409  			s += string(cn.Segment.Value(source))
   410  
   411  			if cn.HardLineBreak() || (cn.SoftLineBreak()) {
   412  				s += "\n"
   413  			}
   414  		} else {
   415  			s += string(c.Text(source))
   416  		}
   417  	}
   418  
   419  	return s
   420  }