From 427ba833a1275ff5bc77ec89f1d29f04dbabb1d5 Mon Sep 17 00:00:00 2001 From: James Tautges Date: Thu, 27 Oct 2016 22:25:39 +0100 Subject: [PATCH 01/11] First attempt at recreating GitHub's markdown list parsing --- lib/plugin/list.js | 147 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 147 insertions(+) create mode 100644 lib/plugin/list.js diff --git a/lib/plugin/list.js b/lib/plugin/list.js new file mode 100644 index 0000000..8caad8f --- /dev/null +++ b/lib/plugin/list.js @@ -0,0 +1,147 @@ +var isUnorderedItem = function (line) { + var match = line.match(/^( *)[-|\*|\+] /) + return match +} + +var isOrderedItem = function (line) { + var match = line.match(/^( *)([1-9][0-9]*)\. /) + return match +} + +module.exports = function (md, opts) { + var listItemParser = function (state, startLine, endLine, silent) { + var originalIndent = Math.min(state.sCount[state.line], 4) + var line = state.src.slice(state.bMarks[state.line], state.eMarks[state.line]) + + var lineStart = isOrderedItem(line) || isUnorderedItem(line) + + if (!lineStart) return false + + var ordered = isOrderedItem(line) + var markChar + if (ordered) { + markChar = '.' + } else { + markChar = line.trim()[0] + } + + state.bMarks[state.line] += lineStart[0].length + + var unorderedPos + var orderedPos + var inEmpty = 0 + var emptyInside = 0 + var indent + var sublist + var lineIndex = state.line + var finished = false + + while (end < endLine) { + line = state.src.slice(state.bMarks[lineIndex], state.eMarks[lineIndex]) + + if (line.trim() === '') { + inEmpty = 1 + continue + } + + unorderedPos = isUnorderedItem(line) + orderedPos = isOrderedItem(line) + + indent = Math.min(state.sCount[lineIndex], 4) + + if ((unorderedPos && !hRule(line)) || orderedPos) { + if (inEmpty) emptyInside = 1 + + // Reached original level in the list. Break out so that the other elements get parsed in listParser + if (indent == originalIndent) break + + if (!sublist) sublist = lineIndex + } else if (inEmpty && indent < 4 && line[0] !== '\t') { + finished = true + break + } else if (inEmpty) { + emptyInside = 1 + } + + // Notice that this algorithm actually removes up to four spaces from the start of all lines it parses. This results in strange behavior such as decreases in indentation increasing the bullet level + state.bMarks[lineIndex] += indent + state.sCount[lineIndex] -= indent + lineIndex++ + } + + var token + + // An internal empty line determines whether this list item is parsed as a block or inline. This is what causes most of the weird behavior + if (emptyInside) { + token = state.push('list_item_open', 'li', 1) + token.markup = markChar + token.map = [startLine, 0] + if (sublist && sublist < endLine) { + state.md.block.tokenize(state, startLine, sublist, true) + token = state.push('list_item_close', 'li', -1) + token.markup = markChar + state.md.block.tokenize(state, sublist, endLine, true) + } else { + state.md.block.tokenize(state, startLine, endLine, true) + token = state.push('list_item_close', 'li', -1) + token.markup = markChar + } + } else { + token = state.push('list_item_open', 'li', 1) + token.markup = markChar + token.map = [startLine, 0] + if (sublist && sublist < endLine) { + state.md.inline.tokenize(state, startLine, sublist, true) + token = state.push('list_item_close', 'li', -1) + token.markup = markChar + state.md.block.tokenize(state, sublist, endLine, true) + } else { + state.md.inline.tokenize(state, startLine, endLine, true) + token = state.push('list_item_close', 'li', -1) + token.markup = markChar + } + } + + return !finished + } + + var listParser = function (state, startLine, endLine, silent) { + var buffer = [] + var emptyLines = 0 + var line = state.src.slice(state.bMarks[state.line], state.eMarks[state.line]) + if (!isOrderedItem(line) && !isUnorderedItem(line)) return false + + var token + var keepGoing = true + + var originalIndent = state.sCount[state.line] + var ordered = isOrderedItem(line) + + if (ordered) { + token = state.push('ordered_list_open', 'ol', 1) + token.markup = '.' + } else { + token = state.push('bullet_list_open', 'ul', 1); + token.markup = line.trim()[0] + } + token.map = [startLine, 0] + + while (state.line < endLine && keepGoing) { + keepGoing = listItemParser(state, state.line, endLine, silent) + } + + if (ordered) { + token = state.push('ordered_list_close', 'ol', 1) + token.markup = '.' + } else { + token = state.push('bullet_list_close', 'ul', 1); + token.markup = line.trim()[0] + } + } + + md.block.ruler.before('list', 'ghlist', listParser, ['paragraph', 'reference', 'blockquote']) +} + + +console.log(" -this and that".match(/^ *[-|\*|\+] /)) +console.log(" 17. this".match(/^ *([1-9][0-9]*)\. /)) From d910aa93b49dea16a2831c9223472adb618545e4 Mon Sep 17 00:00:00 2001 From: James Tautges Date: Thu, 27 Oct 2016 23:44:55 +0100 Subject: [PATCH 02/11] Add list plugin to markdown instance --- lib/render.js | 3 +++ 1 file changed, 3 insertions(+) diff --git a/lib/render.js b/lib/render.js index ef09457..f434ea3 100644 --- a/lib/render.js +++ b/lib/render.js @@ -16,6 +16,8 @@ var youtube = require('./plugin/youtube') var cdnImages = require('./plugin/cdn') var badges = require('./plugin/badges') var packagize = require('./plugin/packagize') +var htmlHeading = require('./plugin/html-heading') +var ghlist = require('./plugin/list') var relaxedLinkRefs = require('./gfm/relaxed-link-reference') var githubHeadings = require('./gfm/indented-headings') @@ -75,6 +77,7 @@ var render = module.exports = function (html, options) { .use(youtube) .use(badges) .use(packagize, {package: options.package}) + .use(ghlist) if (options.highlightSyntax) parser.use(codeWrap) if (options.serveImagesWithCDN) parser.use(cdnImages, {package: options.package}) From 0662b293c3dc281ab0b95b2aa79ce566103f830d Mon Sep 17 00:00:00 2001 From: James Tautges Date: Thu, 27 Oct 2016 22:33:45 +0100 Subject: [PATCH 03/11] Implement horizontal rule check --- lib/plugin/list.js | 21 ++++++++++++++++----- 1 file changed, 16 insertions(+), 5 deletions(-) diff --git a/lib/plugin/list.js b/lib/plugin/list.js index 8caad8f..ea03411 100644 --- a/lib/plugin/list.js +++ b/lib/plugin/list.js @@ -7,6 +7,20 @@ var isOrderedItem = function (line) { var match = line.match(/^( *)([1-9][0-9]*)\. /) return match } + +var hRule = function (line) { + var stripped = line.trim() + var c = stripped[0] + if (c !== '-' && c !== '*' && c !== '+') return false + + var n = 0 + for (var i = 0; i < stripped.length; i++) { + if (stripped[i] === c) n++ + else if (stripped[i] !== ' ') return false + } + + return n >= 3 +} module.exports = function (md, opts) { var listItemParser = function (state, startLine, endLine, silent) { @@ -36,7 +50,7 @@ module.exports = function (md, opts) { var lineIndex = state.line var finished = false - while (end < endLine) { + while (lineIndex < endLine) { line = state.src.slice(state.bMarks[lineIndex], state.eMarks[lineIndex]) if (line.trim() === '') { @@ -49,6 +63,7 @@ module.exports = function (md, opts) { indent = Math.min(state.sCount[lineIndex], 4) + // Check whether the line is a horizontal rule because the same characters are used for that if ((unorderedPos && !hRule(line)) || orderedPos) { if (inEmpty) emptyInside = 1 @@ -141,7 +156,3 @@ module.exports = function (md, opts) { md.block.ruler.before('list', 'ghlist', listParser, ['paragraph', 'reference', 'blockquote']) } - - -console.log(" -this and that".match(/^ *[-|\*|\+] /)) -console.log(" 17. this".match(/^ *([1-9][0-9]*)\. /)) From f4bc492452cc80dcb37fdad97fb0e9da2f95ad18 Mon Sep 17 00:00:00 2001 From: James Tautges Date: Thu, 27 Oct 2016 22:36:12 +0100 Subject: [PATCH 04/11] Decrement block counter on closing tags --- lib/plugin/list.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/plugin/list.js b/lib/plugin/list.js index ea03411..93494bf 100644 --- a/lib/plugin/list.js +++ b/lib/plugin/list.js @@ -146,10 +146,10 @@ module.exports = function (md, opts) { } if (ordered) { - token = state.push('ordered_list_close', 'ol', 1) + token = state.push('ordered_list_close', 'ol', -1) token.markup = '.' } else { - token = state.push('bullet_list_close', 'ul', 1); + token = state.push('bullet_list_close', 'ul', -1); token.markup = line.trim()[0] } } From da28744b4870dc34f6f261c32840e73e81333a63 Mon Sep 17 00:00:00 2001 From: James Tautges Date: Thu, 27 Oct 2016 23:29:48 +0100 Subject: [PATCH 05/11] Handle line bounds better and restrict to block parsing --- lib/plugin/list.js | 70 +++++++++++++++++++++++++++++++--------------- 1 file changed, 48 insertions(+), 22 deletions(-) diff --git a/lib/plugin/list.js b/lib/plugin/list.js index 93494bf..3ece86a 100644 --- a/lib/plugin/list.js +++ b/lib/plugin/list.js @@ -24,7 +24,7 @@ var hRule = function (line) { module.exports = function (md, opts) { var listItemParser = function (state, startLine, endLine, silent) { - var originalIndent = Math.min(state.sCount[state.line], 4) + var originalIndent = Math.min(state.sCount[state.line], 3) var line = state.src.slice(state.bMarks[state.line], state.eMarks[state.line]) var lineStart = isOrderedItem(line) || isUnorderedItem(line) @@ -47,7 +47,7 @@ module.exports = function (md, opts) { var emptyInside = 0 var indent var sublist - var lineIndex = state.line + var lineIndex = state.line + 1 var finished = false while (lineIndex < endLine) { @@ -61,7 +61,7 @@ module.exports = function (md, opts) { unorderedPos = isUnorderedItem(line) orderedPos = isOrderedItem(line) - indent = Math.min(state.sCount[lineIndex], 4) + indent = Math.min(state.sCount[lineIndex], 3) // Check whether the line is a horizontal rule because the same characters are used for that if ((unorderedPos && !hRule(line)) || orderedPos) { @@ -70,7 +70,10 @@ module.exports = function (md, opts) { // Reached original level in the list. Break out so that the other elements get parsed in listParser if (indent == originalIndent) break - if (!sublist) sublist = lineIndex + if (!sublist) { + sublist = lineIndex +// console.log('Sublist starts at |' + line) + } } else if (inEmpty && indent < 4 && line[0] !== '\t') { finished = true break @@ -79,6 +82,7 @@ module.exports = function (md, opts) { } // Notice that this algorithm actually removes up to four spaces from the start of all lines it parses. This results in strange behavior such as decreases in indentation increasing the bullet level +// console.log('Cutting |' + line + '| to |' + line.slice(indent)) state.bMarks[lineIndex] += indent state.sCount[lineIndex] -= indent lineIndex++ @@ -87,49 +91,68 @@ module.exports = function (md, opts) { var token // An internal empty line determines whether this list item is parsed as a block or inline. This is what causes most of the weird behavior - if (emptyInside) { - token = state.push('list_item_open', 'li', 1) +// if (emptyInside) { + token = state.push('list_item_open', 'li', 1) + token.markup = markChar + token.map = [startLine, 0] + state.line = startLine + var oldMax = state.lineMax + if (sublist && sublist < endLine) { +// console.log('startLine: ' + String(startLine)) +// console.log('sublist: ' + String(sublist)) +// console.log('endLine: ' + String(lineIndex)) + state.lineMax = sublist + state.md.block.tokenize(state, startLine, sublist) + state.line = sublist + token = state.push('list_item_close', 'li', -1) token.markup = markChar - token.map = [startLine, 0] - if (sublist && sublist < endLine) { - state.md.block.tokenize(state, startLine, sublist, true) - token = state.push('list_item_close', 'li', -1) - token.markup = markChar - state.md.block.tokenize(state, sublist, endLine, true) - } else { - state.md.block.tokenize(state, startLine, endLine, true) - token = state.push('list_item_close', 'li', -1) - token.markup = markChar - } + state.lineMax = lineIndex + state.md.block.tokenize(state, sublist, lineIndex) } else { + state.lineMax = lineIndex + state.md.block.tokenize(state, startLine, lineIndex) + token = state.push('list_item_close', 'li', -1) + token.markup = markChar + } + state.lineMax = oldMax +/* } else { token = state.push('list_item_open', 'li', 1) token.markup = markChar token.map = [startLine, 0] if (sublist && sublist < endLine) { - state.md.inline.tokenize(state, startLine, sublist, true) + state.posMax = state.bMarks[sublist] + state.pos = state.bMarks[startLine] + state.md.inline.tokenize(state) token = state.push('list_item_close', 'li', -1) token.markup = markChar - state.md.block.tokenize(state, sublist, endLine, true) + state.md.block.tokenize(state, sublist, endLine) } else { - state.md.inline.tokenize(state, startLine, endLine, true) + console.log('hello') + state.posMax = state.bMarks[endLine] + state.pos = state.bMarks[startLine] + state.md.inline.tokenize(state) token = state.push('list_item_close', 'li', -1) token.markup = markChar } } - +*/ + state.line = lineIndex + return !finished } var listParser = function (state, startLine, endLine, silent) { +// console.log('Calling parser on line: ' + String(startLine)) var buffer = [] var emptyLines = 0 var line = state.src.slice(state.bMarks[state.line], state.eMarks[state.line]) +// console.log(line) if (!isOrderedItem(line) && !isUnorderedItem(line)) return false +// console.log('Passed') var token var keepGoing = true - var originalIndent = state.sCount[state.line] var ordered = isOrderedItem(line) if (ordered) { @@ -152,6 +175,9 @@ module.exports = function (md, opts) { token = state.push('bullet_list_close', 'ul', -1); token.markup = line.trim()[0] } +// console.log() + + return true } md.block.ruler.before('list', 'ghlist', listParser, ['paragraph', 'reference', 'blockquote']) From 9cd5c6be7463f5a1dc810ebecc87ef53cc18c966 Mon Sep 17 00:00:00 2001 From: James Tautges Date: Thu, 27 Oct 2016 23:36:23 +0100 Subject: [PATCH 06/11] Increment line index on empty lines as well --- lib/plugin/list.js | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/plugin/list.js b/lib/plugin/list.js index 3ece86a..568c064 100644 --- a/lib/plugin/list.js +++ b/lib/plugin/list.js @@ -55,6 +55,7 @@ module.exports = function (md, opts) { if (line.trim() === '') { inEmpty = 1 + lineIndex++ continue } From 396f6312e8e390cb19635dc83377c43148e6de50 Mon Sep 17 00:00:00 2001 From: James Tautges Date: Fri, 28 Oct 2016 00:04:33 +0100 Subject: [PATCH 07/11] Remove reference to htmlheading, which has its own pull request --- lib/render.js | 1 - 1 file changed, 1 deletion(-) diff --git a/lib/render.js b/lib/render.js index f434ea3..510b05f 100644 --- a/lib/render.js +++ b/lib/render.js @@ -16,7 +16,6 @@ var youtube = require('./plugin/youtube') var cdnImages = require('./plugin/cdn') var badges = require('./plugin/badges') var packagize = require('./plugin/packagize') -var htmlHeading = require('./plugin/html-heading') var ghlist = require('./plugin/list') var relaxedLinkRefs = require('./gfm/relaxed-link-reference') var githubHeadings = require('./gfm/indented-headings') From 102d469c3a4b06145ccc38a46addc05597af1bf9 Mon Sep 17 00:00:00 2001 From: James Tautges Date: Fri, 28 Oct 2016 00:13:46 +0100 Subject: [PATCH 08/11] Remove some commented code attempts and enforce standards --- lib/plugin/list.js | 308 ++++++++++++++++++++------------------------- 1 file changed, 137 insertions(+), 171 deletions(-) diff --git a/lib/plugin/list.js b/lib/plugin/list.js index 568c064..e7d5e4c 100644 --- a/lib/plugin/list.js +++ b/lib/plugin/list.js @@ -1,185 +1,151 @@ var isUnorderedItem = function (line) { - var match = line.match(/^( *)[-|\*|\+] /) - return match + var match = line.match(/^( *)[-|\*|\+] /) + return match } var isOrderedItem = function (line) { - var match = line.match(/^( *)([1-9][0-9]*)\. /) - return match + var match = line.match(/^( *)([1-9][0-9]*)\. /) + return match } var hRule = function (line) { - var stripped = line.trim() - var c = stripped[0] - if (c !== '-' && c !== '*' && c !== '+') return false - - var n = 0 - for (var i = 0; i < stripped.length; i++) { - if (stripped[i] === c) n++ - else if (stripped[i] !== ' ') return false - } + var stripped = line.trim() + var c = stripped[0] + if (c !== '-' && c !== '*' && c !== '+') return false + + var n = 0 + for (var i = 0; i < stripped.length; i++) { + if (stripped[i] === c) n++ + else if (stripped[i] !== ' ') return false + } - return n >= 3 + return n >= 3 } - + module.exports = function (md, opts) { - var listItemParser = function (state, startLine, endLine, silent) { - var originalIndent = Math.min(state.sCount[state.line], 3) - var line = state.src.slice(state.bMarks[state.line], state.eMarks[state.line]) - - var lineStart = isOrderedItem(line) || isUnorderedItem(line) - - if (!lineStart) return false - - var ordered = isOrderedItem(line) - var markChar - if (ordered) { - markChar = '.' - } else { - markChar = line.trim()[0] - } - - state.bMarks[state.line] += lineStart[0].length - - var unorderedPos - var orderedPos - var inEmpty = 0 - var emptyInside = 0 - var indent - var sublist - var lineIndex = state.line + 1 - var finished = false - - while (lineIndex < endLine) { - line = state.src.slice(state.bMarks[lineIndex], state.eMarks[lineIndex]) - - if (line.trim() === '') { - inEmpty = 1 - lineIndex++ - continue - } - - unorderedPos = isUnorderedItem(line) - orderedPos = isOrderedItem(line) - - indent = Math.min(state.sCount[lineIndex], 3) - - // Check whether the line is a horizontal rule because the same characters are used for that - if ((unorderedPos && !hRule(line)) || orderedPos) { - if (inEmpty) emptyInside = 1 - - // Reached original level in the list. Break out so that the other elements get parsed in listParser - if (indent == originalIndent) break - - if (!sublist) { - sublist = lineIndex -// console.log('Sublist starts at |' + line) - } - } else if (inEmpty && indent < 4 && line[0] !== '\t') { - finished = true - break - } else if (inEmpty) { - emptyInside = 1 - } - - // Notice that this algorithm actually removes up to four spaces from the start of all lines it parses. This results in strange behavior such as decreases in indentation increasing the bullet level -// console.log('Cutting |' + line + '| to |' + line.slice(indent)) - state.bMarks[lineIndex] += indent - state.sCount[lineIndex] -= indent - lineIndex++ - } - - var token - - // An internal empty line determines whether this list item is parsed as a block or inline. This is what causes most of the weird behavior -// if (emptyInside) { - token = state.push('list_item_open', 'li', 1) - token.markup = markChar - token.map = [startLine, 0] - state.line = startLine - var oldMax = state.lineMax - if (sublist && sublist < endLine) { -// console.log('startLine: ' + String(startLine)) -// console.log('sublist: ' + String(sublist)) -// console.log('endLine: ' + String(lineIndex)) - state.lineMax = sublist - state.md.block.tokenize(state, startLine, sublist) - state.line = sublist - token = state.push('list_item_close', 'li', -1) - token.markup = markChar - state.lineMax = lineIndex - state.md.block.tokenize(state, sublist, lineIndex) - } else { - state.lineMax = lineIndex - state.md.block.tokenize(state, startLine, lineIndex) - token = state.push('list_item_close', 'li', -1) - token.markup = markChar - } - state.lineMax = oldMax -/* } else { - token = state.push('list_item_open', 'li', 1) - token.markup = markChar - token.map = [startLine, 0] - if (sublist && sublist < endLine) { - state.posMax = state.bMarks[sublist] - state.pos = state.bMarks[startLine] - state.md.inline.tokenize(state) - token = state.push('list_item_close', 'li', -1) - token.markup = markChar - state.md.block.tokenize(state, sublist, endLine) - } else { - console.log('hello') - state.posMax = state.bMarks[endLine] - state.pos = state.bMarks[startLine] - state.md.inline.tokenize(state) - token = state.push('list_item_close', 'li', -1) - token.markup = markChar - } - } -*/ - state.line = lineIndex - - return !finished + var listItemParser = function (state, startLine, endLine, silent) { + var originalIndent = Math.min(state.sCount[state.line], 3) + var line = state.src.slice(state.bMarks[state.line], state.eMarks[state.line]) + + var lineStart = isOrderedItem(line) || isUnorderedItem(line) + + if (!lineStart) return false + + var ordered = isOrderedItem(line) + var markChar + if (ordered) { + markChar = '.' + } else { + markChar = line.trim()[0] + } + + state.bMarks[state.line] += lineStart[0].length + + var unorderedPos + var orderedPos + var inEmpty = 0 + // var emptyInside = 0 + var indent + var sublist + var lineIndex = state.line + 1 + var finished = false + + while (lineIndex < endLine) { + line = state.src.slice(state.bMarks[lineIndex], state.eMarks[lineIndex]) + + if (line.trim() === '') { + inEmpty = 1 + lineIndex++ + continue + } + + unorderedPos = isUnorderedItem(line) + orderedPos = isOrderedItem(line) + + indent = Math.min(state.sCount[lineIndex], 3) + + // Check whether the line is a horizontal rule because the same characters are used for that + if ((unorderedPos && !hRule(line)) || orderedPos) { + // if (inEmpty) emptyInside = 1 + // Reached original level in the list. Break out so that the other elements get parsed in listParser + if (indent === originalIndent) break + + if (!sublist) { + sublist = lineIndex + } + } else if (inEmpty && indent < 4 && line[0] !== '\t') { + finished = true + break + } else if (inEmpty) { + // emptyInside = 1 + } + + // Notice that this algorithm actually removes up to four spaces from the start of all lines it parses. This results in strange behavior such as decreases in indentation increasing the bullet level + state.bMarks[lineIndex] += indent + state.sCount[lineIndex] -= indent + lineIndex++ + } + + var token + + // An internal empty line determines whether this list item is parsed as a block or inline. This is what causes most of the weird behavior + token = state.push('list_item_open', 'li', 1) + token.markup = markChar + token.map = [startLine, 0] + state.line = startLine + var oldMax = state.lineMax + if (sublist && sublist < endLine) { + state.lineMax = sublist + state.md.block.tokenize(state, startLine, sublist) + state.line = sublist + token = state.push('list_item_close', 'li', -1) + token.markup = markChar + state.lineMax = lineIndex + state.md.block.tokenize(state, sublist, lineIndex) + } else { + state.lineMax = lineIndex + state.md.block.tokenize(state, startLine, lineIndex) + token = state.push('list_item_close', 'li', -1) + token.markup = markChar } - - var listParser = function (state, startLine, endLine, silent) { -// console.log('Calling parser on line: ' + String(startLine)) - var buffer = [] - var emptyLines = 0 - var line = state.src.slice(state.bMarks[state.line], state.eMarks[state.line]) -// console.log(line) - if (!isOrderedItem(line) && !isUnorderedItem(line)) return false -// console.log('Passed') - - var token - var keepGoing = true - - var ordered = isOrderedItem(line) - - if (ordered) { - token = state.push('ordered_list_open', 'ol', 1) - token.markup = '.' - } else { - token = state.push('bullet_list_open', 'ul', 1); - token.markup = line.trim()[0] - } - token.map = [startLine, 0] - - while (state.line < endLine && keepGoing) { - keepGoing = listItemParser(state, state.line, endLine, silent) - } - - if (ordered) { - token = state.push('ordered_list_close', 'ol', -1) - token.markup = '.' - } else { - token = state.push('bullet_list_close', 'ul', -1); - token.markup = line.trim()[0] - } -// console.log() - - return true + state.lineMax = oldMax + state.line = lineIndex + + return !finished + } + + var listParser = function (state, startLine, endLine, silent) { + var line = state.src.slice(state.bMarks[state.line], state.eMarks[state.line]) + if (!isOrderedItem(line) && !isUnorderedItem(line)) return false + + var token + var keepGoing = true + + var ordered = isOrderedItem(line) + + if (ordered) { + token = state.push('ordered_list_open', 'ol', 1) + token.markup = '.' + } else { + token = state.push('bullet_list_open', 'ul', 1) + token.markup = line.trim()[0] } + token.map = [startLine, 0] + + while (state.line < endLine && keepGoing) { + keepGoing = listItemParser(state, state.line, endLine, silent) + } + + if (ordered) { + token = state.push('ordered_list_close', 'ol', -1) + token.markup = '.' + } else { + token = state.push('bullet_list_close', 'ul', -1) + token.markup = line.trim()[0] + } + + return true + } - md.block.ruler.before('list', 'ghlist', listParser, ['paragraph', 'reference', 'blockquote']) + md.block.ruler.before('list', 'ghlist', listParser, ['paragraph', 'reference', 'blockquote']) } From cdcb0f1017275642938609b96341b1c14333b6bb Mon Sep 17 00:00:00 2001 From: James Tautges Date: Fri, 4 Nov 2016 17:33:38 +0000 Subject: [PATCH 09/11] Parse list item contents as inline children if the section contains a newline --- lib/plugin/list.js | 97 ++++++++++++++++++++++++++++++++-------------- 1 file changed, 68 insertions(+), 29 deletions(-) diff --git a/lib/plugin/list.js b/lib/plugin/list.js index e7d5e4c..fa8d130 100644 --- a/lib/plugin/list.js +++ b/lib/plugin/list.js @@ -8,6 +8,8 @@ var isOrderedItem = function (line) { return match } + + var hRule = function (line) { var stripped = line.trim() var c = stripped[0] @@ -25,7 +27,8 @@ var hRule = function (line) { module.exports = function (md, opts) { var listItemParser = function (state, startLine, endLine, silent) { var originalIndent = Math.min(state.sCount[state.line], 3) - var line = state.src.slice(state.bMarks[state.line], state.eMarks[state.line]) + var line = state.src.slice(state.bMarks[state.line], state.eMarks[state.line]) + console.log(line) var lineStart = isOrderedItem(line) || isUnorderedItem(line) @@ -44,14 +47,17 @@ module.exports = function (md, opts) { var unorderedPos var orderedPos var inEmpty = 0 - // var emptyInside = 0 + var emptyInside = 0 var indent - var sublist + var sublist + var inFence = false var lineIndex = state.line + 1 - var finished = false + var finished = false + var i while (lineIndex < endLine) { - line = state.src.slice(state.bMarks[lineIndex], state.eMarks[lineIndex]) + line = state.src.slice(state.bMarks[lineIndex], state.eMarks[lineIndex]) + console.log(line) if (line.trim() === '') { inEmpty = 1 @@ -59,14 +65,21 @@ module.exports = function (md, opts) { continue } - unorderedPos = isUnorderedItem(line) - orderedPos = isOrderedItem(line) + indent = Math.min(4, state.sCount[lineIndex]) + + if (!inFence) { + unorderedPos = isUnorderedItem(line) + orderedPos = isOrderedItem(line) + } - indent = Math.min(state.sCount[lineIndex], 3) + if (inEmpty && ((ordered && unorderedPos) || (!ordered && orderedPos))) { + finished = true + break + } // Check whether the line is a horizontal rule because the same characters are used for that if ((unorderedPos && !hRule(line)) || orderedPos) { - // if (inEmpty) emptyInside = 1 + if (inEmpty) emptyInside = 1 // Reached original level in the list. Break out so that the other elements get parsed in listParser if (indent === originalIndent) break @@ -77,12 +90,13 @@ module.exports = function (md, opts) { finished = true break } else if (inEmpty) { - // emptyInside = 1 + emptyInside = 1 + console.log('empty inside') } // Notice that this algorithm actually removes up to four spaces from the start of all lines it parses. This results in strange behavior such as decreases in indentation increasing the bullet level - state.bMarks[lineIndex] += indent - state.sCount[lineIndex] -= indent + state.bMarks[lineIndex] += indent + state.sCount[lineIndex] -= indent lineIndex++ } @@ -92,23 +106,48 @@ module.exports = function (md, opts) { token = state.push('list_item_open', 'li', 1) token.markup = markChar token.map = [startLine, 0] - state.line = startLine - var oldMax = state.lineMax - if (sublist && sublist < endLine) { - state.lineMax = sublist - state.md.block.tokenize(state, startLine, sublist) - state.line = sublist - token = state.push('list_item_close', 'li', -1) - token.markup = markChar - state.lineMax = lineIndex - state.md.block.tokenize(state, sublist, lineIndex) - } else { - state.lineMax = lineIndex - state.md.block.tokenize(state, startLine, lineIndex) - token = state.push('list_item_close', 'li', -1) - token.markup = markChar - } - state.lineMax = oldMax + state.line = startLine + state.pos = state.bMarks[startLine] + var oldMax = state.lineMax + var oldPosMax = state.posMax + if (emptyInside || finished) { + if (sublist && sublist < endLine) { + state.lineMax = sublist + state.md.block.tokenize(state, startLine, sublist) + token = state.push('list_item_close', 'li', -1) + token.markup = markChar + state.line = sublist + state.lineMax = lineIndex + state.md.block.tokenize(state, sublist, lineIndex) + } else { + state.lineMax = lineIndex + state.md.block.tokenize(state, startLine, lineIndex) + token = state.push('list_item_close', 'li', -1) + token.markup = markChar + } + } else { + if (sublist && sublist < endLine) { + token = state.push('inline', '', 0) + token.content = state.src.slice(state.bMarks[startLine], state.bMarks[sublist]).trim() + token.map = [startLine, sublist] + token.children = [] + token = state.push('list_item_close', 'li', -1) + token.markup = markChar + state.line = sublist + state.lineMax = lineIndex + state.md.block.tokenize(state, sublist, lineIndex) + } else { + token = state.push('inline', '', 0) + token.content = state.src.slice(state.bMarks[startLine], state.bMarks[lineIndex]).trim() + token.map = [startLine, lineIndex] + token.children = [] + token = state.push('list_item_close', 'li', -1) + token.markup = markChar + } + } + + state.lineMax = oldMax + state.posMax = oldPosMax state.line = lineIndex return !finished From b25cc038b7960b3704e8ba1481a93bd418625b63 Mon Sep 17 00:00:00 2001 From: James Tautges Date: Fri, 4 Nov 2016 17:49:28 +0000 Subject: [PATCH 10/11] Deal with code fences in list items --- lib/plugin/list.js | 78 +++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 77 insertions(+), 1 deletion(-) diff --git a/lib/plugin/list.js b/lib/plugin/list.js index fa8d130..0f2b234 100644 --- a/lib/plugin/list.js +++ b/lib/plugin/list.js @@ -8,7 +8,81 @@ var isOrderedItem = function (line) { return match } - +var prefixCodefence = function (line) { + if (line.length < 3) return 0 + + // This is how it looks in redcarpet and I didn't feel like making it better + var i = 0 + if (line[0] == ' ') { + i++ + if (line[1] == ' ') { + i++ + if (line[2] == ' ') { + i++ + } + } + } + + if (i + 2 >= line.length || !(line[i] == '~' || line[i] == '`')) { + return 0 + } + + // Make sure fence extends to the end of the line + var fenceChar = line[i] + var n = 0 + while (i < line.length && line[i] == fenceChar) { + n++ + i++ + } + + if (n < 3) return 0 + + return i +} + +var isCodefence = function (line) { + var i = prefixCodefence(line) + if (i == 0) return 0 + + while (i < line.length && line[i] == ' ') i++ + + var languageStart = i + var languageLength = 0; + if (i < line.length && line[i] == '{') { + i++ + languageStart++ + + while (i < line.length && line[i] != '}' && line[i] != '\n') { + i++ + languageLength++ + } + + if (i == line.length || line[i] != '}') return 0 + + while (languageLength > 0 && line[languageStart] == ' ') { + languageStart++ + languageLength-- + } + + while (languageLength > 0 && line[languageStart + languageLength - 1] == ' ') { + languageLength++ + } + + i++ + } else { + while (i < line.length && line[i] != ' ') { + languageLength++ + i++ + } + } + + while (i < line.length && line[i] != '\n') { + if (line[i] != ' ') return 0 + i++ + } + + return i+1 +} var hRule = function (line) { var stripped = line.trim() @@ -67,6 +141,8 @@ module.exports = function (md, opts) { indent = Math.min(4, state.sCount[lineIndex]) + if (isCodefence(line) != 0) inFence = !inFence + if (!inFence) { unorderedPos = isUnorderedItem(line) orderedPos = isOrderedItem(line) From 54bec3f590c61e6748bf3e097cec3aba41f465ec Mon Sep 17 00:00:00 2001 From: James Tautges Date: Fri, 4 Nov 2016 17:53:52 +0000 Subject: [PATCH 11/11] Enforce code style standards --- lib/plugin/list.js | 227 ++++++++++++++++++++++----------------------- 1 file changed, 113 insertions(+), 114 deletions(-) diff --git a/lib/plugin/list.js b/lib/plugin/list.js index 0f2b234..262a87b 100644 --- a/lib/plugin/list.js +++ b/lib/plugin/list.js @@ -9,79 +9,79 @@ var isOrderedItem = function (line) { } var prefixCodefence = function (line) { - if (line.length < 3) return 0 + if (line.length < 3) return 0 // This is how it looks in redcarpet and I didn't feel like making it better - var i = 0 - if (line[0] == ' ') { - i++ - if (line[1] == ' ') { - i++ - if (line[2] == ' ') { - i++ - } - } + var i = 0 + if (line[0] === ' ') { + i++ + if (line[1] === ' ') { + i++ + if (line[2] === ' ') { + i++ + } } + } - if (i + 2 >= line.length || !(line[i] == '~' || line[i] == '`')) { - return 0 - } + if (i + 2 >= line.length || !(line[i] === '~' || line[i] === '`')) { + return 0 + } // Make sure fence extends to the end of the line - var fenceChar = line[i] - var n = 0 - while (i < line.length && line[i] == fenceChar) { - n++ - i++ - } + var fenceChar = line[i] + var n = 0 + while (i < line.length && line[i] === fenceChar) { + n++ + i++ + } - if (n < 3) return 0 + if (n < 3) return 0 - return i + return i } var isCodefence = function (line) { - var i = prefixCodefence(line) - if (i == 0) return 0 + var i = prefixCodefence(line) + if (i === 0) return 0 - while (i < line.length && line[i] == ' ') i++ + while (i < line.length && line[i] === ' ') i++ - var languageStart = i - var languageLength = 0; - if (i < line.length && line[i] == '{') { - i++ - languageStart++ + var languageStart = i + var languageLength = 0 + if (i < line.length && line[i] === '{') { + i++ + languageStart++ - while (i < line.length && line[i] != '}' && line[i] != '\n') { - i++ - languageLength++ - } - - if (i == line.length || line[i] != '}') return 0 + while (i < line.length && line[i] !== '}' && line[i] !== '\n') { + i++ + languageLength++ + } - while (languageLength > 0 && line[languageStart] == ' ') { - languageStart++ - languageLength-- - } + if (i === line.length || line[i] !== '}') return 0 - while (languageLength > 0 && line[languageStart + languageLength - 1] == ' ') { - languageLength++ - } + while (languageLength > 0 && line[languageStart] === ' ') { + languageStart++ + languageLength-- + } - i++ - } else { - while (i < line.length && line[i] != ' ') { - languageLength++ - i++ - } + while (languageLength > 0 && line[languageStart + languageLength - 1] === ' ') { + languageLength++ } - while (i < line.length && line[i] != '\n') { - if (line[i] != ' ') return 0 - i++ + i++ + } else { + while (i < line.length && line[i] !== ' ') { + languageLength++ + i++ } + } + + while (i < line.length && line[i] !== '\n') { + if (line[i] !== ' ') return 0 + i++ + } - return i+1 + return i + 1 } var hRule = function (line) { @@ -101,8 +101,8 @@ var hRule = function (line) { module.exports = function (md, opts) { var listItemParser = function (state, startLine, endLine, silent) { var originalIndent = Math.min(state.sCount[state.line], 3) - var line = state.src.slice(state.bMarks[state.line], state.eMarks[state.line]) - console.log(line) + var line = state.src.slice(state.bMarks[state.line], state.eMarks[state.line]) + console.log(line) var lineStart = isOrderedItem(line) || isUnorderedItem(line) @@ -123,15 +123,14 @@ module.exports = function (md, opts) { var inEmpty = 0 var emptyInside = 0 var indent - var sublist - var inFence = false + var sublist + var inFence = false var lineIndex = state.line + 1 - var finished = false - var i + var finished = false while (lineIndex < endLine) { - line = state.src.slice(state.bMarks[lineIndex], state.eMarks[lineIndex]) - console.log(line) + line = state.src.slice(state.bMarks[lineIndex], state.eMarks[lineIndex]) + console.log(line) if (line.trim() === '') { inEmpty = 1 @@ -139,19 +138,19 @@ module.exports = function (md, opts) { continue } - indent = Math.min(4, state.sCount[lineIndex]) + indent = Math.min(4, state.sCount[lineIndex]) - if (isCodefence(line) != 0) inFence = !inFence + if (isCodefence(line) !== 0) inFence = !inFence - if (!inFence) { - unorderedPos = isUnorderedItem(line) - orderedPos = isOrderedItem(line) - } + if (!inFence) { + unorderedPos = isUnorderedItem(line) + orderedPos = isOrderedItem(line) + } - if (inEmpty && ((ordered && unorderedPos) || (!ordered && orderedPos))) { - finished = true - break - } + if (inEmpty && ((ordered && unorderedPos) || (!ordered && orderedPos))) { + finished = true + break + } // Check whether the line is a horizontal rule because the same characters are used for that if ((unorderedPos && !hRule(line)) || orderedPos) { @@ -166,13 +165,13 @@ module.exports = function (md, opts) { finished = true break } else if (inEmpty) { - emptyInside = 1 - console.log('empty inside') + emptyInside = 1 + console.log('empty inside') } // Notice that this algorithm actually removes up to four spaces from the start of all lines it parses. This results in strange behavior such as decreases in indentation increasing the bullet level - state.bMarks[lineIndex] += indent - state.sCount[lineIndex] -= indent + state.bMarks[lineIndex] += indent + state.sCount[lineIndex] -= indent lineIndex++ } @@ -182,48 +181,48 @@ module.exports = function (md, opts) { token = state.push('list_item_open', 'li', 1) token.markup = markChar token.map = [startLine, 0] - state.line = startLine - state.pos = state.bMarks[startLine] - var oldMax = state.lineMax - var oldPosMax = state.posMax - if (emptyInside || finished) { - if (sublist && sublist < endLine) { - state.lineMax = sublist - state.md.block.tokenize(state, startLine, sublist) - token = state.push('list_item_close', 'li', -1) - token.markup = markChar - state.line = sublist - state.lineMax = lineIndex - state.md.block.tokenize(state, sublist, lineIndex) - } else { - state.lineMax = lineIndex - state.md.block.tokenize(state, startLine, lineIndex) - token = state.push('list_item_close', 'li', -1) - token.markup = markChar - } + state.line = startLine + state.pos = state.bMarks[startLine] + var oldMax = state.lineMax + var oldPosMax = state.posMax + if (emptyInside || finished) { + if (sublist && sublist < endLine) { + state.lineMax = sublist + state.md.block.tokenize(state, startLine, sublist) + token = state.push('list_item_close', 'li', -1) + token.markup = markChar + state.line = sublist + state.lineMax = lineIndex + state.md.block.tokenize(state, sublist, lineIndex) + } else { + state.lineMax = lineIndex + state.md.block.tokenize(state, startLine, lineIndex) + token = state.push('list_item_close', 'li', -1) + token.markup = markChar + } + } else { + if (sublist && sublist < endLine) { + token = state.push('inline', '', 0) + token.content = state.src.slice(state.bMarks[startLine], state.bMarks[sublist]).trim() + token.map = [startLine, sublist] + token.children = [] + token = state.push('list_item_close', 'li', -1) + token.markup = markChar + state.line = sublist + state.lineMax = lineIndex + state.md.block.tokenize(state, sublist, lineIndex) } else { - if (sublist && sublist < endLine) { - token = state.push('inline', '', 0) - token.content = state.src.slice(state.bMarks[startLine], state.bMarks[sublist]).trim() - token.map = [startLine, sublist] - token.children = [] - token = state.push('list_item_close', 'li', -1) - token.markup = markChar - state.line = sublist - state.lineMax = lineIndex - state.md.block.tokenize(state, sublist, lineIndex) - } else { - token = state.push('inline', '', 0) - token.content = state.src.slice(state.bMarks[startLine], state.bMarks[lineIndex]).trim() - token.map = [startLine, lineIndex] - token.children = [] - token = state.push('list_item_close', 'li', -1) - token.markup = markChar - } + token = state.push('inline', '', 0) + token.content = state.src.slice(state.bMarks[startLine], state.bMarks[lineIndex]).trim() + token.map = [startLine, lineIndex] + token.children = [] + token = state.push('list_item_close', 'li', -1) + token.markup = markChar } - - state.lineMax = oldMax - state.posMax = oldPosMax + } + + state.lineMax = oldMax + state.posMax = oldPosMax state.line = lineIndex return !finished