Merge pull request #211 from russross/issue-122 Fix issue 122: fenced code blocks inside blockquotes
Vytautas Ĺ altenis vytas@rtfb.lt
Sun, 01 Nov 2015 09:44:04 +0200
3 files changed,
146 insertions(+),
12 deletions(-)
M
block.go
→
block.go
@@ -891,13 +891,35 @@ }
return 0 } +// blockquote ends with at least one blank line +// followed by something without a blockquote prefix +func (p *parser) terminateBlockquote(data []byte, beg, end int) bool { + if p.isEmpty(data[beg:]) <= 0 { + return false + } + if end >= len(data) { + return true + } + return p.quotePrefix(data[end:]) == 0 && p.isEmpty(data[end:]) == 0 +} + // parse a blockquote fragment func (p *parser) quote(out *bytes.Buffer, data []byte) int { var raw bytes.Buffer beg, end := 0, 0 for beg < len(data) { end = beg + // Step over whole lines, collecting them. While doing that, check for + // fenced code and if one's found, incorporate it altogether, + // irregardless of any contents inside it for data[end] != '\n' { + if p.flags&EXTENSION_FENCED_CODE != 0 { + if i := p.fencedCode(out, data[end:], false); i > 0 { + // -1 to compensate for the extra end++ after the loop: + end += i - 1 + break + } + } end++ } end++@@ -905,11 +927,7 @@
if pre := p.quotePrefix(data[beg:]); pre > 0 { // skip the prefix beg += pre - } else if p.isEmpty(data[beg:]) > 0 && - (end >= len(data) || - (p.quotePrefix(data[end:]) == 0 && p.isEmpty(data[end:]) == 0)) { - // blockquote ends with at least one blank line - // followed by something without a blockquote prefix + } else if p.terminateBlockquote(data, beg, end) { break }@@ -1340,6 +1358,14 @@ // if there's a prefixed header or a horizontal rule after this, paragraph is over
if p.isPrefixHeader(current) || p.isHRule(current) { p.renderParagraph(out, data[:i]) return i + } + + // if there's a fenced code block, paragraph is over + if p.flags&EXTENSION_FENCED_CODE != 0 { + if p.fencedCode(out, current, false) > 0 { + p.renderParagraph(out, data[:i]) + return i + } } // if there's a definition list item, prev line is a definition term
M
block_test.go
→
block_test.go
@@ -14,6 +14,7 @@
package blackfriday import ( + "strings" "testing" )@@ -1062,6 +1063,118 @@
"Some text before a fenced code block\n``` oz\ncode blocks breakup paragraphs\n```\nSome text in between\n``` oz\nmultiple code blocks work okay\n```\nAnd some text after a fenced code block", "<p>Some text before a fenced code block</p>\n\n<pre><code class=\"language-oz\">code blocks breakup paragraphs\n</code></pre>\n\n<p>Some text in between</p>\n\n<pre><code class=\"language-oz\">multiple code blocks work okay\n</code></pre>\n\n<p>And some text after a fenced code block</p>\n", } + doTestsBlock(t, tests, EXTENSION_FENCED_CODE) +} + +func TestFencedCodeInsideBlockquotes(t *testing.T) { + cat := func(s ...string) string { return strings.Join(s, "\n") } + var tests = []string{ + cat("> ```go", + "package moo", + "", + "```", + ""), + `<blockquote> +<pre><code class="language-go">package moo + +</code></pre> +</blockquote> +`, + // ------------------------------------------- + cat("> foo", + "> ", + "> ```go", + "package moo", + "```", + "> ", + "> goo.", + ""), + `<blockquote> +<p>foo</p> + +<pre><code class="language-go">package moo +</code></pre> + +<p>goo.</p> +</blockquote> +`, + // ------------------------------------------- + cat("> foo", + "> ", + "> quote", + "continues", + "```", + ""), + `<blockquote> +<p>foo</p> + +<p>quote +continues +` + "```" + `</p> +</blockquote> +`, + // ------------------------------------------- + cat("> foo", + "> ", + "> ```go", + "package moo", + "```", + "> ", + "> goo.", + "> ", + "> ```go", + "package zoo", + "```", + "> ", + "> woo.", + ""), + `<blockquote> +<p>foo</p> + +<pre><code class="language-go">package moo +</code></pre> + +<p>goo.</p> + +<pre><code class="language-go">package zoo +</code></pre> + +<p>woo.</p> +</blockquote> +`, + } + + // These 2 alternative forms of blockquoted fenced code blocks should produce same output. + forms := [2]string{ + cat("> plain quoted text", + "> ```fenced", + "code", + " with leading single space correctly preserved", + "okay", + "```", + "> rest of quoted text"), + cat("> plain quoted text", + "> ```fenced", + "> code", + "> with leading single space correctly preserved", + "> okay", + "> ```", + "> rest of quoted text"), + } + want := `<blockquote> +<p>plain quoted text</p> + +<pre><code class="language-fenced">code + with leading single space correctly preserved +okay +</code></pre> + +<p>rest of quoted text</p> +</blockquote> +` + tests = append(tests, forms[0], want) + tests = append(tests, forms[1], want) + doTestsBlock(t, tests, EXTENSION_FENCED_CODE) }
M
markdown.go
→
markdown.go
@@ -385,7 +385,6 @@ // - extract references
// - expand tabs // - normalize newlines // - copy everything else -// - add missing newlines before fenced code blocks func firstPass(p *parser, input []byte) []byte { var out bytes.Buffer tabSize := TAB_SIZE_DEFAULT@@ -393,7 +392,6 @@ if p.flags&EXTENSION_TAB_SIZE_EIGHT != 0 {
tabSize = TAB_SIZE_EIGHT } beg, end := 0, 0 - lastLineWasBlank := false lastFencedCodeBlockEnd := 0 for beg < len(input) { // iterate over lines if end = isReference(p, input[beg:], tabSize); end > 0 {@@ -405,16 +403,13 @@ end++
} if p.flags&EXTENSION_FENCED_CODE != 0 { - // when last line was none blank and a fenced code block comes after + // track fenced code block boundaries to suppress tab expansion + // inside them: if beg >= lastFencedCodeBlockEnd { if i := p.fencedCode(&out, input[beg:], false); i > 0 { - if !lastLineWasBlank { - out.WriteByte('\n') // need to inject additional linebreak - } lastFencedCodeBlockEnd = beg + i } } - lastLineWasBlank = end == beg } // add the line body if present