diff options
Diffstat (limited to 'vendor/github.com/yuin/goldmark/parser/list.go')
| -rw-r--r-- | vendor/github.com/yuin/goldmark/parser/list.go | 287 |
1 files changed, 287 insertions, 0 deletions
diff --git a/vendor/github.com/yuin/goldmark/parser/list.go b/vendor/github.com/yuin/goldmark/parser/list.go new file mode 100644 index 0000000..e5cad11 --- /dev/null +++ b/vendor/github.com/yuin/goldmark/parser/list.go | |||
| @@ -0,0 +1,287 @@ | |||
| 1 | package parser | ||
| 2 | |||
| 3 | import ( | ||
| 4 | "strconv" | ||
| 5 | |||
| 6 | "github.com/yuin/goldmark/ast" | ||
| 7 | "github.com/yuin/goldmark/text" | ||
| 8 | "github.com/yuin/goldmark/util" | ||
| 9 | ) | ||
| 10 | |||
| 11 | type listItemType int | ||
| 12 | |||
| 13 | const ( | ||
| 14 | notList listItemType = iota | ||
| 15 | bulletList | ||
| 16 | orderedList | ||
| 17 | ) | ||
| 18 | |||
| 19 | var skipListParserKey = NewContextKey() | ||
| 20 | var emptyListItemWithBlankLines = NewContextKey() | ||
| 21 | var listItemFlagValue interface{} = true | ||
| 22 | |||
| 23 | // Same as | ||
| 24 | // `^(([ ]*)([\-\*\+]))(\s+.*)?\n?$`.FindSubmatchIndex or | ||
| 25 | // `^(([ ]*)(\d{1,9}[\.\)]))(\s+.*)?\n?$`.FindSubmatchIndex | ||
| 26 | func parseListItem(line []byte) ([6]int, listItemType) { | ||
| 27 | i := 0 | ||
| 28 | l := len(line) | ||
| 29 | ret := [6]int{} | ||
| 30 | for ; i < l && line[i] == ' '; i++ { | ||
| 31 | c := line[i] | ||
| 32 | if c == '\t' { | ||
| 33 | return ret, notList | ||
| 34 | } | ||
| 35 | } | ||
| 36 | if i > 3 { | ||
| 37 | return ret, notList | ||
| 38 | } | ||
| 39 | ret[0] = 0 | ||
| 40 | ret[1] = i | ||
| 41 | ret[2] = i | ||
| 42 | var typ listItemType | ||
| 43 | if i < l && (line[i] == '-' || line[i] == '*' || line[i] == '+') { | ||
| 44 | i++ | ||
| 45 | ret[3] = i | ||
| 46 | typ = bulletList | ||
| 47 | } else if i < l { | ||
| 48 | for ; i < l && util.IsNumeric(line[i]); i++ { | ||
| 49 | } | ||
| 50 | ret[3] = i | ||
| 51 | if ret[3] == ret[2] || ret[3]-ret[2] > 9 { | ||
| 52 | return ret, notList | ||
| 53 | } | ||
| 54 | if i < l && (line[i] == '.' || line[i] == ')') { | ||
| 55 | i++ | ||
| 56 | ret[3] = i | ||
| 57 | } else { | ||
| 58 | return ret, notList | ||
| 59 | } | ||
| 60 | typ = orderedList | ||
| 61 | } else { | ||
| 62 | return ret, notList | ||
| 63 | } | ||
| 64 | if i < l && line[i] != '\n' { | ||
| 65 | w, _ := util.IndentWidth(line[i:], 0) | ||
| 66 | if w == 0 { | ||
| 67 | return ret, notList | ||
| 68 | } | ||
| 69 | } | ||
| 70 | if i >= l { | ||
| 71 | ret[4] = -1 | ||
| 72 | ret[5] = -1 | ||
| 73 | return ret, typ | ||
| 74 | } | ||
| 75 | ret[4] = i | ||
| 76 | ret[5] = len(line) | ||
| 77 | if line[ret[5]-1] == '\n' && line[i] != '\n' { | ||
| 78 | ret[5]-- | ||
| 79 | } | ||
| 80 | return ret, typ | ||
| 81 | } | ||
| 82 | |||
| 83 | func matchesListItem(source []byte, strict bool) ([6]int, listItemType) { | ||
| 84 | m, typ := parseListItem(source) | ||
| 85 | if typ != notList && (!strict || strict && m[1] < 4) { | ||
| 86 | return m, typ | ||
| 87 | } | ||
| 88 | return m, notList | ||
| 89 | } | ||
| 90 | |||
| 91 | func calcListOffset(source []byte, match [6]int) int { | ||
| 92 | offset := 0 | ||
| 93 | if match[4] < 0 || util.IsBlank(source[match[4]:]) { // list item starts with a blank line | ||
| 94 | offset = 1 | ||
| 95 | } else { | ||
| 96 | offset, _ = util.IndentWidth(source[match[4]:], match[4]) | ||
| 97 | if offset > 4 { // offseted codeblock | ||
| 98 | offset = 1 | ||
| 99 | } | ||
| 100 | } | ||
| 101 | return offset | ||
| 102 | } | ||
| 103 | |||
| 104 | func lastOffset(node ast.Node) int { | ||
| 105 | lastChild := node.LastChild() | ||
| 106 | if lastChild != nil { | ||
| 107 | return lastChild.(*ast.ListItem).Offset | ||
| 108 | } | ||
| 109 | return 0 | ||
| 110 | } | ||
| 111 | |||
| 112 | type listParser struct { | ||
| 113 | } | ||
| 114 | |||
| 115 | var defaultListParser = &listParser{} | ||
| 116 | |||
| 117 | // NewListParser returns a new BlockParser that | ||
| 118 | // parses lists. | ||
| 119 | // This parser must take precedence over the ListItemParser. | ||
| 120 | func NewListParser() BlockParser { | ||
| 121 | return defaultListParser | ||
| 122 | } | ||
| 123 | |||
| 124 | func (b *listParser) Trigger() []byte { | ||
| 125 | return []byte{'-', '+', '*', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9'} | ||
| 126 | } | ||
| 127 | |||
| 128 | func (b *listParser) Open(parent ast.Node, reader text.Reader, pc Context) (ast.Node, State) { | ||
| 129 | last := pc.LastOpenedBlock().Node | ||
| 130 | if _, lok := last.(*ast.List); lok || pc.Get(skipListParserKey) != nil { | ||
| 131 | pc.Set(skipListParserKey, nil) | ||
| 132 | return nil, NoChildren | ||
| 133 | } | ||
| 134 | line, _ := reader.PeekLine() | ||
| 135 | match, typ := matchesListItem(line, true) | ||
| 136 | if typ == notList { | ||
| 137 | return nil, NoChildren | ||
| 138 | } | ||
| 139 | start := -1 | ||
| 140 | if typ == orderedList { | ||
| 141 | number := line[match[2] : match[3]-1] | ||
| 142 | start, _ = strconv.Atoi(string(number)) | ||
| 143 | } | ||
| 144 | |||
| 145 | if ast.IsParagraph(last) && last.Parent() == parent { | ||
| 146 | // we allow only lists starting with 1 to interrupt paragraphs. | ||
| 147 | if typ == orderedList && start != 1 { | ||
| 148 | return nil, NoChildren | ||
| 149 | } | ||
| 150 | //an empty list item cannot interrupt a paragraph: | ||
| 151 | if match[4] < 0 || util.IsBlank(line[match[4]:match[5]]) { | ||
| 152 | return nil, NoChildren | ||
| 153 | } | ||
| 154 | } | ||
| 155 | |||
| 156 | marker := line[match[3]-1] | ||
| 157 | node := ast.NewList(marker) | ||
| 158 | if start > -1 { | ||
| 159 | node.Start = start | ||
| 160 | } | ||
| 161 | pc.Set(emptyListItemWithBlankLines, nil) | ||
| 162 | return node, HasChildren | ||
| 163 | } | ||
| 164 | |||
| 165 | func (b *listParser) Continue(node ast.Node, reader text.Reader, pc Context) State { | ||
| 166 | list := node.(*ast.List) | ||
| 167 | line, _ := reader.PeekLine() | ||
| 168 | if util.IsBlank(line) { | ||
| 169 | if node.LastChild().ChildCount() == 0 { | ||
| 170 | pc.Set(emptyListItemWithBlankLines, listItemFlagValue) | ||
| 171 | } | ||
| 172 | return Continue | HasChildren | ||
| 173 | } | ||
| 174 | |||
| 175 | // "offset" means a width that bar indicates. | ||
| 176 | // - aaaaaaaa | ||
| 177 | // |----| | ||
| 178 | // | ||
| 179 | // If the indent is less than the last offset like | ||
| 180 | // - a | ||
| 181 | // - b <--- current line | ||
| 182 | // it maybe a new child of the list. | ||
| 183 | // | ||
| 184 | // Empty list items can have multiple blanklines | ||
| 185 | // | ||
| 186 | // - <--- 1st item is an empty thus "offset" is unknown | ||
| 187 | // | ||
| 188 | // | ||
| 189 | // - <--- current line | ||
| 190 | // | ||
| 191 | // -> 1 list with 2 blank items | ||
| 192 | // | ||
| 193 | // So if the last item is an empty, it maybe a new child of the list. | ||
| 194 | // | ||
| 195 | offset := lastOffset(node) | ||
| 196 | lastIsEmpty := node.LastChild().ChildCount() == 0 | ||
| 197 | indent, _ := util.IndentWidth(line, reader.LineOffset()) | ||
| 198 | |||
| 199 | if indent < offset || lastIsEmpty { | ||
| 200 | if indent < 4 { | ||
| 201 | match, typ := matchesListItem(line, false) // may have a leading spaces more than 3 | ||
| 202 | if typ != notList && match[1]-offset < 4 { | ||
| 203 | marker := line[match[3]-1] | ||
| 204 | if !list.CanContinue(marker, typ == orderedList) { | ||
| 205 | return Close | ||
| 206 | } | ||
| 207 | // Thematic Breaks take precedence over lists | ||
| 208 | if isThematicBreak(line[match[3]-1:], 0) { | ||
| 209 | isHeading := false | ||
| 210 | last := pc.LastOpenedBlock().Node | ||
| 211 | if ast.IsParagraph(last) { | ||
| 212 | c, ok := matchesSetextHeadingBar(line[match[3]-1:]) | ||
| 213 | if ok && c == '-' { | ||
| 214 | isHeading = true | ||
| 215 | } | ||
| 216 | } | ||
| 217 | if !isHeading { | ||
| 218 | return Close | ||
| 219 | } | ||
| 220 | } | ||
| 221 | return Continue | HasChildren | ||
| 222 | } | ||
| 223 | } | ||
| 224 | if !lastIsEmpty { | ||
| 225 | return Close | ||
| 226 | } | ||
| 227 | } | ||
| 228 | |||
| 229 | if lastIsEmpty && indent < offset { | ||
| 230 | return Close | ||
| 231 | } | ||
| 232 | |||
| 233 | // Non empty items can not exist next to an empty list item | ||
| 234 | // with blank lines. So we need to close the current list | ||
| 235 | // | ||
| 236 | // - | ||
| 237 | // | ||
| 238 | // foo | ||
| 239 | // | ||
| 240 | // -> 1 list with 1 blank items and 1 paragraph | ||
| 241 | if pc.Get(emptyListItemWithBlankLines) != nil { | ||
| 242 | return Close | ||
| 243 | } | ||
| 244 | return Continue | HasChildren | ||
| 245 | } | ||
| 246 | |||
| 247 | func (b *listParser) Close(node ast.Node, reader text.Reader, pc Context) { | ||
| 248 | list := node.(*ast.List) | ||
| 249 | |||
| 250 | for c := node.FirstChild(); c != nil && list.IsTight; c = c.NextSibling() { | ||
| 251 | if c.FirstChild() != nil && c.FirstChild() != c.LastChild() { | ||
| 252 | for c1 := c.FirstChild().NextSibling(); c1 != nil; c1 = c1.NextSibling() { | ||
| 253 | if bl, ok := c1.(ast.Node); ok && bl.HasBlankPreviousLines() { | ||
| 254 | list.IsTight = false | ||
| 255 | break | ||
| 256 | } | ||
| 257 | } | ||
| 258 | } | ||
| 259 | if c != node.FirstChild() { | ||
| 260 | if bl, ok := c.(ast.Node); ok && bl.HasBlankPreviousLines() { | ||
| 261 | list.IsTight = false | ||
| 262 | } | ||
| 263 | } | ||
| 264 | } | ||
| 265 | |||
| 266 | if list.IsTight { | ||
| 267 | for child := node.FirstChild(); child != nil; child = child.NextSibling() { | ||
| 268 | for gc := child.FirstChild(); gc != nil; { | ||
| 269 | paragraph, ok := gc.(*ast.Paragraph) | ||
| 270 | gc = gc.NextSibling() | ||
| 271 | if ok { | ||
| 272 | textBlock := ast.NewTextBlock() | ||
| 273 | textBlock.SetLines(paragraph.Lines()) | ||
| 274 | child.ReplaceChild(child, paragraph, textBlock) | ||
| 275 | } | ||
| 276 | } | ||
| 277 | } | ||
| 278 | } | ||
| 279 | } | ||
| 280 | |||
| 281 | func (b *listParser) CanInterruptParagraph() bool { | ||
| 282 | return true | ||
| 283 | } | ||
| 284 | |||
| 285 | func (b *listParser) CanAcceptIndentedLine() bool { | ||
| 286 | return false | ||
| 287 | } | ||
