1
2
3
4
5 package markdown
6
7 import (
8 "bytes"
9 "fmt"
10 "strings"
11 )
12
13 type List struct {
14 Position
15 Bullet rune
16 Start int
17 Loose bool
18 Items []Block
19 }
20
21 type Item struct {
22 Position
23 Blocks []Block
24 width int
25 }
26
27 func (b *List) PrintHTML(buf *bytes.Buffer) {
28 if b.Bullet == '.' || b.Bullet == ')' {
29 buf.WriteString("<ol")
30 if b.Start != 1 {
31 fmt.Fprintf(buf, " start=\"%d\"", b.Start)
32 }
33 buf.WriteString(">\n")
34 } else {
35 buf.WriteString("<ul>\n")
36 }
37 for _, c := range b.Items {
38 c.PrintHTML(buf)
39 }
40 if b.Bullet == '.' || b.Bullet == ')' {
41 buf.WriteString("</ol>\n")
42 } else {
43 buf.WriteString("</ul>\n")
44 }
45 }
46
47 func (b *List) printMarkdown(buf *bytes.Buffer, s mdState) {
48 if buf.Len() > 0 && buf.Bytes()[buf.Len()-1] != '\n' {
49 buf.WriteByte('\n')
50 }
51 s.bullet = b.Bullet
52 s.num = b.Start
53 for i, item := range b.Items {
54 if i > 0 && b.Loose {
55 buf.WriteByte('\n')
56 }
57 item.printMarkdown(buf, s)
58 s.num++
59 }
60 }
61
62 func (b *Item) printMarkdown(buf *bytes.Buffer, s mdState) {
63 var marker string
64 if s.bullet == '.' || s.bullet == ')' {
65 marker = fmt.Sprintf("%d%c ", s.num, s.bullet)
66 } else {
67 marker = fmt.Sprintf("%c ", s.bullet)
68 }
69 marker = strings.Repeat(" ", b.width-len(marker)) + marker
70 s.prefix1 = s.prefix + marker
71 s.prefix += strings.Repeat(" ", len(marker))
72 printMarkdownBlocks(b.Blocks, buf, s)
73 }
74
75 func (b *Item) PrintHTML(buf *bytes.Buffer) {
76 buf.WriteString("<li>")
77 if len(b.Blocks) > 0 {
78 if _, ok := b.Blocks[0].(*Text); !ok {
79 buf.WriteString("\n")
80 }
81 }
82 for i, c := range b.Blocks {
83 c.PrintHTML(buf)
84 if i+1 < len(b.Blocks) {
85 if _, ok := c.(*Text); ok {
86 buf.WriteString("\n")
87 }
88 }
89 }
90 buf.WriteString("</li>\n")
91 }
92
93 type listBuilder struct {
94 bullet rune
95 num int
96 loose bool
97 item *itemBuilder
98 todo func() line
99 }
100
101 func (b *listBuilder) build(p buildState) Block {
102 blocks := p.blocks()
103 pos := p.pos()
104
105
106 pos.EndLine = blocks[len(blocks)-1].Pos().EndLine
107 Loose:
108 for i, c := range blocks {
109 c := c.(*Item)
110 if i+1 < len(blocks) {
111 if blocks[i+1].Pos().StartLine-c.EndLine > 1 {
112 b.loose = true
113 break Loose
114 }
115 }
116 for j, d := range c.Blocks {
117 endLine := d.Pos().EndLine
118 if j+1 < len(c.Blocks) {
119 if c.Blocks[j+1].Pos().StartLine-endLine > 1 {
120 b.loose = true
121 break Loose
122 }
123 }
124 }
125 }
126
127 if !b.loose {
128 for _, c := range blocks {
129 c := c.(*Item)
130 for i, d := range c.Blocks {
131 if p, ok := d.(*Paragraph); ok {
132 c.Blocks[i] = p.Text
133 }
134 }
135 }
136 }
137
138 return &List{
139 pos,
140 b.bullet,
141 b.num,
142 b.loose,
143 p.blocks(),
144 }
145 }
146
147 func (b *itemBuilder) build(p buildState) Block {
148 b.list.item = nil
149 return &Item{p.pos(), p.blocks(), b.width}
150 }
151
152 func (c *listBuilder) extend(p *parseState, s line) (line, bool) {
153 d := c.item
154 if d != nil && s.trimSpace(d.width, d.width, true) || d == nil && s.isBlank() {
155 return s, true
156 }
157 return s, false
158 }
159
160 func (c *itemBuilder) extend(p *parseState, s line) (line, bool) {
161 if s.isBlank() && !c.haveContent {
162 return s, false
163 }
164 if s.isBlank() {
165
166
167 return line{}, true
168 }
169 if !s.isBlank() {
170 c.haveContent = true
171 }
172 return s, true
173 }
174
175 func newListItem(p *parseState, s line) (line, bool) {
176 if list, ok := p.curB().(*listBuilder); ok && list.todo != nil {
177 s = list.todo()
178 list.todo = nil
179 return s, true
180 }
181 if p.startListItem(&s) {
182 return s, true
183 }
184 return s, false
185 }
186
187 func (p *parseState) startListItem(s *line) bool {
188 t := *s
189 n := 0
190 for i := 0; i < 3; i++ {
191 if !t.trimSpace(1, 1, false) {
192 break
193 }
194 n++
195 }
196 bullet := t.peek()
197 var num int
198 Switch:
199 switch bullet {
200 default:
201 return false
202 case '-', '*', '+':
203 t.trim(bullet)
204 n++
205 case '0', '1', '2', '3', '4', '5', '6', '7', '8', '9':
206 for j := t.i; ; j++ {
207 if j >= len(t.text) {
208 return false
209 }
210 c := t.text[j]
211 if c == '.' || c == ')' {
212
213 bullet = c
214 j++
215 n += j - t.i
216 t.i = j
217 break Switch
218 }
219 if c < '0' || '9' < c {
220 return false
221 }
222 if j-t.i >= 9 {
223 return false
224 }
225 num = num*10 + int(c) - '0'
226 }
227
228 }
229 if !t.trimSpace(1, 1, true) {
230 return false
231 }
232 n++
233 tt := t
234 m := 0
235 for i := 0; i < 3 && tt.trimSpace(1, 1, false); i++ {
236 m++
237 }
238 if !tt.trimSpace(1, 1, true) {
239 n += m
240 t = tt
241 }
242
243
244
245 var list *listBuilder
246 if c, ok := p.nextB().(*listBuilder); ok {
247 list = c
248 }
249 if list == nil || list.bullet != rune(bullet) {
250
251
252
253
254
255 if list == nil && p.para() != nil && (t.isBlank() || (bullet == '.' || bullet == ')') && num != 1) {
256
257
258
259
260
261 p.corner = true
262 return false
263 }
264 list = &listBuilder{bullet: rune(bullet), num: num}
265 p.addBlock(list)
266 }
267 b := &itemBuilder{list: list, width: n, haveContent: !t.isBlank()}
268 list.todo = func() line {
269 p.addBlock(b)
270 list.item = b
271 return t
272 }
273 return true
274 }
275
276
277
278 func (p *parseState) taskList(list *List) {
279 for _, item := range list.Items {
280 item := item.(*Item)
281 if len(item.Blocks) == 0 {
282 continue
283 }
284 var text *Text
285 switch b := item.Blocks[0].(type) {
286 default:
287 continue
288 case *Paragraph:
289 text = b.Text
290 case *Text:
291 text = b
292 }
293 if len(text.Inline) < 1 {
294 continue
295 }
296 pl, ok := text.Inline[0].(*Plain)
297 if !ok {
298 continue
299 }
300 s := pl.Text
301 if len(s) < 4 || s[0] != '[' || s[2] != ']' || (s[1] != ' ' && s[1] != 'x' && s[1] != 'X') {
302 continue
303 }
304 if s[3] != ' ' && s[3] != '\t' {
305 p.corner = true
306 continue
307 }
308 text.Inline = append([]Inline{&Task{Checked: s[1] == 'x' || s[1] == 'X'},
309 &Plain{Text: s[len("[x]"):]}}, text.Inline[1:]...)
310 }
311 }
312
313 func ins(first Inline, x []Inline) []Inline {
314 x = append(x, nil)
315 copy(x[1:], x)
316 x[0] = first
317 return x
318 }
319
320 type Task struct {
321 Checked bool
322 }
323
324 func (x *Task) Inline() {
325 }
326
327 func (x *Task) PrintHTML(buf *bytes.Buffer) {
328 buf.WriteString("<input ")
329 if x.Checked {
330 buf.WriteString(`checked="" `)
331 }
332 buf.WriteString(`disabled="" type="checkbox">`)
333 }
334
335 func (x *Task) printMarkdown(buf *bytes.Buffer) {
336 x.PrintText(buf)
337 }
338
339 func (x *Task) PrintText(buf *bytes.Buffer) {
340 buf.WriteByte('[')
341 if x.Checked {
342 buf.WriteByte('x')
343 } else {
344 buf.WriteByte(' ')
345 }
346 buf.WriteByte(']')
347 buf.WriteByte(' ')
348 }
349
350 func listCorner(list *List) bool {
351 for _, item := range list.Items {
352 item := item.(*Item)
353 if len(item.Blocks) == 0 {
354
355 return true
356 }
357 switch item.Blocks[0].(type) {
358 case *List, *ThematicBreak, *CodeBlock:
359
360 return true
361 }
362 }
363 return false
364 }
365
View as plain text