123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446 |
- 'use strict'
- var trim = require('trim')
- var repeat = require('repeat-string')
- var decimal = require('is-decimal')
- var getIndent = require('../util/get-indentation')
- var removeIndent = require('../util/remove-indentation')
- var interrupt = require('../util/interrupt')
- module.exports = list
- var asterisk = '*'
- var underscore = '_'
- var plusSign = '+'
- var dash = '-'
- var dot = '.'
- var space = ' '
- var lineFeed = '\n'
- var tab = '\t'
- var rightParenthesis = ')'
- var lowercaseX = 'x'
- var tabSize = 4
- var looseListItemExpression = /\n\n(?!\s*$)/
- var taskItemExpression = /^\[([ X\tx])][ \t]/
- var bulletExpression = /^([ \t]*)([*+-]|\d+[.)])( {1,4}(?! )| |\t|$|(?=\n))([^\n]*)/
- var pedanticBulletExpression = /^([ \t]*)([*+-]|\d+[.)])([ \t]+)/
- var initialIndentExpression = /^( {1,4}|\t)?/gm
- function list(eat, value, silent) {
- var self = this
- var commonmark = self.options.commonmark
- var pedantic = self.options.pedantic
- var tokenizers = self.blockTokenizers
- var interuptors = self.interruptList
- var index = 0
- var length = value.length
- var start = null
- var size
- var queue
- var ordered
- var character
- var marker
- var nextIndex
- var startIndex
- var prefixed
- var currentMarker
- var content
- var line
- var previousEmpty
- var empty
- var items
- var allLines
- var emptyLines
- var item
- var enterTop
- var exitBlockquote
- var spread = false
- var node
- var now
- var end
- var indented
- while (index < length) {
- character = value.charAt(index)
- if (character !== tab && character !== space) {
- break
- }
- index++
- }
- character = value.charAt(index)
- if (character === asterisk || character === plusSign || character === dash) {
- marker = character
- ordered = false
- } else {
- ordered = true
- queue = ''
- while (index < length) {
- character = value.charAt(index)
- if (!decimal(character)) {
- break
- }
- queue += character
- index++
- }
- character = value.charAt(index)
- if (
- !queue ||
- !(character === dot || (commonmark && character === rightParenthesis))
- ) {
- return
- }
- /* Slightly abusing `silent` mode, whose goal is to make interrupting
- * paragraphs work.
- * Well, that’s exactly what we want to do here: don’t interrupt:
- * 2. here, because the “list” doesn’t start with `1`. */
- if (silent && queue !== '1') {
- return
- }
- start = parseInt(queue, 10)
- marker = character
- }
- character = value.charAt(++index)
- if (
- character !== space &&
- character !== tab &&
- (pedantic || (character !== lineFeed && character !== ''))
- ) {
- return
- }
- if (silent) {
- return true
- }
- index = 0
- items = []
- allLines = []
- emptyLines = []
- while (index < length) {
- nextIndex = value.indexOf(lineFeed, index)
- startIndex = index
- prefixed = false
- indented = false
- if (nextIndex === -1) {
- nextIndex = length
- }
- size = 0
- while (index < length) {
- character = value.charAt(index)
- if (character === tab) {
- size += tabSize - (size % tabSize)
- } else if (character === space) {
- size++
- } else {
- break
- }
- index++
- }
- if (item && size >= item.indent) {
- indented = true
- }
- character = value.charAt(index)
- currentMarker = null
- if (!indented) {
- if (
- character === asterisk ||
- character === plusSign ||
- character === dash
- ) {
- currentMarker = character
- index++
- size++
- } else {
- queue = ''
- while (index < length) {
- character = value.charAt(index)
- if (!decimal(character)) {
- break
- }
- queue += character
- index++
- }
- character = value.charAt(index)
- index++
- if (
- queue &&
- (character === dot || (commonmark && character === rightParenthesis))
- ) {
- currentMarker = character
- size += queue.length + 1
- }
- }
- if (currentMarker) {
- character = value.charAt(index)
- if (character === tab) {
- size += tabSize - (size % tabSize)
- index++
- } else if (character === space) {
- end = index + tabSize
- while (index < end) {
- if (value.charAt(index) !== space) {
- break
- }
- index++
- size++
- }
- if (index === end && value.charAt(index) === space) {
- index -= tabSize - 1
- size -= tabSize - 1
- }
- } else if (character !== lineFeed && character !== '') {
- currentMarker = null
- }
- }
- }
- if (currentMarker) {
- if (!pedantic && marker !== currentMarker) {
- break
- }
- prefixed = true
- } else {
- if (!commonmark && !indented && value.charAt(startIndex) === space) {
- indented = true
- } else if (commonmark && item) {
- indented = size >= item.indent || size > tabSize
- }
- prefixed = false
- index = startIndex
- }
- line = value.slice(startIndex, nextIndex)
- content = startIndex === index ? line : value.slice(index, nextIndex)
- if (
- currentMarker === asterisk ||
- currentMarker === underscore ||
- currentMarker === dash
- ) {
- if (tokenizers.thematicBreak.call(self, eat, line, true)) {
- break
- }
- }
- previousEmpty = empty
- empty = !prefixed && !trim(content).length
- if (indented && item) {
- item.value = item.value.concat(emptyLines, line)
- allLines = allLines.concat(emptyLines, line)
- emptyLines = []
- } else if (prefixed) {
- if (emptyLines.length !== 0) {
- spread = true
- item.value.push('')
- item.trail = emptyLines.concat()
- }
- item = {
- value: [line],
- indent: size,
- trail: []
- }
- items.push(item)
- allLines = allLines.concat(emptyLines, line)
- emptyLines = []
- } else if (empty) {
- if (previousEmpty && !commonmark) {
- break
- }
- emptyLines.push(line)
- } else {
- if (previousEmpty) {
- break
- }
- if (interrupt(interuptors, tokenizers, self, [eat, line, true])) {
- break
- }
- item.value = item.value.concat(emptyLines, line)
- allLines = allLines.concat(emptyLines, line)
- emptyLines = []
- }
- index = nextIndex + 1
- }
- node = eat(allLines.join(lineFeed)).reset({
- type: 'list',
- ordered: ordered,
- start: start,
- spread: spread,
- children: []
- })
- enterTop = self.enterList()
- exitBlockquote = self.enterBlock()
- index = -1
- length = items.length
- while (++index < length) {
- item = items[index].value.join(lineFeed)
- now = eat.now()
- eat(item)(listItem(self, item, now), node)
- item = items[index].trail.join(lineFeed)
- if (index !== length - 1) {
- item += lineFeed
- }
- eat(item)
- }
- enterTop()
- exitBlockquote()
- return node
- }
- function listItem(ctx, value, position) {
- var offsets = ctx.offset
- var fn = ctx.options.pedantic ? pedanticListItem : normalListItem
- var checked = null
- var task
- var indent
- value = fn.apply(null, arguments)
- if (ctx.options.gfm) {
- task = value.match(taskItemExpression)
- if (task) {
- indent = task[0].length
- checked = task[1].toLowerCase() === lowercaseX
- offsets[position.line] += indent
- value = value.slice(indent)
- }
- }
- return {
- type: 'listItem',
- spread: looseListItemExpression.test(value),
- checked: checked,
- children: ctx.tokenizeBlock(value, position)
- }
- }
- // Create a list-item using overly simple mechanics.
- function pedanticListItem(ctx, value, position) {
- var offsets = ctx.offset
- var line = position.line
- // Remove the list-item’s bullet.
- value = value.replace(pedanticBulletExpression, replacer)
- // The initial line was also matched by the below, so we reset the `line`.
- line = position.line
- return value.replace(initialIndentExpression, replacer)
- // A simple replacer which removed all matches, and adds their length to
- // `offset`.
- function replacer($0) {
- offsets[line] = (offsets[line] || 0) + $0.length
- line++
- return ''
- }
- }
- // Create a list-item using sane mechanics.
- function normalListItem(ctx, value, position) {
- var offsets = ctx.offset
- var line = position.line
- var max
- var bullet
- var rest
- var lines
- var trimmedLines
- var index
- var length
- // Remove the list-item’s bullet.
- value = value.replace(bulletExpression, replacer)
- lines = value.split(lineFeed)
- trimmedLines = removeIndent(value, getIndent(max).indent).split(lineFeed)
- // We replaced the initial bullet with something else above, which was used
- // to trick `removeIndentation` into removing some more characters when
- // possible. However, that could result in the initial line to be stripped
- // more than it should be.
- trimmedLines[0] = rest
- offsets[line] = (offsets[line] || 0) + bullet.length
- line++
- index = 0
- length = lines.length
- while (++index < length) {
- offsets[line] =
- (offsets[line] || 0) + lines[index].length - trimmedLines[index].length
- line++
- }
- return trimmedLines.join(lineFeed)
- /* eslint-disable-next-line max-params */
- function replacer($0, $1, $2, $3, $4) {
- bullet = $1 + $2 + $3
- rest = $4
- // Make sure that the first nine numbered list items can indent with an
- // extra space. That is, when the bullet did not receive an extra final
- // space.
- if (Number($2) < 10 && bullet.length % 2 === 1) {
- $2 = space + $2
- }
- max = $1 + repeat(space, $2.length) + $3
- return max + rest
- }
- }
|