aboutsummaryrefslogtreecommitdiff
path: root/src/cmd/vendor/rsc.io/markdown/list.go
diff options
context:
space:
mode:
Diffstat (limited to 'src/cmd/vendor/rsc.io/markdown/list.go')
-rw-r--r--src/cmd/vendor/rsc.io/markdown/list.go364
1 files changed, 364 insertions, 0 deletions
diff --git a/src/cmd/vendor/rsc.io/markdown/list.go b/src/cmd/vendor/rsc.io/markdown/list.go
new file mode 100644
index 0000000000..8b9fcfe42e
--- /dev/null
+++ b/src/cmd/vendor/rsc.io/markdown/list.go
@@ -0,0 +1,364 @@
+// Copyright 2021 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package markdown
+
+import (
+ "bytes"
+ "fmt"
+ "strings"
+)
+
+type List struct {
+ Position
+ Bullet rune
+ Start int
+ Loose bool
+ Items []Block // always *Item
+}
+
+type Item struct {
+ Position
+ Blocks []Block
+ width int
+}
+
+func (b *List) PrintHTML(buf *bytes.Buffer) {
+ if b.Bullet == '.' || b.Bullet == ')' {
+ buf.WriteString("<ol")
+ if b.Start != 1 {
+ fmt.Fprintf(buf, " start=\"%d\"", b.Start)
+ }
+ buf.WriteString(">\n")
+ } else {
+ buf.WriteString("<ul>\n")
+ }
+ for _, c := range b.Items {
+ c.PrintHTML(buf)
+ }
+ if b.Bullet == '.' || b.Bullet == ')' {
+ buf.WriteString("</ol>\n")
+ } else {
+ buf.WriteString("</ul>\n")
+ }
+}
+
+func (b *List) printMarkdown(buf *bytes.Buffer, s mdState) {
+ if buf.Len() > 0 && buf.Bytes()[buf.Len()-1] != '\n' {
+ buf.WriteByte('\n')
+ }
+ s.bullet = b.Bullet
+ s.num = b.Start
+ for i, item := range b.Items {
+ if i > 0 && b.Loose {
+ buf.WriteByte('\n')
+ }
+ item.printMarkdown(buf, s)
+ s.num++
+ }
+}
+
+func (b *Item) printMarkdown(buf *bytes.Buffer, s mdState) {
+ var marker string
+ if s.bullet == '.' || s.bullet == ')' {
+ marker = fmt.Sprintf("%d%c ", s.num, s.bullet)
+ } else {
+ marker = fmt.Sprintf("%c ", s.bullet)
+ }
+ marker = strings.Repeat(" ", b.width-len(marker)) + marker
+ s.prefix1 = s.prefix + marker
+ s.prefix += strings.Repeat(" ", len(marker))
+ printMarkdownBlocks(b.Blocks, buf, s)
+}
+
+func (b *Item) PrintHTML(buf *bytes.Buffer) {
+ buf.WriteString("<li>")
+ if len(b.Blocks) > 0 {
+ if _, ok := b.Blocks[0].(*Text); !ok {
+ buf.WriteString("\n")
+ }
+ }
+ for i, c := range b.Blocks {
+ c.PrintHTML(buf)
+ if i+1 < len(b.Blocks) {
+ if _, ok := c.(*Text); ok {
+ buf.WriteString("\n")
+ }
+ }
+ }
+ buf.WriteString("</li>\n")
+}
+
+type listBuilder struct {
+ bullet rune
+ num int
+ loose bool
+ item *itemBuilder
+ todo func() line
+}
+
+func (b *listBuilder) build(p buildState) Block {
+ blocks := p.blocks()
+ pos := p.pos()
+
+ // list can have wrong pos b/c extend dance.
+ pos.EndLine = blocks[len(blocks)-1].Pos().EndLine
+Loose:
+ for i, c := range blocks {
+ c := c.(*Item)
+ if i+1 < len(blocks) {
+ if blocks[i+1].Pos().StartLine-c.EndLine > 1 {
+ b.loose = true
+ break Loose
+ }
+ }
+ for j, d := range c.Blocks {
+ endLine := d.Pos().EndLine
+ if j+1 < len(c.Blocks) {
+ if c.Blocks[j+1].Pos().StartLine-endLine > 1 {
+ b.loose = true
+ break Loose
+ }
+ }
+ }
+ }
+
+ if !b.loose {
+ for _, c := range blocks {
+ c := c.(*Item)
+ for i, d := range c.Blocks {
+ if p, ok := d.(*Paragraph); ok {
+ c.Blocks[i] = p.Text
+ }
+ }
+ }
+ }
+
+ return &List{
+ pos,
+ b.bullet,
+ b.num,
+ b.loose,
+ p.blocks(),
+ }
+}
+
+func (b *itemBuilder) build(p buildState) Block {
+ b.list.item = nil
+ return &Item{p.pos(), p.blocks(), b.width}
+}
+
+func (c *listBuilder) extend(p *parseState, s line) (line, bool) {
+ d := c.item
+ if d != nil && s.trimSpace(d.width, d.width, true) || d == nil && s.isBlank() {
+ return s, true
+ }
+ return s, false
+}
+
+func (c *itemBuilder) extend(p *parseState, s line) (line, bool) {
+ if s.isBlank() && !c.haveContent {
+ return s, false
+ }
+ if s.isBlank() {
+ // Goldmark does this and apparently commonmark.js too.
+ // Not sure why it is necessary.
+ return line{}, true
+ }
+ if !s.isBlank() {
+ c.haveContent = true
+ }
+ return s, true
+}
+
+func newListItem(p *parseState, s line) (line, bool) {
+ if list, ok := p.curB().(*listBuilder); ok && list.todo != nil {
+ s = list.todo()
+ list.todo = nil
+ return s, true
+ }
+ if p.startListItem(&s) {
+ return s, true
+ }
+ return s, false
+}
+
+func (p *parseState) startListItem(s *line) bool {
+ t := *s
+ n := 0
+ for i := 0; i < 3; i++ {
+ if !t.trimSpace(1, 1, false) {
+ break
+ }
+ n++
+ }
+ bullet := t.peek()
+ var num int
+Switch:
+ switch bullet {
+ default:
+ return false
+ case '-', '*', '+':
+ t.trim(bullet)
+ n++
+ case '0', '1', '2', '3', '4', '5', '6', '7', '8', '9':
+ for j := t.i; ; j++ {
+ if j >= len(t.text) {
+ return false
+ }
+ c := t.text[j]
+ if c == '.' || c == ')' {
+ // success
+ bullet = c
+ j++
+ n += j - t.i
+ t.i = j
+ break Switch
+ }
+ if c < '0' || '9' < c {
+ return false
+ }
+ if j-t.i >= 9 {
+ return false
+ }
+ num = num*10 + int(c) - '0'
+ }
+
+ }
+ if !t.trimSpace(1, 1, true) {
+ return false
+ }
+ n++
+ tt := t
+ m := 0
+ for i := 0; i < 3 && tt.trimSpace(1, 1, false); i++ {
+ m++
+ }
+ if !tt.trimSpace(1, 1, true) {
+ n += m
+ t = tt
+ }
+
+ // point of no return
+
+ var list *listBuilder
+ if c, ok := p.nextB().(*listBuilder); ok {
+ list = c
+ }
+ if list == nil || list.bullet != rune(bullet) {
+ // “When the first list item in a list interrupts a paragraph—that is,
+ // when it starts on a line that would otherwise count as
+ // paragraph continuation text—then (a) the lines Ls must
+ // not begin with a blank line,
+ // and (b) if the list item is ordered, the start number must be 1.”
+ if list == nil && p.para() != nil && (t.isBlank() || (bullet == '.' || bullet == ')') && num != 1) {
+ // Goldmark and Dingus both seem to get this wrong
+ // (or the words above don't mean what we think they do).
+ // when the paragraph that could be continued
+ // is inside a block quote.
+ // See testdata/extra.txt 117.md.
+ p.corner = true
+ return false
+ }
+ list = &listBuilder{bullet: rune(bullet), num: num}
+ p.addBlock(list)
+ }
+ b := &itemBuilder{list: list, width: n, haveContent: !t.isBlank()}
+ list.todo = func() line {
+ p.addBlock(b)
+ list.item = b
+ return t
+ }
+ return true
+}
+
+// GitHub task list extension
+
+func (p *parseState) taskList(list *List) {
+ for _, item := range list.Items {
+ item := item.(*Item)
+ if len(item.Blocks) == 0 {
+ continue
+ }
+ var text *Text
+ switch b := item.Blocks[0].(type) {
+ default:
+ continue
+ case *Paragraph:
+ text = b.Text
+ case *Text:
+ text = b
+ }
+ if len(text.Inline) < 1 {
+ continue
+ }
+ pl, ok := text.Inline[0].(*Plain)
+ if !ok {
+ continue
+ }
+ s := pl.Text
+ if len(s) < 4 || s[0] != '[' || s[2] != ']' || (s[1] != ' ' && s[1] != 'x' && s[1] != 'X') {
+ continue
+ }
+ if s[3] != ' ' && s[3] != '\t' {
+ p.corner = true // goldmark does not require the space
+ continue
+ }
+ text.Inline = append([]Inline{&Task{Checked: s[1] == 'x' || s[1] == 'X'},
+ &Plain{Text: s[len("[x]"):]}}, text.Inline[1:]...)
+ }
+}
+
+func ins(first Inline, x []Inline) []Inline {
+ x = append(x, nil)
+ copy(x[1:], x)
+ x[0] = first
+ return x
+}
+
+type Task struct {
+ Checked bool
+}
+
+func (x *Task) Inline() {
+}
+
+func (x *Task) PrintHTML(buf *bytes.Buffer) {
+ buf.WriteString("<input ")
+ if x.Checked {
+ buf.WriteString(`checked="" `)
+ }
+ buf.WriteString(`disabled="" type="checkbox">`)
+}
+
+func (x *Task) printMarkdown(buf *bytes.Buffer) {
+ x.PrintText(buf)
+}
+
+func (x *Task) PrintText(buf *bytes.Buffer) {
+ buf.WriteByte('[')
+ if x.Checked {
+ buf.WriteByte('x')
+ } else {
+ buf.WriteByte(' ')
+ }
+ buf.WriteByte(']')
+ buf.WriteByte(' ')
+}
+
+func listCorner(list *List) bool {
+ for _, item := range list.Items {
+ item := item.(*Item)
+ if len(item.Blocks) == 0 {
+ // Goldmark mishandles what follows; see testdata/extra.txt 111.md.
+ return true
+ }
+ switch item.Blocks[0].(type) {
+ case *List, *ThematicBreak, *CodeBlock:
+ // Goldmark mishandles a list with various block items inside it.
+ return true
+ }
+ }
+ return false
+}