From 133788657b0ddebf0e884571fc99a5528e55a10f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vytautas=20=C5=A0altenis?= Date: Wed, 28 Oct 2015 21:37:14 +0200 Subject: [PATCH 1/5] Refix fenced code blocks w/o preceding blank lines Change approach at fixing #45: don't patch input markdown at preprocess pass, instead improve special case detection when parsing paragraphs. Leave the fenced code block detection in the preprocess pass though, it's been put to another use since then, to suppress tab expansion inside code blocks. --- block.go | 8 ++++++++ markdown.go | 8 ++------ 2 files changed, 10 insertions(+), 6 deletions(-) diff --git a/block.go b/block.go index 768c40b..a8e73d8 100644 --- a/block.go +++ b/block.go @@ -1342,6 +1342,14 @@ func (p *parser) paragraph(out *bytes.Buffer, data []byte) int { 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 if p.flags&EXTENSION_DEFINITION_LISTS != 0 { if p.dliPrefix(current) != 0 { diff --git a/markdown.go b/markdown.go index 1e3fa93..76f4dc5 100644 --- a/markdown.go +++ b/markdown.go @@ -393,7 +393,6 @@ func firstPass(p *parser, input []byte) []byte { 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 +404,13 @@ func firstPass(p *parser, input []byte) []byte { } 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 From 607f2ceb8a9183899fcc3026d23f115d733bad07 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vytautas=20=C5=A0altenis?= Date: Thu, 29 Oct 2015 20:02:11 +0200 Subject: [PATCH 2/5] Move complex conditional to a helper func --- block.go | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/block.go b/block.go index a8e73d8..0ac29f6 100644 --- a/block.go +++ b/block.go @@ -891,6 +891,18 @@ func (p *parser) quotePrefix(data []byte) int { return 0 } +// blockquote ends with at least one blank line +// followed by something without a blockquote prefix +func terminateBlockquote(p *parser, 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 @@ -905,11 +917,7 @@ func (p *parser) quote(out *bytes.Buffer, data []byte) int { 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 terminateBlockquote(p, data, beg, end) { break } From 15eb452ae4d56122e8f4ddb29f9787345fceff34 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vytautas=20=C5=A0altenis?= Date: Thu, 29 Oct 2015 20:06:27 +0200 Subject: [PATCH 3/5] Fix fenced code processing inside blockquotes Add a call to fenced code block processor inside the loop that's responsible for collecting the quoted lines. Grok all the fenced code block as a part of the quoted text. Closes #122. --- block.go | 10 +++++++ block_test.go | 72 +++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 82 insertions(+) diff --git a/block.go b/block.go index 0ac29f6..f7d5c3d 100644 --- a/block.go +++ b/block.go @@ -909,7 +909,17 @@ func (p *parser) quote(out *bytes.Buffer, data []byte) int { 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++ diff --git a/block_test.go b/block_test.go index a78a7d5..486917c 100644 --- a/block_test.go +++ b/block_test.go @@ -1065,6 +1065,78 @@ func TestFencedCodeBlock(t *testing.T) { doTestsBlock(t, tests, EXTENSION_FENCED_CODE) } +func TestFencedCodeInsideBlockquotes(t *testing.T) { + var tests = []string{ + "> ```go\n" + + "package moo\n\n" + + "```\n", + `
+
package moo
+
+
+
+`, + // ------------------------------------------- + "> foo\n" + + "> \n" + + "> ```go\n" + + "package moo\n" + + "```\n" + + "> \n" + + "> goo.\n", + `
+

foo

+ +
package moo
+
+ +

goo.

+
+`, + // ------------------------------------------- + "> foo\n" + + "> \n" + + "> quote\n" + + "continues\n" + + "```\n", + "
\n" + + "

foo

\n\n" + + "

quote\n" + + "continues\n" + + "```

\n" + + "
\n", + + "> foo\n" + + "> \n" + + "> ```go\n" + + "package moo\n" + + "```\n" + + "> \n" + + "> goo.\n" + + "> \n" + + "> ```go\n" + + "package zoo\n" + + "```\n" + + "> \n" + + "> woo.\n", + `
+

foo

+ +
package moo
+
+ +

goo.

+ +
package zoo
+
+ +

woo.

