diff --git a/lib/rules_block/blockquote.js b/lib/rules_block/blockquote.js index 2b104aa..fcb047c 100644 --- a/lib/rules_block/blockquote.js +++ b/lib/rules_block/blockquote.js @@ -6,9 +6,25 @@ var isSpace = require('../common/utils').isSpace; module.exports = function blockquote(state, startLine, endLine, silent) { - var nextLine, lastLineEmpty, oldTShift, oldSCount, oldBMarks, oldIndent, oldParentType, lines, initial, offset, ch, - terminatorRules, token, - i, l, terminate, + var adjustTab, + ch, + i, + initial, + l, + lastLineEmpty, + lines, + nextLine, + offset, + oldBMarks, + oldBSCount, + oldIndent, + oldParentType, + oldSCount, + oldTShift, + spaceAfterMarker, + terminate, + terminatorRules, + token, pos = state.bMarks[startLine] + state.tShift[startLine], max = state.eMarks[startLine]; @@ -19,15 +35,41 @@ module.exports = function blockquote(state, startLine, endLine, silent) { // so no point trying to find the end of it in silent mode if (silent) { return true; } - // skip one optional space (but not tab, check cmark impl) after '>' - if (state.src.charCodeAt(pos) === 0x20) { pos++; } - oldIndent = state.blkIndent; state.blkIndent = 0; // skip spaces after ">" and re-calculate offset initial = offset = state.sCount[startLine] + pos - (state.bMarks[startLine] + state.tShift[startLine]); + // skip one optional space after '>' + if (state.src.charCodeAt(pos) === 0x20 /* space */) { + // ' > test ' + // ^ -- position start of line here: + pos++; + initial++; + offset++; + adjustTab = false; + spaceAfterMarker = true; + } else if (state.src.charCodeAt(pos) === 0x09 /* tab */) { + spaceAfterMarker = true; + + if ((state.bsCount[startLine] + offset) % 4 === 3) { + // ' >\t test ' + // ^ -- position start of line here (tab has width===1) + pos++; + initial++; + offset++; + adjustTab = false; + } else { + // ' >\t test ' + // ^ -- position start of line here + shift bsCount slightly + // to make extra space appear + adjustTab = true; + } + } else { + spaceAfterMarker = false; + } + oldBMarks = [ state.bMarks[startLine] ]; state.bMarks[startLine] = pos; @@ -36,7 +78,7 @@ module.exports = function blockquote(state, startLine, endLine, silent) { if (isSpace(ch)) { if (ch === 0x09) { - offset += 4 - offset % 4; + offset += 4 - (offset + state.bsCount[startLine] + (adjustTab ? 1 : 0)) % 4; } else { offset++; } @@ -47,6 +89,9 @@ module.exports = function blockquote(state, startLine, endLine, silent) { pos++; } + oldBSCount = [ state.bsCount[startLine] ]; + state.bsCount[startLine] = state.sCount[startLine] + 1 + (spaceAfterMarker ? 1 : 0); + lastLineEmpty = pos >= max; oldSCount = [ state.sCount[startLine] ]; @@ -92,12 +137,38 @@ module.exports = function blockquote(state, startLine, endLine, silent) { if (state.src.charCodeAt(pos++) === 0x3E/* > */) { // This line is inside the blockquote. - // skip one optional space (but not tab, check cmark impl) after '>' - if (state.src.charCodeAt(pos) === 0x20) { pos++; } - // skip spaces after ">" and re-calculate offset initial = offset = state.sCount[nextLine] + pos - (state.bMarks[nextLine] + state.tShift[nextLine]); + // skip one optional space after '>' + if (state.src.charCodeAt(pos) === 0x20 /* space */) { + // ' > test ' + // ^ -- position start of line here: + pos++; + initial++; + offset++; + adjustTab = false; + spaceAfterMarker = true; + } else if (state.src.charCodeAt(pos) === 0x09 /* tab */) { + spaceAfterMarker = true; + + if ((state.bsCount[nextLine] + offset) % 4 === 3) { + // ' >\t test ' + // ^ -- position start of line here (tab has width===1) + pos++; + initial++; + offset++; + adjustTab = false; + } else { + // ' >\t test ' + // ^ -- position start of line here + shift bsCount slightly + // to make extra space appear + adjustTab = true; + } + } else { + spaceAfterMarker = false; + } + oldBMarks.push(state.bMarks[nextLine]); state.bMarks[nextLine] = pos; @@ -106,7 +177,7 @@ module.exports = function blockquote(state, startLine, endLine, silent) { if (isSpace(ch)) { if (ch === 0x09) { - offset += 4 - offset % 4; + offset += 4 - (offset + state.bsCount[nextLine] + (adjustTab ? 1 : 0)) % 4; } else { offset++; } @@ -119,6 +190,9 @@ module.exports = function blockquote(state, startLine, endLine, silent) { lastLineEmpty = pos >= max; + oldBSCount.push(state.bsCount[nextLine]); + state.bsCount[nextLine] = state.sCount[nextLine] + 1 + (spaceAfterMarker ? 1 : 0); + oldSCount.push(state.sCount[nextLine]); state.sCount[nextLine] = offset - initial; @@ -141,6 +215,7 @@ module.exports = function blockquote(state, startLine, endLine, silent) { if (terminate) { break; } oldBMarks.push(state.bMarks[nextLine]); + oldBSCount.push(state.bsCount[nextLine]); oldTShift.push(state.tShift[nextLine]); oldSCount.push(state.sCount[nextLine]); @@ -167,6 +242,7 @@ module.exports = function blockquote(state, startLine, endLine, silent) { state.bMarks[i + startLine] = oldBMarks[i]; state.tShift[i + startLine] = oldTShift[i]; state.sCount[i + startLine] = oldSCount[i]; + state.bsCount[i + startLine] = oldBSCount[i]; } state.blkIndent = oldIndent; diff --git a/lib/rules_block/list.js b/lib/rules_block/list.js index 32a2d72..4a052f3 100644 --- a/lib/rules_block/list.js +++ b/lib/rules_block/list.js @@ -209,7 +209,7 @@ module.exports = function list(state, startLine, endLine, silent) { if (isSpace(ch)) { if (ch === 0x09) { - offset += 4 - offset % 4; + offset += 4 - (offset + state.bsCount[nextLine]) % 4; } else { offset++; } diff --git a/lib/rules_block/state_block.js b/lib/rules_block/state_block.js index fa523a6..3119ff1 100644 --- a/lib/rules_block/state_block.js +++ b/lib/rules_block/state_block.js @@ -27,6 +27,18 @@ function StateBlock(src, md, env, tokens) { this.tShift = []; // offsets of the first non-space characters (tabs not expanded) this.sCount = []; // indents for each line (tabs expanded) + // An amount of virtual spaces (tabs expanded) between beginning + // of each line (bMarks) and real beginning of that line. + // + // It exists only as a hack because blockquotes override bMarks + // losing information in the process. + // + // It's used only when expanding tabs, you can think about it as + // an initial tab length, e.g. bsCount=21 applied to string `\t123` + // means first tab should be expanded to 4-21%4 === 3 spaces. + // + this.bsCount = []; + // block parser variables this.blkIndent = 0; // required block content indent // (for example, if we are in list) @@ -73,6 +85,7 @@ function StateBlock(src, md, env, tokens) { this.eMarks.push(pos); this.tShift.push(indent); this.sCount.push(offset); + this.bsCount.push(0); indent_found = false; indent = 0; @@ -86,6 +99,7 @@ function StateBlock(src, md, env, tokens) { this.eMarks.push(s.length); this.tShift.push(0); this.sCount.push(0); + this.bsCount.push(0); this.lineMax = this.bMarks.length - 1; // don't count last fake line } @@ -183,7 +197,7 @@ StateBlock.prototype.getLines = function getLines(begin, end, indent, keepLastLF if (isSpace(ch)) { if (ch === 0x09) { - lineIndent += 4 - lineIndent % 4; + lineIndent += 4 - (lineIndent + this.bsCount[line]) % 4; } else { lineIndent++; } diff --git a/test/fixtures/markdown-it/commonmark_extras.txt b/test/fixtures/markdown-it/commonmark_extras.txt index 040d156..3851a5d 100644 --- a/test/fixtures/markdown-it/commonmark_extras.txt +++ b/test/fixtures/markdown-it/commonmark_extras.txt @@ -288,12 +288,88 @@ test Coverage. Tabs in blockquotes. . - > foo - > bar +> test + + > test + + > test + +> --- +> test + + > --- + > test + + > --- + > test + +> test + + > test + + > test + +> --- +> test + + > --- + > test + + > --- + > test .
-+foo - bar +
+test +
+++test +
+++test +
++
++test +
++
++test +
++
++test +
+++test +
+++test +
+++test +
++
++test +
++
++test +
+.
+test