list.go 6.5 KB

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