diff --git a/src/mistune/block_parser.py b/src/mistune/block_parser.py index 67b7039..7f64004 100644 --- a/src/mistune/block_parser.py +++ b/src/mistune/block_parser.py @@ -109,7 +109,10 @@ def __init__( def parse_blank_line(self, m: Match[str], state: BlockState) -> int: """Parse token for blank lines.""" - state.append_token({"type": "blank_line"}) + if state.cursor == 0: + state.append_token({"type": "blank_line", "newlines": m.group(0).count("\n")}) + else: + state.append_token({"type": "blank_line"}) return m.end() def parse_thematic_break(self, m: Match[str], state: BlockState) -> int: diff --git a/src/mistune/renderers/markdown.py b/src/mistune/renderers/markdown.py index 8c00917..dcf9136 100644 --- a/src/mistune/renderers/markdown.py +++ b/src/mistune/renderers/markdown.py @@ -20,10 +20,16 @@ class MarkdownRenderer(BaseRenderer): NAME = "markdown" def __call__(self, tokens: Iterable[Dict[str, Any]], state: BlockState) -> str: - out = self.render_tokens(tokens, state) + tokens = list(tokens) + leading = "" + i = 0 + while i < len(tokens) and tokens[i]["type"] == "blank_line" and "newlines" in tokens[i]: + leading += "\n" * tokens[i]["newlines"] + i += 1 + out = self.render_tokens(tokens[i:], state) # special handle for line breaks out += "\n\n".join(self.render_referrences(state)) + "\n" - return strip_end(out) + return strip_end(leading + out) def render_referrences(self, state: BlockState) -> Iterable[str]: ref_links = state.env["ref_links"] diff --git a/tests/test_renderers.py b/tests/test_renderers.py index 2076eb0..5773b59 100644 --- a/tests/test_renderers.py +++ b/tests/test_renderers.py @@ -144,6 +144,12 @@ def test_prose_not_over_escaped(self): ): self.assert_round_trip(text) + def test_leading_blank_lines_preserved(self): + # blank_line tokens at the start of the document were rendered as + # empty strings, so leading newlines disappeared on reformat + self.assertEqual(self.reformat("\nabc"), "\nabc\n") + self.assertEqual(self.reformat("\n\n\n\nabc"), "\n\n\n\nabc\n") + class TestRendererMethodRegistration(TestCase): def test_registered_renderer_method(self):