|
|
@ -4,57 +4,22 @@ |
|
|
|
|
|
|
|
|
|
|
|
var isEmpty = require('../helpers').isEmpty; |
|
|
|
var getLines = require('../helpers').getLines; |
|
|
|
var skipSpaces = require('../helpers').skipSpaces; |
|
|
|
var skipEmptyLines = require('../helpers').skipEmptyLines; |
|
|
|
|
|
|
|
|
|
|
|
function findEndOfItem(state, startLine, endLine, indent) { |
|
|
|
var lastNonEmptyLine = startLine, |
|
|
|
rules_named = state.lexerBlock.rules_named, |
|
|
|
nextLine = startLine + 1; |
|
|
|
// Search `[-+*][\n ]`, returns next pos arter marker on success
|
|
|
|
// or -1 on fail.
|
|
|
|
function skipBulletListMarker(state, startLine) { |
|
|
|
var marker, pos, max; |
|
|
|
|
|
|
|
// jump line-by-line until empty one or EOF
|
|
|
|
for (; nextLine < endLine; nextLine++) { |
|
|
|
if (isEmpty(state, nextLine)) { |
|
|
|
// two successive newlines end the list
|
|
|
|
if (lastNonEmptyLine < nextLine - 1) { break; } |
|
|
|
continue; |
|
|
|
} |
|
|
|
|
|
|
|
// if this line is indented more than with N spaces,
|
|
|
|
// it's the new paragraph of the same list item
|
|
|
|
if (state.tShift[nextLine] >= indent) { |
|
|
|
lastNonEmptyLine = nextLine; |
|
|
|
continue; |
|
|
|
} |
|
|
|
|
|
|
|
// paragraph after linebreak - not a continuation
|
|
|
|
if (lastNonEmptyLine < nextLine - 1) { break; } |
|
|
|
|
|
|
|
// Otherwise it's a possible continuation, so we need to check
|
|
|
|
// other tags, same as with blockquote and paragraph.
|
|
|
|
|
|
|
|
if (rules_named.fences(state, nextLine, endLine, true)) { break; } |
|
|
|
if (rules_named.hr(state, nextLine, endLine, true)) { break; } |
|
|
|
if (rules_named.heading(state, nextLine, endLine, true)) { break; } |
|
|
|
if (rules_named.lheading(state, nextLine, endLine, true)) { break; } |
|
|
|
if (rules_named.blockquote(state, nextLine, endLine, true)) { break; } |
|
|
|
if (rules_named.list(state, nextLine, endLine, true)) { break; } |
|
|
|
//if (rules_named.tag(state, nextLine, endLine, true)) { break; }
|
|
|
|
//if (rules_named.def(state, nextLine, endLine, true)) { break; }
|
|
|
|
lastNonEmptyLine = nextLine; |
|
|
|
} |
|
|
|
return lastNonEmptyLine; |
|
|
|
} |
|
|
|
if (state.tShift[startLine] > 3) { return -1; } |
|
|
|
|
|
|
|
// skips `[-+*][\n ]`, returns -1 if not found
|
|
|
|
function skipBulletListMarker(state, startLine/*, endLine*/) { |
|
|
|
var marker, |
|
|
|
pos = state.bMarks[startLine] + state.tShift[startLine], |
|
|
|
max = state.eMarks[startLine]; |
|
|
|
pos = state.bMarks[startLine] + state.tShift[startLine]; |
|
|
|
max = state.eMarks[startLine]; |
|
|
|
|
|
|
|
if (pos > max) { return -1; } |
|
|
|
if (pos >= max) { return -1; } |
|
|
|
|
|
|
|
marker = state.src.charCodeAt(pos++); |
|
|
|
// Check bullet
|
|
|
@ -68,138 +33,216 @@ function skipBulletListMarker(state, startLine/*, endLine*/) { |
|
|
|
// " 1.test " - is not a list item
|
|
|
|
return -1; |
|
|
|
} |
|
|
|
|
|
|
|
return pos; |
|
|
|
} |
|
|
|
|
|
|
|
// skips `\d+\.[\n ]`, returns -1 if not found
|
|
|
|
function skipOrderedListMarker(state, startLine/*, endLine*/) { |
|
|
|
var marker, |
|
|
|
haveMarker = false, |
|
|
|
// Search `\d+\.[\n ]`, returns next pos arter marker on success
|
|
|
|
// or -1 on fail.
|
|
|
|
function skipOrderedListMarker(state, startLine) { |
|
|
|
var ch, |
|
|
|
pos = state.bMarks[startLine] + state.tShift[startLine], |
|
|
|
max = state.eMarks[startLine]; |
|
|
|
|
|
|
|
if (pos + 1 > max) { return -1; } |
|
|
|
if (pos + 1 >= max) { return -1; } |
|
|
|
|
|
|
|
marker = state.src.charCodeAt(pos++); |
|
|
|
if (marker < 0x30/* 0 */ || marker > 0x39/* 9 */) { return -1; } |
|
|
|
ch = state.src.charCodeAt(pos++); |
|
|
|
|
|
|
|
// First char should be non zero digit
|
|
|
|
if (ch < 0x31/* 1 */ || ch > 0x39/* 9 */) { return -1; } |
|
|
|
|
|
|
|
while (pos < max) { |
|
|
|
marker = state.src.charCodeAt(pos++); |
|
|
|
for (;;) { |
|
|
|
// EOL -> fail
|
|
|
|
if (pos >= max) { return -1; } |
|
|
|
|
|
|
|
ch = state.src.charCodeAt(pos++); |
|
|
|
|
|
|
|
if (ch >= 0x30/* 0 */ && ch <= 0x39/* 9 */) { |
|
|
|
continue; |
|
|
|
} |
|
|
|
|
|
|
|
// found valid marker
|
|
|
|
if (marker === 0x29 || marker === 0x2e) { |
|
|
|
haveMarker = true; |
|
|
|
if (ch === 0x29/* ) */ || ch === 0x2e/* . */) { |
|
|
|
break; |
|
|
|
} |
|
|
|
|
|
|
|
// still skipping digits...
|
|
|
|
if (marker < 0x30/* 0 */ || marker > 0x39/* 9 */) { return -1; } |
|
|
|
} |
|
|
|
|
|
|
|
if (!haveMarker) { |
|
|
|
// " 1\n"
|
|
|
|
return -1; |
|
|
|
} |
|
|
|
if (pos < max && state.src.charCodeAt(pos) !== 0x20) { |
|
|
|
|
|
|
|
|
|
|
|
if (pos < max && state.src.charCodeAt(pos) !== 0x20/* space */) { |
|
|
|
// " 1.test " - is not a list item
|
|
|
|
return -1; |
|
|
|
} |
|
|
|
return pos; |
|
|
|
} |
|
|
|
|
|
|
|
function bullet_item(state, startLine, endLine, isOrdered) { |
|
|
|
var indentAfterMarker, indent, start, lastLine, subState, pos, |
|
|
|
max = state.eMarks[startLine]; |
|
|
|
|
|
|
|
if (isOrdered) { |
|
|
|
pos = skipOrderedListMarker(state, startLine, endLine); |
|
|
|
module.exports = function list(state, startLine, endLine, silent) { |
|
|
|
var line, |
|
|
|
nextLine, |
|
|
|
indent, |
|
|
|
start, |
|
|
|
posAfterMarker, |
|
|
|
max, |
|
|
|
indentAfterMarker, |
|
|
|
markerValue, |
|
|
|
isOrdered, |
|
|
|
lastNonEmptyLine, |
|
|
|
hasNextItem, |
|
|
|
subState, |
|
|
|
posNext, |
|
|
|
contentStart, |
|
|
|
rules_named = state.lexerBlock.rules_named; |
|
|
|
|
|
|
|
// Detect list type and position after marker
|
|
|
|
if ((posAfterMarker = skipOrderedListMarker(state, startLine)) >= 0) { |
|
|
|
isOrdered = true; |
|
|
|
} else if ((posAfterMarker = skipBulletListMarker(state, startLine)) >= 0) { |
|
|
|
isOrdered = false; |
|
|
|
} else { |
|
|
|
pos = skipBulletListMarker(state, startLine, endLine); |
|
|
|
return false; |
|
|
|
} |
|
|
|
|
|
|
|
if (pos === -1) { return false; } |
|
|
|
// For validation mode we can terminate immediately
|
|
|
|
if (silent) { return true; } |
|
|
|
|
|
|
|
// Start list
|
|
|
|
if (isOrdered) { |
|
|
|
start = state.bMarks[startLine] + state.tShift[startLine]; |
|
|
|
markerValue = Number(state.src.substr(start, posAfterMarker - start - 1)); |
|
|
|
|
|
|
|
start = pos; |
|
|
|
pos = skipSpaces(state, pos); |
|
|
|
state.tokens.push({ |
|
|
|
type: 'ordered_list_open', |
|
|
|
order: markerValue |
|
|
|
}); |
|
|
|
|
|
|
|
if (pos >= max) { |
|
|
|
// trimming space in "- \n 3" case, indent is 1 here
|
|
|
|
indentAfterMarker = 1; |
|
|
|
} else { |
|
|
|
indentAfterMarker = pos - start; |
|
|
|
state.tokens.push({ type: 'bullet_list_open' }); |
|
|
|
} |
|
|
|
|
|
|
|
// If we have more than 4 spaces, the indent is 1
|
|
|
|
// (the rest is just indented code block)
|
|
|
|
if (indentAfterMarker > 4) { indentAfterMarker = 1; } |
|
|
|
//
|
|
|
|
// Iterate list items
|
|
|
|
//
|
|
|
|
|
|
|
|
// If indent is less than 1, assume that it's one, example:
|
|
|
|
// "-\n test"
|
|
|
|
if (indentAfterMarker < 1) { indentAfterMarker = 1; } |
|
|
|
line = startLine; |
|
|
|
nextLine = line + 1; |
|
|
|
|
|
|
|
// " - test"
|
|
|
|
// ^^^^^ - calculating total length of this thing
|
|
|
|
indent = state.tShift[startLine] + indentAfterMarker + (isOrdered ? 2 : 1); |
|
|
|
while (line < endLine) { |
|
|
|
|
|
|
|
lastLine = findEndOfItem(state, startLine, endLine, indent); |
|
|
|
contentStart = skipSpaces(state, posAfterMarker); |
|
|
|
max = state.eMarks[line]; |
|
|
|
|
|
|
|
state.tokens.push({ type: 'list_item_open' }); |
|
|
|
/*state.lexerInline.tokenize( |
|
|
|
state, |
|
|
|
state.bMarks[startLine], |
|
|
|
state.eMarks[lastLine] |
|
|
|
);*/ |
|
|
|
subState = state.clone(getLines(state, startLine, lastLine + 1, true) |
|
|
|
.replace(RegExp('^ {' + indent + '}', 'mg'), '').substr(indent)); |
|
|
|
state.lexerBlock.tokenize(subState, 0, subState.lineMax); |
|
|
|
if (contentStart >= max) { |
|
|
|
// trimming space in "- \n 3" case, indent is 1 here
|
|
|
|
indentAfterMarker = 1; |
|
|
|
} else { |
|
|
|
indentAfterMarker = contentStart - posAfterMarker; |
|
|
|
} |
|
|
|
|
|
|
|
state.tokens.push({ type: 'list_item_close' }); |
|
|
|
// If we have more than 4 spaces, the indent is 1
|
|
|
|
// (the rest is just indented code block)
|
|
|
|
if (indentAfterMarker > 4) { indentAfterMarker = 1; } |
|
|
|
|
|
|
|
// If indent is less than 1, assume that it's one, example:
|
|
|
|
// "-\n test"
|
|
|
|
if (indentAfterMarker < 1) { indentAfterMarker = 1; } |
|
|
|
|
|
|
|
// " - test"
|
|
|
|
// ^^^^^ - calculating total length of this thing
|
|
|
|
indent = (posAfterMarker - state.tShift[line]) + indentAfterMarker; |
|
|
|
|
|
|
|
//
|
|
|
|
// Scan lines inside list items
|
|
|
|
//
|
|
|
|
lastNonEmptyLine = line; |
|
|
|
hasNextItem = false; |
|
|
|
|
|
|
|
for (; nextLine < endLine; nextLine++) { |
|
|
|
if (isEmpty(state, nextLine)) { |
|
|
|
// TODO: check right fenced code block
|
|
|
|
// Problem - can be in nested list, should detect indent right
|
|
|
|
|
|
|
|
// two successive newlines end the list
|
|
|
|
if (lastNonEmptyLine < nextLine - 1) { break; } |
|
|
|
continue; |
|
|
|
} |
|
|
|
|
|
|
|
// if this line is indented more than with N spaces,
|
|
|
|
// it's the new paragraph of the same list item
|
|
|
|
if (state.tShift[nextLine] >= indent) { |
|
|
|
lastNonEmptyLine = nextLine; |
|
|
|
continue; |
|
|
|
} |
|
|
|
|
|
|
|
// paragraph after linebreak - not a continuation
|
|
|
|
if (lastNonEmptyLine < nextLine - 1) { break; } |
|
|
|
|
|
|
|
state.line = lastLine + 1; |
|
|
|
return true; |
|
|
|
} |
|
|
|
//
|
|
|
|
// if we are here, then next line is not empty and not last.
|
|
|
|
//
|
|
|
|
|
|
|
|
// Check that list is not terminated with another block type
|
|
|
|
if (rules_named.fences(state, nextLine, endLine, true)) { break; } |
|
|
|
if (rules_named.hr(state, nextLine, endLine, true)) { break; } |
|
|
|
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
|
|
// In other block types this check (block ot the same type) is skipped.
|
|
|
|
|
|
|
|
// check if next item of the same type exists,
|
|
|
|
// and remember the new position after marker
|
|
|
|
if (isOrdered) { |
|
|
|
posNext = skipOrderedListMarker(state, nextLine); |
|
|
|
} else { |
|
|
|
posNext = skipBulletListMarker(state, nextLine); |
|
|
|
} |
|
|
|
if (posNext >= 0) { |
|
|
|
hasNextItem = true; |
|
|
|
break; |
|
|
|
} |
|
|
|
// Another type of list item - need to terminate this list.
|
|
|
|
if (rules_named.list(state, nextLine, endLine, true)) { break; } |
|
|
|
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
|
|
|
|
|
|
|
|
|
|
if (rules_named.heading(state, nextLine, endLine, true)) { break; } |
|
|
|
if (rules_named.lheading(state, nextLine, endLine, true)) { break; } |
|
|
|
if (rules_named.blockquote(state, nextLine, endLine, true)) { break; } |
|
|
|
if (rules_named.table(state, nextLine, endLine, true)) { break; } |
|
|
|
//if (rules_named.tag(state, nextLine, endLine, true)) { break; }
|
|
|
|
//if (rules_named.def(state, nextLine, endLine, true)) { break; }
|
|
|
|
|
|
|
|
module.exports = function list(state, startLine, endLine, silent) { |
|
|
|
var line, start, markerInt, |
|
|
|
orderedMarker = skipOrderedListMarker(state, startLine, endLine), |
|
|
|
bulletMarker = skipBulletListMarker(state, startLine, endLine), |
|
|
|
isOrdered; |
|
|
|
lastNonEmptyLine = nextLine; |
|
|
|
} |
|
|
|
|
|
|
|
if (orderedMarker === -1 && bulletMarker === -1) { return false; } |
|
|
|
if (silent) { return true; } |
|
|
|
// Run sublexer & write tokens
|
|
|
|
state.tokens.push({ type: 'list_item_open' }); |
|
|
|
|
|
|
|
isOrdered = orderedMarker !== -1; |
|
|
|
if (isOrdered) { |
|
|
|
start = state.bMarks[startLine] + state.tShift[startLine]; |
|
|
|
markerInt = Number(state.src.substr(start, orderedMarker - start)); |
|
|
|
if (markerInt > 1) { |
|
|
|
state.tokens.push({ type: 'ordered_list_open', start: markerInt }); |
|
|
|
} else { |
|
|
|
state.tokens.push({ type: 'ordered_list_open' }); |
|
|
|
} |
|
|
|
} else { |
|
|
|
state.tokens.push({ type: 'bullet_list_open' }); |
|
|
|
} |
|
|
|
// TODO: need to detect loose type.
|
|
|
|
// Problem: blocks. separated by empty lines can be member of sublists.
|
|
|
|
|
|
|
|
line = startLine; |
|
|
|
while (line < endLine) { |
|
|
|
if (bullet_item(state, line, endLine, isOrdered)) { |
|
|
|
line = state.line; |
|
|
|
subState = state.clone(state.src.slice( |
|
|
|
contentStart, |
|
|
|
state.eMarks[lastNonEmptyLine]) |
|
|
|
.replace(RegExp('^ {1,' + indent + '}', 'mg'), '')); |
|
|
|
state.lexerBlock.tokenize(subState, 0, subState.lineMax); |
|
|
|
state.tokens.push({ type: 'list_item_close' }); |
|
|
|
|
|
|
|
// if we have a trailing newline, skip it;
|
|
|
|
// if we have more than one, it should end the list,
|
|
|
|
// so can't use skipEmptyNewlines here
|
|
|
|
if (line < endLine && isEmpty(state, line)) { line++; } |
|
|
|
} else { |
|
|
|
break; |
|
|
|
} |
|
|
|
if (!hasNextItem) { break; } |
|
|
|
|
|
|
|
posAfterMarker = posNext; |
|
|
|
line = nextLine; |
|
|
|
nextLine++; |
|
|
|
} |
|
|
|
|
|
|
|
// Finilize list
|
|
|
|
if (isOrdered) { |
|
|
|
state.tokens.push({ type: 'ordered_list_close' }); |
|
|
|
} else { |
|
|
|
state.tokens.push({ type: 'bullet_list_close' }); |
|
|
|
} |
|
|
|
state.line = skipEmptyLines(state, state.line); |
|
|
|
|
|
|
|
state.line = skipEmptyLines(state, nextLine); |
|
|
|
return true; |
|
|
|
}; |
|
|
|