+
+`, + } + doTestsBlock(t, tests, EXTENSION_FENCED_CODE) +} + func TestTable(t *testing.T) { var tests = []string{ "a | b\n---|---\nc | d\n", From e93d8f1624b42bddac18c29086bc8e7d84c69f28 Mon Sep 17 00:00:00 2001 From: Dmitri Shuralyov Date: Sat, 31 Oct 2015 14:35:21 -0700 Subject: [PATCH 4/5] Make test values more readable, add additional test cases. --- block_test.go | 109 ++++++++++++++++++++++++++++++++++---------------- 1 file changed, 75 insertions(+), 34 deletions(-) diff --git a/block_test.go b/block_test.go index 486917c..b33c257 100644 --- a/block_test.go +++ b/block_test.go @@ -14,6 +14,7 @@ package blackfriday import ( + "strings" "testing" ) @@ -1066,10 +1067,13 @@ func TestFencedCodeBlock(t *testing.T) { } func TestFencedCodeInsideBlockquotes(t *testing.T) { + cat := func(s ...string) string { return strings.Join(s, "\n") } var tests = []string{ - "> ```go\n" + - "package moo\n\n" + - "```\n", + cat("> ```go", + "package moo", + "", + "```", + ""), `
package moo
 
@@ -1077,13 +1081,14 @@ func TestFencedCodeInsideBlockquotes(t *testing.T) {
 
`, // ------------------------------------------- - "> foo\n" + - "> \n" + - "> ```go\n" + - "package moo\n" + - "```\n" + - "> \n" + - "> goo.\n", + cat("> foo", + "> ", + "> ```go", + "package moo", + "```", + "> ", + "> goo.", + ""), `

foo

@@ -1094,31 +1099,35 @@ func TestFencedCodeInsideBlockquotes(t *testing.T) {
`, // ------------------------------------------- - "> foo\n" + - "> \n" + - "> quote\n" + - "continues\n" + - "```\n", - "
\n" + - "

foo

\n\n" + - "

quote\n" + - "continues\n" + - "```

\n" + - "
\n", + cat("> foo", + "> ", + "> quote", + "continues", + "```", + ""), + `
+

foo

- "> foo\n" + - "> \n" + - "> ```go\n" + - "package moo\n" + - "```\n" + - "> \n" + - "> goo.\n" + - "> \n" + - "> ```go\n" + - "package zoo\n" + - "```\n" + - "> \n" + - "> woo.\n", +

quote +continues +` + "```" + `

+
+`, + // ------------------------------------------- + cat("> foo", + "> ", + "> ```go", + "package moo", + "```", + "> ", + "> goo.", + "> ", + "> ```go", + "package zoo", + "```", + "> ", + "> woo.", + ""), `

foo

@@ -1134,6 +1143,38 @@ func TestFencedCodeInsideBlockquotes(t *testing.T) {
`, } + + // 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 := `
+

plain quoted text

+ +
code
+ with leading single space correctly preserved
+okay
+
+ +

rest of quoted text

+
+` + tests = append(tests, forms[0], want) + tests = append(tests, forms[1], want) + doTestsBlock(t, tests, EXTENSION_FENCED_CODE) } From 4193e8665ac72e14a0380c91e4ba350bfe311e1c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vytautas=20=C5=A0altenis?= Date: Sun, 1 Nov 2015 09:32:30 +0200 Subject: [PATCH 5/5] Drop misleading comment and turn func into method * The comment is no longer true. * Other functions similar to terminateBlockquote() are methods, so make this one a method too. --- block.go | 4 ++-- markdown.go | 1 - 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/block.go b/block.go index f7d5c3d..de84da5 100644 --- a/block.go +++ b/block.go @@ -893,7 +893,7 @@ func (p *parser) quotePrefix(data []byte) int { // blockquote ends with at least one blank line // followed by something without a blockquote prefix -func terminateBlockquote(p *parser, data []byte, beg, end int) bool { +func (p *parser) terminateBlockquote(data []byte, beg, end int) bool { if p.isEmpty(data[beg:]) <= 0 { return false } @@ -927,7 +927,7 @@ func (p *parser) quote(out *bytes.Buffer, data []byte) int { if pre := p.quotePrefix(data[beg:]); pre > 0 { // skip the prefix beg += pre - } else if terminateBlockquote(p, data, beg, end) { + } else if p.terminateBlockquote(data, beg, end) { break } diff --git a/markdown.go b/markdown.go index 76f4dc5..a4e7bd3 100644 --- a/markdown.go +++ b/markdown.go @@ -385,7 +385,6 @@ func MarkdownOptions(input []byte, renderer Renderer, opts Options) []byte { // - 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