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