diff --git a/.gitignore b/.gitignore index 06a9f10b..75623dcc 100644 --- a/.gitignore +++ b/.gitignore @@ -5,3 +5,4 @@ _obj _test* markdown +tags diff --git a/block.go b/block.go index 5f71bc6f..a4ab9302 100644 --- a/block.go +++ b/block.go @@ -1230,6 +1230,17 @@ func (p *parser) paragraph(out *bytes.Buffer, data []byte) int { return i } + // if there's a list after this, paragraph is over + if p.flags&EXTENSION_NO_EMPTY_LINE_BEFORE_BLOCK != 0 { + if p.uliPrefix(current) != 0 || + p.oliPrefix(current) != 0 || + p.quotePrefix(current) != 0 || + p.codePrefix(current) != 0 { + p.renderParagraph(out, data[:i]) + return i + } + } + // otherwise, scan to the beginning of the next line for data[i] != '\n' { i++ diff --git a/block_test.go b/block_test.go index d2edaf84..de4f7a93 100644 --- a/block_test.go +++ b/block_test.go @@ -692,3 +692,281 @@ func TestTable(t *testing.T) { } doTestsBlock(t, tests, EXTENSION_TABLES) } + +func TestUnorderedListWith_EXTENSION_NO_EMPTY_LINE_BEFORE_BLOCK(t *testing.T) { + var tests = []string{ + "* Hello\n", + "\n", + + "* Yin\n* Yang\n", + "\n", + + "* Ting\n* Bong\n* Goo\n", + "\n", + + "* Yin\n\n* Yang\n", + "\n", + + "* Ting\n\n* Bong\n* Goo\n", + "\n", + + "+ Hello\n", + "\n", + + "+ Yin\n+ Yang\n", + "\n", + + "+ Ting\n+ Bong\n+ Goo\n", + "\n", + + "+ Yin\n\n+ Yang\n", + "\n", + + "+ Ting\n\n+ Bong\n+ Goo\n", + "\n", + + "- Hello\n", + "\n", + + "- Yin\n- Yang\n", + "\n", + + "- Ting\n- Bong\n- Goo\n", + "\n", + + "- Yin\n\n- Yang\n", + "\n", + + "- Ting\n\n- Bong\n- Goo\n", + "\n", + + "*Hello\n", + "

*Hello

\n", + + "* Hello \n", + "\n", + + "* Hello \n Next line \n", + "\n", + + "Paragraph\n* No linebreak\n", + "

Paragraph

\n\n\n", + + "Paragraph\n\n* Linebreak\n", + "

Paragraph

\n\n\n", + + "* List\n * Nested list\n", + "\n", + + "* List\n\n * Nested list\n", + "\n", + + "* List\n Second line\n\n + Nested\n", + "\n", + + "* List\n + Nested\n\n Continued\n", + "\n", + + "* List\n * shallow indent\n", + "\n", + + "* List\n" + + " * shallow indent\n" + + " * part of second list\n" + + " * still second\n" + + " * almost there\n" + + " * third level\n", + "\n", + + "* List\n extra indent, same paragraph\n", + "\n", + + "* List\n\n code block\n", + "\n", + + "* List\n\n code block with spaces\n", + "\n", + + "* List\n\n * sublist\n\n normal text\n\n * another sublist\n", + "\n", + } + doTestsBlock(t, tests, EXTENSION_NO_EMPTY_LINE_BEFORE_BLOCK) +} + +func TestOrderedList_EXTENSION_NO_EMPTY_LINE_BEFORE_BLOCK(t *testing.T) { + var tests = []string{ + "1. Hello\n", + "
    \n
  1. Hello
  2. \n
\n", + + "1. Yin\n2. Yang\n", + "
    \n
  1. Yin
  2. \n
  3. Yang
  4. \n
\n", + + "1. Ting\n2. Bong\n3. Goo\n", + "
    \n
  1. Ting
  2. \n
  3. Bong
  4. \n
  5. Goo
  6. \n
