Source file src/cmd/vendor/rsc.io/markdown/code.go

     1  // Copyright 2021 The Go Authors. All rights reserved.
     2  // Use of this source code is governed by a BSD-style
     3  // license that can be found in the LICENSE file.
     4  
     5  package markdown
     6  
     7  import (
     8  	"bytes"
     9  	"fmt"
    10  	"strings"
    11  )
    12  
    13  type CodeBlock struct {
    14  	Position
    15  	Fence string
    16  	Info  string
    17  	Text  []string
    18  }
    19  
    20  func (b *CodeBlock) PrintHTML(buf *bytes.Buffer) {
    21  	if buf.Len() > 0 && buf.Bytes()[buf.Len()-1] != '\n' {
    22  		buf.WriteString("\n")
    23  	}
    24  	buf.WriteString("<pre><code")
    25  	if b.Info != "" {
    26  		// https://spec.commonmark.org/0.30/#info-string
    27  		// “The first word of the info string is typically used to
    28  		// specify the language of the code sample...”
    29  		// No definition of what “first word” means though.
    30  		// The Dingus splits on isUnicodeSpace, but Goldmark only uses space.
    31  		lang := b.Info
    32  		for i, c := range lang {
    33  			if isUnicodeSpace(c) {
    34  				lang = lang[:i]
    35  				break
    36  			}
    37  		}
    38  		fmt.Fprintf(buf, " class=\"language-%s\"", htmlQuoteEscaper.Replace(lang))
    39  	}
    40  	buf.WriteString(">")
    41  	if b.Fence == "" { // TODO move
    42  		for len(b.Text) > 0 && trimSpaceTab(b.Text[len(b.Text)-1]) == "" {
    43  			b.Text = b.Text[:len(b.Text)-1]
    44  		}
    45  	}
    46  	for _, s := range b.Text {
    47  		buf.WriteString(htmlEscaper.Replace(s))
    48  		buf.WriteString("\n")
    49  	}
    50  	buf.WriteString("</code></pre>\n")
    51  }
    52  
    53  // func initialSpaces(s string) int {
    54  // 	for i := 0; i < len(s); i++ {
    55  // 		if s[i] != ' ' {
    56  // 			return i
    57  // 		}
    58  // 	}
    59  // 	return len(s)
    60  // }
    61  
    62  func (b *CodeBlock) printMarkdown(buf *bytes.Buffer, s mdState) {
    63  	prefix1 := s.prefix1
    64  	if prefix1 == "" {
    65  		prefix1 = s.prefix
    66  	}
    67  	if b.Fence == "" {
    68  		for i, line := range b.Text {
    69  			// Ignore final empty line (why is it even there?).
    70  			if i == len(b.Text)-1 && len(line) == 0 {
    71  				break
    72  			}
    73  			// var iline string
    74  			// is := initialSpaces(line)
    75  			// if is < 4 {
    76  			// 	iline = "    " + line
    77  			// } else {
    78  			// 	iline = "\t" + line[4:]
    79  			// }
    80  			// Indent by 4 spaces.
    81  			pre := s.prefix
    82  			if i == 0 {
    83  				pre = prefix1
    84  			}
    85  			fmt.Fprintf(buf, "%s%s%s\n", pre, "    ", line)
    86  		}
    87  	} else {
    88  		fmt.Fprintf(buf, "%s%s\n", prefix1, b.Fence)
    89  		for _, line := range b.Text {
    90  			fmt.Fprintf(buf, "%s%s\n", s.prefix, line)
    91  		}
    92  		fmt.Fprintf(buf, "%s%s\n", s.prefix, b.Fence)
    93  	}
    94  }
    95  
    96  func newPre(p *parseState, s line) (line, bool) {
    97  	peek2 := s
    98  	if p.para() == nil && peek2.trimSpace(4, 4, false) && !peek2.isBlank() {
    99  		b := &preBuilder{ /*indent: strings.TrimSuffix(s.string(), peek2.string())*/ }
   100  		p.addBlock(b)
   101  		p.corner = p.corner || peek2.nl != '\n' // goldmark does not normalize to \n
   102  		b.text = append(b.text, peek2.string())
   103  		return line{}, true
   104  	}
   105  	return s, false
   106  }
   107  
   108  func newFence(p *parseState, s line) (line, bool) {
   109  	var fence, info string
   110  	var n int
   111  	peek := s
   112  	if peek.trimFence(&fence, &info, &n) {
   113  		if fence[0] == '~' && info != "" {
   114  			// goldmark does not handle info after ~~~
   115  			p.corner = true
   116  		} else if info != "" && !isLetter(info[0]) {
   117  			// goldmark does not allow numbered info.
   118  			// goldmark does not treat a tab as introducing a new word.
   119  			p.corner = true
   120  		}
   121  		for _, c := range info {
   122  			if isUnicodeSpace(c) {
   123  				if c != ' ' {
   124  					// goldmark only breaks on space
   125  					p.corner = true
   126  				}
   127  				break
   128  			}
   129  		}
   130  
   131  		p.addBlock(&fenceBuilder{fence, info, n, nil})
   132  		return line{}, true
   133  	}
   134  	return s, false
   135  }
   136  
   137  func (s *line) trimFence(fence, info *string, n *int) bool {
   138  	t := *s
   139  	*n = 0
   140  	for *n < 3 && t.trimSpace(1, 1, false) {
   141  		*n++
   142  	}
   143  	switch c := t.peek(); c {
   144  	case '`', '~':
   145  		f := t.string()
   146  		n := 0
   147  		for i := 0; ; i++ {
   148  			if !t.trim(c) {
   149  				if i >= 3 {
   150  					break
   151  				}
   152  				return false
   153  			}
   154  			n++
   155  		}
   156  		txt := mdUnescaper.Replace(t.trimString())
   157  		if c == '`' && strings.Contains(txt, "`") {
   158  			return false
   159  		}
   160  		txt = trimSpaceTab(txt)
   161  		*info = txt
   162  
   163  		*fence = f[:n]
   164  		*s = line{}
   165  		return true
   166  	}
   167  	return false
   168  }
   169  
   170  // For indented code blocks.
   171  type preBuilder struct {
   172  	indent string
   173  	text   []string
   174  }
   175  
   176  func (c *preBuilder) extend(p *parseState, s line) (line, bool) {
   177  	if !s.trimSpace(4, 4, true) {
   178  		return s, false
   179  	}
   180  	c.text = append(c.text, s.string())
   181  	p.corner = p.corner || s.nl != '\n' // goldmark does not normalize to \n
   182  	return line{}, true
   183  }
   184  
   185  func (b *preBuilder) build(p buildState) Block {
   186  	return &CodeBlock{p.pos(), "", "", b.text}
   187  }
   188  
   189  type fenceBuilder struct {
   190  	fence string
   191  	info  string
   192  	n     int
   193  	text  []string
   194  }
   195  
   196  func (c *fenceBuilder) extend(p *parseState, s line) (line, bool) {
   197  	var fence, info string
   198  	var n int
   199  	if t := s; t.trimFence(&fence, &info, &n) && strings.HasPrefix(fence, c.fence) && info == "" {
   200  		return line{}, false
   201  	}
   202  	if !s.trimSpace(c.n, c.n, false) {
   203  		p.corner = true // goldmark mishandles fenced blank lines with not enough spaces
   204  		s.trimSpace(0, c.n, false)
   205  	}
   206  	c.text = append(c.text, s.string())
   207  	p.corner = p.corner || s.nl != '\n' // goldmark does not normalize to \n
   208  	return line{}, true
   209  }
   210  
   211  func (c *fenceBuilder) build(p buildState) Block {
   212  	return &CodeBlock{
   213  		p.pos(),
   214  		c.fence,
   215  		c.info,
   216  		c.text,
   217  	}
   218  }
   219  

View as plain text