'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 = 0 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) { size += tabSize - (size % tabSize) } else if (character === space) { size++ } else { break } index++ } if (size >= tabSize) { return } 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 } 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 } end = index + tabSize 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 (size >= tabSize) { indented = true } 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 } }