\n", + + "1. Yin\n\n2. Yang\n", + "
    \n
  1. Yin

  2. \n\n
  3. Yang

  4. \n
\n", + + "1. Ting\n\n2. Bong\n3. Goo\n", + "
    \n
  1. Ting

  2. \n\n
  3. Bong

  4. \n\n
  5. Goo

  6. \n
\n", + + "1 Hello\n", + "

1 Hello

\n", + + "1.Hello\n", + "

1.Hello

\n", + + "1. Hello \n", + "
    \n
  1. Hello
  2. \n
\n", + + "1. Hello \n Next line \n", + "
    \n
  1. Hello\nNext line
  2. \n
\n", + + "Paragraph\n1. No linebreak\n", + "

Paragraph

\n\n
    \n
  1. No linebreak
  2. \n
\n", + + "Paragraph\n\n1. Linebreak\n", + "

Paragraph

\n\n
    \n
  1. Linebreak
  2. \n
\n", + + "1. List\n 1. Nested list\n", + "
    \n
  1. List\n\n
      \n
    1. Nested list
    2. \n
  2. \n
\n", + + "1. List\n\n 1. Nested list\n", + "
    \n
  1. List

    \n\n
      \n
    1. Nested list
    2. \n
  2. \n
\n", + + "1. List\n Second line\n\n 1. Nested\n", + "
    \n
  1. List\nSecond line

    \n\n
      \n
    1. Nested
    2. \n
  2. \n
\n", + + "1. List\n 1. Nested\n\n Continued\n", + "
    \n
  1. List

    \n\n
      \n
    1. Nested
    2. \n
    \n\n

    Continued

  2. \n
\n", + + "1. List\n 1. shallow indent\n", + "
    \n
  1. List\n\n
      \n
    1. shallow indent
    2. \n
  2. \n
\n", + + "1. List\n" + + " 1. shallow indent\n" + + " 2. part of second list\n" + + " 3. still second\n" + + " 4. almost there\n" + + " 1. third level\n", + "
    \n" + + "
  1. List\n\n" + + "
      \n" + + "
    1. shallow indent
    2. \n" + + "
    3. part of second list
    4. \n" + + "
    5. still second
    6. \n" + + "
    7. almost there\n\n" + + "
        \n" + + "
      1. third level
      2. \n" + + "
    8. \n" + + "
  2. \n" + + "
\n", + + "1. List\n extra indent, same paragraph\n", + "
    \n
  1. List\n extra indent, same paragraph
  2. \n
\n", + + "1. List\n\n code block\n", + "
    \n
  1. List

    \n\n
    code block\n
  2. \n
\n", + + "1. List\n\n code block with spaces\n", + "
    \n
  1. List

    \n\n
      code block with spaces\n
  2. \n
\n", + + "1. List\n * Mixted list\n", + "
    \n
  1. List\n\n
  2. \n
\n", + + "1. List\n * Mixed list\n", + "
    \n
  1. List\n\n
  2. \n
\n", + + "* Start with unordered\n 1. Ordered\n", + "\n", + + "* Start with unordered\n 1. Ordered\n", + "\n", + + "1. numbers\n1. are ignored\n", + "
    \n
  1. numbers
  2. \n
  3. are ignored
  4. \n
\n", + } + doTestsBlock(t, tests, EXTENSION_NO_EMPTY_LINE_BEFORE_BLOCK) +} + +func TestFencedCodeBlock_EXTENSION_NO_EMPTY_LINE_BEFORE_BLOCK(t *testing.T) { + var tests = []string{ + "``` go\nfunc foo() bool {\n\treturn true;\n}\n```\n", + "
func foo() bool {\n    return true;\n}\n
\n", + + "``` c\n/* special & char < > \" escaping */\n```\n", + "
/* special & char < > " escaping */\n
\n", + + "``` c\nno *inline* processing ~~of text~~\n```\n", + "
no *inline* processing ~~of text~~\n
\n", + + "```\nNo language\n```\n", + "
No language\n
\n", + + "``` {ocaml}\nlanguage in braces\n```\n", + "
language in braces\n
\n", + + "``` {ocaml} \nwith extra whitespace\n```\n", + "
with extra whitespace\n
\n", + + "```{ ocaml }\nwith extra whitespace\n```\n", + "
with extra whitespace\n
\n", + + "~ ~~ java\nWith whitespace\n~~~\n", + "

