From 65f096804aa7576c78f94bd14c96f004f765602b Mon Sep 17 00:00:00 2001 From: Vitaly Puzrin Date: Tue, 9 Sep 2014 00:04:04 +0400 Subject: [PATCH] list review & reorganize --- .eslintrc | 2 +- lib/lexer_block/blockquote.js | 3 + lib/lexer_block/list.js | 299 +++++++++++++++++++--------------- lib/lexer_block/paragraph.js | 2 +- lib/renderer.js | 18 +- 5 files changed, 183 insertions(+), 141 deletions(-) diff --git a/.eslintrc b/.eslintrc index 9e28e7a..ac343cb 100644 --- a/.eslintrc +++ b/.eslintrc @@ -17,7 +17,7 @@ rules: eqeqeq: 2 guard-for-in: 2 handle-callback-err: 2 - max-depth: [ 1, 3 ] + max-depth: [ 1, 4 ] max-nested-callbacks: [ 1, 4 ] # string can exceed 80 chars, but should not overflow github website :) max-len: [ 2, 120, 1000 ] diff --git a/lib/lexer_block/blockquote.js b/lib/lexer_block/blockquote.js index c619599..8338815 100644 --- a/lib/lexer_block/blockquote.js +++ b/lib/lexer_block/blockquote.js @@ -73,6 +73,9 @@ module.exports = function blockquote(state, startLine, endLine, silent) { 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.table(state, nextLine, endLine, true)) { break; } + //if (rules_named.tag(state, nextLine, endLine, true)) { break; } + //if (rules_named.def(state, nextLine, endLine, true)) { break; } } state.tokens.push({ type: 'blockquote_open' }); diff --git a/lib/lexer_block/list.js b/lib/lexer_block/list.js index dc24c0f..bf5d5b8 100644 --- a/lib/lexer_block/list.js +++ b/lib/lexer_block/list.js @@ -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; }; diff --git a/lib/lexer_block/paragraph.js b/lib/lexer_block/paragraph.js index 900421a..1eae922 100644 --- a/lib/lexer_block/paragraph.js +++ b/lib/lexer_block/paragraph.js @@ -14,13 +14,13 @@ module.exports = function paragraph(state, startLine, endLine) { // jump line-by-line until empty one or EOF while (nextLine < endLine && !isEmpty(state, nextLine)) { // Some tags can terminate paragraph without empty line. - // Try those tags in validation more (without tokens generation) if (rules_named.fences(state, nextLine, endLine, true)) { break; } if (rules_named.hr(state, nextLine, endLine, true)) { break; } 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; } nextLine++; diff --git a/lib/renderer.js b/lib/renderer.js index 8cdfe61..4fcce4f 100644 --- a/lib/renderer.js +++ b/lib/renderer.js @@ -29,14 +29,6 @@ rules.blockquote_close = function (state /*, token*/) { }; -rules.bullet_list_open = function (state /*, token*/) { - state.result += '\n'; -}; - - rules.code = function (state, token) { state.result += '
' + escapeHtml(token.content) + '
\n'; }; @@ -67,17 +59,21 @@ rules.hr = function (state/*, token*/) { }; +rules.bullet_list_open = function (state /*, token*/) { + state.result += '\n'; +}; rules.list_item_open = function (state /*, token*/) { state.result += '
  • '; }; rules.list_item_close = function (state /*, token*/) { state.result += '
  • \n'; }; - - rules.ordered_list_open = function (state, token) { state.result += ' 1 ? ' start="' + token.order + '"' : '') + '>\n'; }; rules.ordered_list_close = function (state /*, token*/) {