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
24
25
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]:]) {
94 offset = 1
95 } else {
96 offset, _ = util.IndentWidth(source[match[4]:], match[4])
97 if offset > 4 {
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
118
119
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
147 if typ == orderedList && start != 1 {
148 return nil, NoChildren
149 }
150
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
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
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)
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
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
234
235
236
237
238
239
240
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 }
288
View as plain text