~ ~~ java\nWith whitespace\n~~~

\n", + + "~~\nonly two\n~~\n", + "

~~\nonly two\n~~

\n", + + "```` python\nextra\n````\n", + "
extra\n
\n", + + "~~~ perl\nthree to start, four to end\n~~~~\n", + "

~~~ perl\nthree to start, four to end\n~~~~

\n", + + "~~~~ perl\nfour to start, three to end\n~~~\n", + "

~~~~ perl\nfour to start, three to end\n~~~

\n", + + "~~~ bash\ntildes\n~~~\n", + "
tildes\n
\n", + + "``` lisp\nno ending\n", + "

``` lisp\nno ending

\n", + + "~~~ lisp\nend with language\n~~~ lisp\n", + "

~~~ lisp\nend with language\n~~~ lisp

\n", + + "```\nmismatched begin and end\n~~~\n", + "

```\nmismatched begin and end\n~~~

\n", + + "~~~\nmismatched begin and end\n```\n", + "

~~~\nmismatched begin and end\n```

\n", + + " ``` oz\nleading spaces\n```\n", + "
leading spaces\n
\n", + + " ``` oz\nleading spaces\n ```\n", + "
leading spaces\n
\n", + + " ``` oz\nleading spaces\n ```\n", + "
leading spaces\n
\n", + + "``` oz\nleading spaces\n ```\n", + "
leading spaces\n
\n", + + " ``` oz\nleading spaces\n ```\n", + "
``` oz\n
\n\n

leading spaces

\n\n
```\n
\n", + } + doTestsBlock(t, tests, EXTENSION_FENCED_CODE|EXTENSION_NO_EMPTY_LINE_BEFORE_BLOCK) +} diff --git a/markdown.go b/markdown.go index 86886830..77e93e88 100644 --- a/markdown.go +++ b/markdown.go @@ -28,16 +28,17 @@ const VERSION = "1.1" // These are the supported markdown parsing extensions. // OR these values together to select multiple extensions. const ( - EXTENSION_NO_INTRA_EMPHASIS = 1 << iota // ignore emphasis markers inside words - EXTENSION_TABLES // render tables - EXTENSION_FENCED_CODE // render fenced code blocks - EXTENSION_AUTOLINK // detect embedded URLs that are not explicitly marked - EXTENSION_STRIKETHROUGH // strikethrough text using ~~test~~ - EXTENSION_LAX_HTML_BLOCKS // loosen up HTML block parsing rules - EXTENSION_SPACE_HEADERS // be strict about prefix header rules - EXTENSION_HARD_LINE_BREAK // translate newlines into line breaks - EXTENSION_TAB_SIZE_EIGHT // expand tabs to eight spaces instead of four - EXTENSION_FOOTNOTES // Pandoc-style footnotes + EXTENSION_NO_INTRA_EMPHASIS = 1 << iota // ignore emphasis markers inside words + EXTENSION_TABLES // render tables + EXTENSION_FENCED_CODE // render fenced code blocks + EXTENSION_AUTOLINK // detect embedded URLs that are not explicitly marked + EXTENSION_STRIKETHROUGH // strikethrough text using ~~test~~ + EXTENSION_LAX_HTML_BLOCKS // loosen up HTML block parsing rules + EXTENSION_SPACE_HEADERS // be strict about prefix header rules + EXTENSION_HARD_LINE_BREAK // translate newlines into line breaks + EXTENSION_TAB_SIZE_EIGHT // expand tabs to eight spaces instead of four + EXTENSION_FOOTNOTES // Pandoc-style footnotes + EXTENSION_NO_EMPTY_LINE_BEFORE_BLOCK // No need to insert an empty line to start a (code, quote, order list, unorder list)block ) // These are the possible flag values for the link renderer. diff --git a/upskirtref/Hard-wrapped paragraphs with list-like lines no empty line before block.html b/upskirtref/Hard-wrapped paragraphs with list-like lines no empty line before block.html new file mode 100644 index 00000000..fc253194 --- /dev/null +++ b/upskirtref/Hard-wrapped paragraphs with list-like lines no empty line before block.html @@ -0,0 +1,14 @@ +

