From 5aa2c1cfc003164e1ba0ecff0acaf61302228d36 Mon Sep 17 00:00:00 2001 From: Kyle King Date: Mon, 26 Jan 2026 21:41:55 -0600 Subject: [PATCH 1/2] =?UTF-8?q?=F0=9F=90=9B=20FIX:=20properly=20wrap=20foo?= =?UTF-8?q?tnotes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- mdformat_footnote/plugin.py | 31 +++++++++++++++++++++---------- tests/test_word_wrap.py | 7 ++++--- 2 files changed, 25 insertions(+), 13 deletions(-) diff --git a/mdformat_footnote/plugin.py b/mdformat_footnote/plugin.py index 98088de..0ea5d07 100644 --- a/mdformat_footnote/plugin.py +++ b/mdformat_footnote/plugin.py @@ -24,19 +24,30 @@ def _footnote_ref_renderer(node: RenderTreeNode, context: RenderContext) -> str: def _footnote_renderer(node: RenderTreeNode, context: RenderContext) -> str: first_line = f"[^{node.meta['label']}]:" indent = " " * 4 - elements = [] + + children = [c for c in node.children if c.type != "footnote_anchor"] + + if children and children[0].type == "paragraph": + with context.indented(len(first_line) + 1): + first_element = children[0].render(context) + + first_para_first_line, *first_para_rest_lines = first_element.split("\n") + + with context.indented(len(indent)): + elements = [child.render(context) for child in children[1:]] + + result = first_line + " " + first_para_first_line + if first_para_rest_lines: + result += "\n" + textwrap.indent("\n".join(first_para_rest_lines), indent) + if elements: + result += "\n\n" + textwrap.indent("\n\n".join(elements), indent) + return result + with context.indented(len(indent)): - for child in node.children: - if child.type == "footnote_anchor": - continue - elements.append(child.render(context)) + elements = [child.render(context) for child in children] body = textwrap.indent("\n\n".join(elements), indent) - # if the first body element is a paragraph, we can start on the first line, - # otherwise we start on the second line - if body and node.children and node.children[0].type != "paragraph": + if body: body = "\n" + body - else: - body = " " + body.lstrip() return first_line + body diff --git a/tests/test_word_wrap.py b/tests/test_word_wrap.py index fbe2867..6cdcb84 100644 --- a/tests/test_word_wrap.py +++ b/tests/test_word_wrap.py @@ -13,9 +13,10 @@ def test_word_wrap(): expected_output = """\ [^a] -[^a]: Ooh no, the first line of this first - paragraph is still wrapped too wide - unfortunately. Should fix this. +[^a]: Ooh no, the first line of this + first paragraph is still wrapped + too wide unfortunately. Should fix + this. But this second paragraph is wrapped exactly as expected. Woohooo, From 97af72c7b4c9e6b4b66f59a1c723666f4142f694 Mon Sep 17 00:00:00 2001 From: Kyle King Date: Mon, 26 Jan 2026 21:56:47 -0600 Subject: [PATCH 2/2] test: split out word wrap into test file --- tests/fixtures_wrap.md | 55 +++++++++++++++++++++++++++++++++++++++++ tests/test_word_wrap.py | 40 +++++++++++++++--------------- 2 files changed, 75 insertions(+), 20 deletions(-) create mode 100644 tests/fixtures_wrap.md diff --git a/tests/fixtures_wrap.md b/tests/fixtures_wrap.md new file mode 100644 index 0000000..668d01d --- /dev/null +++ b/tests/fixtures_wrap.md @@ -0,0 +1,55 @@ +wrap at 30 +. +[^short] + +[^short]: This is a shorter footnote that should wrap at thirty characters max. +. +[^short] + +[^short]: This is a shorter + footnote that should + wrap at thirty + characters max. +. + + +wrap at 40 +. +[^a] + +[^a]: Ooh no, the first line of this first paragraph is still wrapped too wide + unfortunately. Should fix this. + + But this second paragraph is wrapped exactly as expected. Woohooo, awesome! +. +[^a] + +[^a]: Ooh no, the first line of this + first paragraph is still wrapped + too wide unfortunately. Should fix + this. + + But this second paragraph is wrapped + exactly as expected. Woohooo, + awesome! +. + + +wrap at 60 +. +[^longer] + +[^longer]: This footnote has a longer wrap length so it can contain more text per line + before wrapping occurs. + + Multiple paragraphs should also respect the wrapping configuration. +. +[^longer] + +[^longer]: This footnote has a longer wrap length so it can + contain more text per line before wrapping + occurs. + + Multiple paragraphs should also respect the wrapping + configuration. +. diff --git a/tests/test_word_wrap.py b/tests/test_word_wrap.py index 6cdcb84..cf871b5 100644 --- a/tests/test_word_wrap.py +++ b/tests/test_word_wrap.py @@ -1,26 +1,26 @@ -import mdformat +from pathlib import Path +import re +from markdown_it.utils import read_fixture_file +import mdformat +import pytest -def test_word_wrap(): - input_text = """\ -[^a] +FIXTURE_PATH = Path(__file__).parent / "fixtures_wrap.md" +fixtures = read_fixture_file(FIXTURE_PATH) -[^a]: Ooh no, the first line of this first paragraph is still wrapped too wide - unfortunately. Should fix this. - But this second paragraph is wrapped exactly as expected. Woohooo, awesome! -""" - expected_output = """\ -[^a] +def _extract_wrap_length(title): + if match := re.search(r"wrap at (\d+)", title): + return int(match.group(1)) + return 40 -[^a]: Ooh no, the first line of this - first paragraph is still wrapped - too wide unfortunately. Should fix - this. - But this second paragraph is wrapped - exactly as expected. Woohooo, - awesome! -""" - output = mdformat.text(input_text, options={"wrap": 40}, extensions={"footnote"}) - assert output == expected_output +@pytest.mark.parametrize( + "line,title,text,expected", + fixtures, + ids=[f[1] for f in fixtures], +) +def test_word_wrap(line, title, text, expected): + wrap_length = _extract_wrap_length(title) + output = mdformat.text(text, options={"wrap": wrap_length}, extensions={"footnote"}) + assert output.rstrip() == expected.rstrip(), output