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 5package markdown 6 7import ( 8 "bytes" 9 "fmt" 10 "strings" 11) 12 13type CodeBlock struct { 14 Position 15 Fence string 16 Info string 17 Text []string 18} 19 20func (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 62func (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 96func 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 108func 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 137func (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. 171type preBuilder struct { 172 indent string 173 text []string 174} 175 176func (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 185func (b *preBuilder) build(p buildState) Block { 186 return &CodeBlock{p.pos(), "", "", b.text} 187} 188 189type fenceBuilder struct { 190 fence string 191 info string 192 n int 193 text []string 194} 195 196func (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 211func (c *fenceBuilder) build(p buildState) Block { 212 return &CodeBlock{ 213 p.pos(), 214 c.fence, 215 c.info, 216 c.text, 217 } 218} 219