In Markdown 1.0.0 and earlier. Version

+ +
    +
  1. This line turns into a list item. +Because a hard-wrapped line in the +middle of a paragraph looked like a +list item.
  2. +
+ +

Here's one with a bullet.

+ + diff --git a/upskirtref/Hard-wrapped paragraphs with list-like lines no empty line before block.text b/upskirtref/Hard-wrapped paragraphs with list-like lines no empty line before block.text new file mode 100644 index 00000000..f8a5b27b --- /dev/null +++ b/upskirtref/Hard-wrapped paragraphs with list-like lines no empty line before block.text @@ -0,0 +1,8 @@ +In Markdown 1.0.0 and earlier. Version +8. This line turns into a list item. +Because a hard-wrapped line in the +middle of a paragraph looked like a +list item. + +Here's one with a bullet. +* criminey. diff --git a/upskirtref_test.go b/upskirtref_test.go index 219de4cb..8205e48c 100644 --- a/upskirtref_test.go +++ b/upskirtref_test.go @@ -19,12 +19,12 @@ import ( "testing" ) -func runMarkdownReference(input string) string { +func runMarkdownReference(input string, flag int) string { renderer := HtmlRenderer(0, "", "") - return string(Markdown([]byte(input), renderer, 0)) + return string(Markdown([]byte(input), renderer, flag)) } -func doTestsReference(t *testing.T, files []string) { +func doTestsReference(t *testing.T, files []string, flag int) { // catch and report panics var candidate string defer func() { @@ -50,7 +50,7 @@ func doTestsReference(t *testing.T, files []string) { } expected := string(expectedBytes) - actual := string(runMarkdownReference(input)) + actual := string(runMarkdownReference(input, flag)) if actual != expected { t.Errorf("\n [%#v]\nExpected[%#v]\nActual [%#v]", basename+".text", expected, actual) @@ -62,7 +62,7 @@ func doTestsReference(t *testing.T, files []string) { start := 0 for end := start + 1; end <= len(input); end++ { candidate = input[start:end] - _ = runMarkdownReference(candidate) + _ = runMarkdownReference(candidate, flag) } } } @@ -93,5 +93,33 @@ func TestReference(t *testing.T) { "Tabs", "Tidyness", } - doTestsReference(t, files) + doTestsReference(t, files, 0) +} + +func TestReference_EXTENSION_NO_EMPTY_LINE_BEFORE_BLOCK(t *testing.T) { + files := []string{ + "Amps and angle encoding", + "Auto links", + "Backslash escapes", + "Blockquotes with code blocks", + "Code Blocks", + "Code Spans", + "Hard-wrapped paragraphs with list-like lines no empty line before block", + "Horizontal rules", + "Inline HTML (Advanced)", + "Inline HTML (Simple)", + "Inline HTML comments", + "Links, inline style", + "Links, reference style", + "Links, shortcut references", + "Literal quotes in titles", + "Markdown Documentation - Basics", + "Markdown Documentation - Syntax", + "Nested blockquotes", + "Ordered and unordered lists", + "Strong and em together", + "Tabs", + "Tidyness", + } + doTestsReference(t, files, EXTENSION_NO_EMPTY_LINE_BEFORE_BLOCK) }