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
|
|||
|
|
}
|
|||
|
|
}
|