Browse Source

Fix tab behavior inside blockquotes

pull/293/head
Alex Kocharin 8 years ago
parent
commit
7a053ef4c5
  1. 98
      lib/rules_block/blockquote.js
  2. 2
      lib/rules_block/list.js
  3. 16
      lib/rules_block/state_block.js
  4. 84
      test/fixtures/markdown-it/commonmark_extras.txt

98
lib/rules_block/blockquote.js

@ -6,9 +6,25 @@ var isSpace = require('../common/utils').isSpace;
module.exports = function blockquote(state, startLine, endLine, silent) { module.exports = function blockquote(state, startLine, endLine, silent) {
var nextLine, lastLineEmpty, oldTShift, oldSCount, oldBMarks, oldIndent, oldParentType, lines, initial, offset, ch, var adjustTab,
terminatorRules, token, ch,
i, l, terminate, i,
initial,
l,
lastLineEmpty,
lines,
nextLine,
offset,
oldBMarks,
oldBSCount,
oldIndent,
oldParentType,
oldSCount,
oldTShift,
spaceAfterMarker,
terminate,
terminatorRules,
token,
pos = state.bMarks[startLine] + state.tShift[startLine], pos = state.bMarks[startLine] + state.tShift[startLine],
max = state.eMarks[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 // so no point trying to find the end of it in silent mode
if (silent) { return true; } 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; oldIndent = state.blkIndent;
state.blkIndent = 0; state.blkIndent = 0;
// skip spaces after ">" and re-calculate offset // skip spaces after ">" and re-calculate offset
initial = offset = state.sCount[startLine] + pos - (state.bMarks[startLine] + state.tShift[startLine]); 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] ]; oldBMarks = [ state.bMarks[startLine] ];
state.bMarks[startLine] = pos; state.bMarks[startLine] = pos;
@ -36,7 +78,7 @@ module.exports = function blockquote(state, startLine, endLine, silent) {
if (isSpace(ch)) { if (isSpace(ch)) {
if (ch === 0x09) { if (ch === 0x09) {
offset += 4 - offset % 4; offset += 4 - (offset + state.bsCount[startLine] + (adjustTab ? 1 : 0)) % 4;
} else { } else {
offset++; offset++;
} }
@ -47,6 +89,9 @@ module.exports = function blockquote(state, startLine, endLine, silent) {
pos++; pos++;
} }
oldBSCount = [ state.bsCount[startLine] ];
state.bsCount[startLine] = state.sCount[startLine] + 1 + (spaceAfterMarker ? 1 : 0);
lastLineEmpty = pos >= max; lastLineEmpty = pos >= max;
oldSCount = [ state.sCount[startLine] ]; oldSCount = [ state.sCount[startLine] ];
@ -92,12 +137,38 @@ module.exports = function blockquote(state, startLine, endLine, silent) {
if (state.src.charCodeAt(pos++) === 0x3E/* > */) { if (state.src.charCodeAt(pos++) === 0x3E/* > */) {
// This line is inside the blockquote. // 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 // skip spaces after ">" and re-calculate offset
initial = offset = state.sCount[nextLine] + pos - (state.bMarks[nextLine] + state.tShift[nextLine]); 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]); oldBMarks.push(state.bMarks[nextLine]);
state.bMarks[nextLine] = pos; state.bMarks[nextLine] = pos;
@ -106,7 +177,7 @@ module.exports = function blockquote(state, startLine, endLine, silent) {
if (isSpace(ch)) { if (isSpace(ch)) {
if (ch === 0x09) { if (ch === 0x09) {
offset += 4 - offset % 4; offset += 4 - (offset + state.bsCount[nextLine] + (adjustTab ? 1 : 0)) % 4;
} else { } else {
offset++; offset++;
} }
@ -119,6 +190,9 @@ module.exports = function blockquote(state, startLine, endLine, silent) {
lastLineEmpty = pos >= max; lastLineEmpty = pos >= max;
oldBSCount.push(state.bsCount[nextLine]);
state.bsCount[nextLine] = state.sCount[nextLine] + 1 + (spaceAfterMarker ? 1 : 0);
oldSCount.push(state.sCount[nextLine]); oldSCount.push(state.sCount[nextLine]);
state.sCount[nextLine] = offset - initial; state.sCount[nextLine] = offset - initial;
@ -141,6 +215,7 @@ module.exports = function blockquote(state, startLine, endLine, silent) {
if (terminate) { break; } if (terminate) { break; }
oldBMarks.push(state.bMarks[nextLine]); oldBMarks.push(state.bMarks[nextLine]);
oldBSCount.push(state.bsCount[nextLine]);
oldTShift.push(state.tShift[nextLine]); oldTShift.push(state.tShift[nextLine]);
oldSCount.push(state.sCount[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.bMarks[i + startLine] = oldBMarks[i];
state.tShift[i + startLine] = oldTShift[i]; state.tShift[i + startLine] = oldTShift[i];
state.sCount[i + startLine] = oldSCount[i]; state.sCount[i + startLine] = oldSCount[i];
state.bsCount[i + startLine] = oldBSCount[i];
} }
state.blkIndent = oldIndent; state.blkIndent = oldIndent;

2
lib/rules_block/list.js

@ -209,7 +209,7 @@ module.exports = function list(state, startLine, endLine, silent) {
if (isSpace(ch)) { if (isSpace(ch)) {
if (ch === 0x09) { if (ch === 0x09) {
offset += 4 - offset % 4; offset += 4 - (offset + state.bsCount[nextLine]) % 4;
} else { } else {
offset++; offset++;
} }

16
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.tShift = []; // offsets of the first non-space characters (tabs not expanded)
this.sCount = []; // indents for each line (tabs 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 // block parser variables
this.blkIndent = 0; // required block content indent this.blkIndent = 0; // required block content indent
// (for example, if we are in list) // (for example, if we are in list)
@ -73,6 +85,7 @@ function StateBlock(src, md, env, tokens) {
this.eMarks.push(pos); this.eMarks.push(pos);
this.tShift.push(indent); this.tShift.push(indent);
this.sCount.push(offset); this.sCount.push(offset);
this.bsCount.push(0);
indent_found = false; indent_found = false;
indent = 0; indent = 0;
@ -86,6 +99,7 @@ function StateBlock(src, md, env, tokens) {
this.eMarks.push(s.length); this.eMarks.push(s.length);
this.tShift.push(0); this.tShift.push(0);
this.sCount.push(0); this.sCount.push(0);
this.bsCount.push(0);
this.lineMax = this.bMarks.length - 1; // don't count last fake line 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 (isSpace(ch)) {
if (ch === 0x09) { if (ch === 0x09) {
lineIndent += 4 - lineIndent % 4; lineIndent += 4 - (lineIndent + this.bsCount[line]) % 4;
} else { } else {
lineIndent++; lineIndent++;
} }

84
test/fixtures/markdown-it/commonmark_extras.txt

@ -288,12 +288,88 @@ test
Coverage. Tabs in blockquotes. Coverage. Tabs in blockquotes.
. .
> foo > test
> bar
> test
> test
> ---
> test
> ---
> test
> ---
> test
> test
> test
> test
> ---
> test
> ---
> test
> ---
> test
. .
<blockquote> <blockquote>
<pre><code> foo <pre><code> test
bar </code></pre>
</blockquote>
<blockquote>
<pre><code> test
</code></pre>
</blockquote>
<blockquote>
<pre><code>test
</code></pre>
</blockquote>
<blockquote>
<hr>
<pre><code> test
</code></pre>
</blockquote>
<blockquote>
<hr>
<pre><code> test
</code></pre>
</blockquote>
<blockquote>
<hr>
<pre><code>test
</code></pre>
</blockquote>
<blockquote>
<pre><code> test
</code></pre>
</blockquote>
<blockquote>
<pre><code> test
</code></pre>
</blockquote>
<blockquote>
<pre><code> test
</code></pre>
</blockquote>
<blockquote>
<hr>
<pre><code> test
</code></pre>
</blockquote>
<blockquote>
<hr>
<pre><code> test
</code></pre>
</blockquote>
<blockquote>
<hr>
<pre><code> test
</code></pre> </code></pre>
</blockquote> </blockquote>
. .

Loading…
Cancel
Save