453 lines
9.4 KiB
JavaScript
453 lines
9.4 KiB
JavaScript
'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
|
||
}
|
||
}
|