list.js 6.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268
  1. /**
  2. * @typedef {import('micromark-util-types').Code} Code
  3. * @typedef {import('micromark-util-types').Construct} Construct
  4. * @typedef {import('micromark-util-types').ContainerState} ContainerState
  5. * @typedef {import('micromark-util-types').Exiter} Exiter
  6. * @typedef {import('micromark-util-types').State} State
  7. * @typedef {import('micromark-util-types').TokenizeContext} TokenizeContext
  8. * @typedef {import('micromark-util-types').Tokenizer} Tokenizer
  9. */
  10. import {factorySpace} from 'micromark-factory-space'
  11. import {asciiDigit, markdownSpace} from 'micromark-util-character'
  12. import {blankLine} from './blank-line.js'
  13. import {thematicBreak} from './thematic-break.js'
  14. /** @type {Construct} */
  15. export const list = {
  16. name: 'list',
  17. tokenize: tokenizeListStart,
  18. continuation: {
  19. tokenize: tokenizeListContinuation
  20. },
  21. exit: tokenizeListEnd
  22. }
  23. /** @type {Construct} */
  24. const listItemPrefixWhitespaceConstruct = {
  25. tokenize: tokenizeListItemPrefixWhitespace,
  26. partial: true
  27. }
  28. /** @type {Construct} */
  29. const indentConstruct = {
  30. tokenize: tokenizeIndent,
  31. partial: true
  32. }
  33. // To do: `markdown-rs` parses list items on their own and later stitches them
  34. // together.
  35. /**
  36. * @type {Tokenizer}
  37. * @this {TokenizeContext}
  38. */
  39. function tokenizeListStart(effects, ok, nok) {
  40. const self = this
  41. const tail = self.events[self.events.length - 1]
  42. let initialSize =
  43. tail && tail[1].type === 'linePrefix'
  44. ? tail[2].sliceSerialize(tail[1], true).length
  45. : 0
  46. let size = 0
  47. return start
  48. /** @type {State} */
  49. function start(code) {
  50. const kind =
  51. self.containerState.type ||
  52. (code === 42 || code === 43 || code === 45
  53. ? 'listUnordered'
  54. : 'listOrdered')
  55. if (
  56. kind === 'listUnordered'
  57. ? !self.containerState.marker || code === self.containerState.marker
  58. : asciiDigit(code)
  59. ) {
  60. if (!self.containerState.type) {
  61. self.containerState.type = kind
  62. effects.enter(kind, {
  63. _container: true
  64. })
  65. }
  66. if (kind === 'listUnordered') {
  67. effects.enter('listItemPrefix')
  68. return code === 42 || code === 45
  69. ? effects.check(thematicBreak, nok, atMarker)(code)
  70. : atMarker(code)
  71. }
  72. if (!self.interrupt || code === 49) {
  73. effects.enter('listItemPrefix')
  74. effects.enter('listItemValue')
  75. return inside(code)
  76. }
  77. }
  78. return nok(code)
  79. }
  80. /** @type {State} */
  81. function inside(code) {
  82. if (asciiDigit(code) && ++size < 10) {
  83. effects.consume(code)
  84. return inside
  85. }
  86. if (
  87. (!self.interrupt || size < 2) &&
  88. (self.containerState.marker
  89. ? code === self.containerState.marker
  90. : code === 41 || code === 46)
  91. ) {
  92. effects.exit('listItemValue')
  93. return atMarker(code)
  94. }
  95. return nok(code)
  96. }
  97. /**
  98. * @type {State}
  99. **/
  100. function atMarker(code) {
  101. effects.enter('listItemMarker')
  102. effects.consume(code)
  103. effects.exit('listItemMarker')
  104. self.containerState.marker = self.containerState.marker || code
  105. return effects.check(
  106. blankLine,
  107. // Can’t be empty when interrupting.
  108. self.interrupt ? nok : onBlank,
  109. effects.attempt(
  110. listItemPrefixWhitespaceConstruct,
  111. endOfPrefix,
  112. otherPrefix
  113. )
  114. )
  115. }
  116. /** @type {State} */
  117. function onBlank(code) {
  118. self.containerState.initialBlankLine = true
  119. initialSize++
  120. return endOfPrefix(code)
  121. }
  122. /** @type {State} */
  123. function otherPrefix(code) {
  124. if (markdownSpace(code)) {
  125. effects.enter('listItemPrefixWhitespace')
  126. effects.consume(code)
  127. effects.exit('listItemPrefixWhitespace')
  128. return endOfPrefix
  129. }
  130. return nok(code)
  131. }
  132. /** @type {State} */
  133. function endOfPrefix(code) {
  134. self.containerState.size =
  135. initialSize +
  136. self.sliceSerialize(effects.exit('listItemPrefix'), true).length
  137. return ok(code)
  138. }
  139. }
  140. /**
  141. * @type {Tokenizer}
  142. * @this {TokenizeContext}
  143. */
  144. function tokenizeListContinuation(effects, ok, nok) {
  145. const self = this
  146. self.containerState._closeFlow = undefined
  147. return effects.check(blankLine, onBlank, notBlank)
  148. /** @type {State} */
  149. function onBlank(code) {
  150. self.containerState.furtherBlankLines =
  151. self.containerState.furtherBlankLines ||
  152. self.containerState.initialBlankLine
  153. // We have a blank line.
  154. // Still, try to consume at most the items size.
  155. return factorySpace(
  156. effects,
  157. ok,
  158. 'listItemIndent',
  159. self.containerState.size + 1
  160. )(code)
  161. }
  162. /** @type {State} */
  163. function notBlank(code) {
  164. if (self.containerState.furtherBlankLines || !markdownSpace(code)) {
  165. self.containerState.furtherBlankLines = undefined
  166. self.containerState.initialBlankLine = undefined
  167. return notInCurrentItem(code)
  168. }
  169. self.containerState.furtherBlankLines = undefined
  170. self.containerState.initialBlankLine = undefined
  171. return effects.attempt(indentConstruct, ok, notInCurrentItem)(code)
  172. }
  173. /** @type {State} */
  174. function notInCurrentItem(code) {
  175. // While we do continue, we signal that the flow should be closed.
  176. self.containerState._closeFlow = true
  177. // As we’re closing flow, we’re no longer interrupting.
  178. self.interrupt = undefined
  179. // Always populated by defaults.
  180. return factorySpace(
  181. effects,
  182. effects.attempt(list, ok, nok),
  183. 'linePrefix',
  184. self.parser.constructs.disable.null.includes('codeIndented')
  185. ? undefined
  186. : 4
  187. )(code)
  188. }
  189. }
  190. /**
  191. * @type {Tokenizer}
  192. * @this {TokenizeContext}
  193. */
  194. function tokenizeIndent(effects, ok, nok) {
  195. const self = this
  196. return factorySpace(
  197. effects,
  198. afterPrefix,
  199. 'listItemIndent',
  200. self.containerState.size + 1
  201. )
  202. /** @type {State} */
  203. function afterPrefix(code) {
  204. const tail = self.events[self.events.length - 1]
  205. return tail &&
  206. tail[1].type === 'listItemIndent' &&
  207. tail[2].sliceSerialize(tail[1], true).length === self.containerState.size
  208. ? ok(code)
  209. : nok(code)
  210. }
  211. }
  212. /**
  213. * @type {Exiter}
  214. * @this {TokenizeContext}
  215. */
  216. function tokenizeListEnd(effects) {
  217. effects.exit(this.containerState.type)
  218. }
  219. /**
  220. * @type {Tokenizer}
  221. * @this {TokenizeContext}
  222. */
  223. function tokenizeListItemPrefixWhitespace(effects, ok, nok) {
  224. const self = this
  225. // Always populated by defaults.
  226. return factorySpace(
  227. effects,
  228. afterPrefix,
  229. 'listItemPrefixWhitespace',
  230. self.parser.constructs.disable.null.includes('codeIndented')
  231. ? undefined
  232. : 4 + 1
  233. )
  234. /** @type {State} */
  235. function afterPrefix(code) {
  236. const tail = self.events[self.events.length - 1]
  237. return !markdownSpace(code) &&
  238. tail &&
  239. tail[1].type === 'listItemPrefixWhitespace'
  240. ? ok(code)
  241. : nok(code)
  242. }
  243. }