list.js 9.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446
  1. 'use strict'
  2. var trim = require('trim')
  3. var repeat = require('repeat-string')
  4. var decimal = require('is-decimal')
  5. var getIndent = require('../util/get-indentation')
  6. var removeIndent = require('../util/remove-indentation')
  7. var interrupt = require('../util/interrupt')
  8. module.exports = list
  9. var asterisk = '*'
  10. var underscore = '_'
  11. var plusSign = '+'
  12. var dash = '-'
  13. var dot = '.'
  14. var space = ' '
  15. var lineFeed = '\n'
  16. var tab = '\t'
  17. var rightParenthesis = ')'
  18. var lowercaseX = 'x'
  19. var tabSize = 4
  20. var looseListItemExpression = /\n\n(?!\s*$)/
  21. var taskItemExpression = /^\[([ X\tx])][ \t]/
  22. var bulletExpression = /^([ \t]*)([*+-]|\d+[.)])( {1,4}(?! )| |\t|$|(?=\n))([^\n]*)/
  23. var pedanticBulletExpression = /^([ \t]*)([*+-]|\d+[.)])([ \t]+)/
  24. var initialIndentExpression = /^( {1,4}|\t)?/gm
  25. function list(eat, value, silent) {
  26. var self = this
  27. var commonmark = self.options.commonmark
  28. var pedantic = self.options.pedantic
  29. var tokenizers = self.blockTokenizers
  30. var interuptors = self.interruptList
  31. var index = 0
  32. var length = value.length
  33. var start = null
  34. var size
  35. var queue
  36. var ordered
  37. var character
  38. var marker
  39. var nextIndex
  40. var startIndex
  41. var prefixed
  42. var currentMarker
  43. var content
  44. var line
  45. var previousEmpty
  46. var empty
  47. var items
  48. var allLines
  49. var emptyLines
  50. var item
  51. var enterTop
  52. var exitBlockquote
  53. var spread = false
  54. var node
  55. var now
  56. var end
  57. var indented
  58. while (index < length) {
  59. character = value.charAt(index)
  60. if (character !== tab && character !== space) {
  61. break
  62. }
  63. index++
  64. }
  65. character = value.charAt(index)
  66. if (character === asterisk || character === plusSign || character === dash) {
  67. marker = character
  68. ordered = false
  69. } else {
  70. ordered = true
  71. queue = ''
  72. while (index < length) {
  73. character = value.charAt(index)
  74. if (!decimal(character)) {
  75. break
  76. }
  77. queue += character
  78. index++
  79. }
  80. character = value.charAt(index)
  81. if (
  82. !queue ||
  83. !(character === dot || (commonmark && character === rightParenthesis))
  84. ) {
  85. return
  86. }
  87. /* Slightly abusing `silent` mode, whose goal is to make interrupting
  88. * paragraphs work.
  89. * Well, that’s exactly what we want to do here: don’t interrupt:
  90. * 2. here, because the “list” doesn’t start with `1`. */
  91. if (silent && queue !== '1') {
  92. return
  93. }
  94. start = parseInt(queue, 10)
  95. marker = character
  96. }
  97. character = value.charAt(++index)
  98. if (
  99. character !== space &&
  100. character !== tab &&
  101. (pedantic || (character !== lineFeed && character !== ''))
  102. ) {
  103. return
  104. }
  105. if (silent) {
  106. return true
  107. }
  108. index = 0
  109. items = []
  110. allLines = []
  111. emptyLines = []
  112. while (index < length) {
  113. nextIndex = value.indexOf(lineFeed, index)
  114. startIndex = index
  115. prefixed = false
  116. indented = false
  117. if (nextIndex === -1) {
  118. nextIndex = length
  119. }
  120. size = 0
  121. while (index < length) {
  122. character = value.charAt(index)
  123. if (character === tab) {
  124. size += tabSize - (size % tabSize)
  125. } else if (character === space) {
  126. size++
  127. } else {
  128. break
  129. }
  130. index++
  131. }
  132. if (item && size >= item.indent) {
  133. indented = true
  134. }
  135. character = value.charAt(index)
  136. currentMarker = null
  137. if (!indented) {
  138. if (
  139. character === asterisk ||
  140. character === plusSign ||
  141. character === dash
  142. ) {
  143. currentMarker = character
  144. index++
  145. size++
  146. } else {
  147. queue = ''
  148. while (index < length) {
  149. character = value.charAt(index)
  150. if (!decimal(character)) {
  151. break
  152. }
  153. queue += character
  154. index++
  155. }
  156. character = value.charAt(index)
  157. index++
  158. if (
  159. queue &&
  160. (character === dot || (commonmark && character === rightParenthesis))
  161. ) {
  162. currentMarker = character
  163. size += queue.length + 1
  164. }
  165. }
  166. if (currentMarker) {
  167. character = value.charAt(index)
  168. if (character === tab) {
  169. size += tabSize - (size % tabSize)
  170. index++
  171. } else if (character === space) {
  172. end = index + tabSize
  173. while (index < end) {
  174. if (value.charAt(index) !== space) {
  175. break
  176. }
  177. index++
  178. size++
  179. }
  180. if (index === end && value.charAt(index) === space) {
  181. index -= tabSize - 1
  182. size -= tabSize - 1
  183. }
  184. } else if (character !== lineFeed && character !== '') {
  185. currentMarker = null
  186. }
  187. }
  188. }
  189. if (currentMarker) {
  190. if (!pedantic && marker !== currentMarker) {
  191. break
  192. }
  193. prefixed = true
  194. } else {
  195. if (!commonmark && !indented && value.charAt(startIndex) === space) {
  196. indented = true
  197. } else if (commonmark && item) {
  198. indented = size >= item.indent || size > tabSize
  199. }
  200. prefixed = false
  201. index = startIndex
  202. }
  203. line = value.slice(startIndex, nextIndex)
  204. content = startIndex === index ? line : value.slice(index, nextIndex)
  205. if (
  206. currentMarker === asterisk ||
  207. currentMarker === underscore ||
  208. currentMarker === dash
  209. ) {
  210. if (tokenizers.thematicBreak.call(self, eat, line, true)) {
  211. break
  212. }
  213. }
  214. previousEmpty = empty
  215. empty = !prefixed && !trim(content).length
  216. if (indented && item) {
  217. item.value = item.value.concat(emptyLines, line)
  218. allLines = allLines.concat(emptyLines, line)
  219. emptyLines = []
  220. } else if (prefixed) {
  221. if (emptyLines.length !== 0) {
  222. spread = true
  223. item.value.push('')
  224. item.trail = emptyLines.concat()
  225. }
  226. item = {
  227. value: [line],
  228. indent: size,
  229. trail: []
  230. }
  231. items.push(item)
  232. allLines = allLines.concat(emptyLines, line)
  233. emptyLines = []
  234. } else if (empty) {
  235. if (previousEmpty && !commonmark) {
  236. break
  237. }
  238. emptyLines.push(line)
  239. } else {
  240. if (previousEmpty) {
  241. break
  242. }
  243. if (interrupt(interuptors, tokenizers, self, [eat, line, true])) {
  244. break
  245. }
  246. item.value = item.value.concat(emptyLines, line)
  247. allLines = allLines.concat(emptyLines, line)
  248. emptyLines = []
  249. }
  250. index = nextIndex + 1
  251. }
  252. node = eat(allLines.join(lineFeed)).reset({
  253. type: 'list',
  254. ordered: ordered,
  255. start: start,
  256. spread: spread,
  257. children: []
  258. })
  259. enterTop = self.enterList()
  260. exitBlockquote = self.enterBlock()
  261. index = -1
  262. length = items.length
  263. while (++index < length) {
  264. item = items[index].value.join(lineFeed)
  265. now = eat.now()
  266. eat(item)(listItem(self, item, now), node)
  267. item = items[index].trail.join(lineFeed)
  268. if (index !== length - 1) {
  269. item += lineFeed
  270. }
  271. eat(item)
  272. }
  273. enterTop()
  274. exitBlockquote()
  275. return node
  276. }
  277. function listItem(ctx, value, position) {
  278. var offsets = ctx.offset
  279. var fn = ctx.options.pedantic ? pedanticListItem : normalListItem
  280. var checked = null
  281. var task
  282. var indent
  283. value = fn.apply(null, arguments)
  284. if (ctx.options.gfm) {
  285. task = value.match(taskItemExpression)
  286. if (task) {
  287. indent = task[0].length
  288. checked = task[1].toLowerCase() === lowercaseX
  289. offsets[position.line] += indent
  290. value = value.slice(indent)
  291. }
  292. }
  293. return {
  294. type: 'listItem',
  295. spread: looseListItemExpression.test(value),
  296. checked: checked,
  297. children: ctx.tokenizeBlock(value, position)
  298. }
  299. }
  300. // Create a list-item using overly simple mechanics.
  301. function pedanticListItem(ctx, value, position) {
  302. var offsets = ctx.offset
  303. var line = position.line
  304. // Remove the list-item’s bullet.
  305. value = value.replace(pedanticBulletExpression, replacer)
  306. // The initial line was also matched by the below, so we reset the `line`.
  307. line = position.line
  308. return value.replace(initialIndentExpression, replacer)
  309. // A simple replacer which removed all matches, and adds their length to
  310. // `offset`.
  311. function replacer($0) {
  312. offsets[line] = (offsets[line] || 0) + $0.length
  313. line++
  314. return ''
  315. }
  316. }
  317. // Create a list-item using sane mechanics.
  318. function normalListItem(ctx, value, position) {
  319. var offsets = ctx.offset
  320. var line = position.line
  321. var max
  322. var bullet
  323. var rest
  324. var lines
  325. var trimmedLines
  326. var index
  327. var length
  328. // Remove the list-item’s bullet.
  329. value = value.replace(bulletExpression, replacer)
  330. lines = value.split(lineFeed)
  331. trimmedLines = removeIndent(value, getIndent(max).indent).split(lineFeed)
  332. // We replaced the initial bullet with something else above, which was used
  333. // to trick `removeIndentation` into removing some more characters when
  334. // possible. However, that could result in the initial line to be stripped
  335. // more than it should be.
  336. trimmedLines[0] = rest
  337. offsets[line] = (offsets[line] || 0) + bullet.length
  338. line++
  339. index = 0
  340. length = lines.length
  341. while (++index < length) {
  342. offsets[line] =
  343. (offsets[line] || 0) + lines[index].length - trimmedLines[index].length
  344. line++
  345. }
  346. return trimmedLines.join(lineFeed)
  347. /* eslint-disable-next-line max-params */
  348. function replacer($0, $1, $2, $3, $4) {
  349. bullet = $1 + $2 + $3
  350. rest = $4
  351. // Make sure that the first nine numbered list items can indent with an
  352. // extra space. That is, when the bullet did not receive an extra final
  353. // space.
  354. if (Number($2) < 10 && bullet.length % 2 === 1) {
  355. $2 = space + $2
  356. }
  357. max = $1 + repeat(space, $2.length) + $3
  358. return max + rest
  359. }
  360. }