| 'use strict' |
| |
| /* eslint-disable max-params */ |
| |
| 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 = /^\[([ \t]|x|X)][ \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 prevEmpty |
| 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 |
| } |
| } |
| |
| prevEmpty = 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 (prevEmpty && !commonmark) { |
| break |
| } |
| |
| emptyLines.push(line) |
| } else { |
| if (prevEmpty) { |
| 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) |
| |
| 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 |
| } |
| } |