Skip to content

Commit d3cd3a9

Browse files
authored
🐛 FIX: properly wrap footnotes (#24)
1 parent 1c2b7a2 commit d3cd3a9

3 files changed

Lines changed: 96 additions & 29 deletions

File tree

mdformat_footnote/plugin.py

Lines changed: 21 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -24,19 +24,30 @@ def _footnote_ref_renderer(node: RenderTreeNode, context: RenderContext) -> str:
2424
def _footnote_renderer(node: RenderTreeNode, context: RenderContext) -> str:
2525
first_line = f"[^{node.meta['label']}]:"
2626
indent = " " * 4
27-
elements = []
27+
28+
children = [c for c in node.children if c.type != "footnote_anchor"]
29+
30+
if children and children[0].type == "paragraph":
31+
with context.indented(len(first_line) + 1):
32+
first_element = children[0].render(context)
33+
34+
first_para_first_line, *first_para_rest_lines = first_element.split("\n")
35+
36+
with context.indented(len(indent)):
37+
elements = [child.render(context) for child in children[1:]]
38+
39+
result = first_line + " " + first_para_first_line
40+
if first_para_rest_lines:
41+
result += "\n" + textwrap.indent("\n".join(first_para_rest_lines), indent)
42+
if elements:
43+
result += "\n\n" + textwrap.indent("\n\n".join(elements), indent)
44+
return result
45+
2846
with context.indented(len(indent)):
29-
for child in node.children:
30-
if child.type == "footnote_anchor":
31-
continue
32-
elements.append(child.render(context))
47+
elements = [child.render(context) for child in children]
3348
body = textwrap.indent("\n\n".join(elements), indent)
34-
# if the first body element is a paragraph, we can start on the first line,
35-
# otherwise we start on the second line
36-
if body and node.children and node.children[0].type != "paragraph":
49+
if body:
3750
body = "\n" + body
38-
else:
39-
body = " " + body.lstrip()
4051
return first_line + body
4152

4253

tests/fixtures_wrap.md

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
wrap at 30
2+
.
3+
[^short]
4+
5+
[^short]: This is a shorter footnote that should wrap at thirty characters max.
6+
.
7+
[^short]
8+
9+
[^short]: This is a shorter
10+
footnote that should
11+
wrap at thirty
12+
characters max.
13+
.
14+
15+
16+
wrap at 40
17+
.
18+
[^a]
19+
20+
[^a]: Ooh no, the first line of this first paragraph is still wrapped too wide
21+
unfortunately. Should fix this.
22+
23+
But this second paragraph is wrapped exactly as expected. Woohooo, awesome!
24+
.
25+
[^a]
26+
27+
[^a]: Ooh no, the first line of this
28+
first paragraph is still wrapped
29+
too wide unfortunately. Should fix
30+
this.
31+
32+
But this second paragraph is wrapped
33+
exactly as expected. Woohooo,
34+
awesome!
35+
.
36+
37+
38+
wrap at 60
39+
.
40+
[^longer]
41+
42+
[^longer]: This footnote has a longer wrap length so it can contain more text per line
43+
before wrapping occurs.
44+
45+
Multiple paragraphs should also respect the wrapping configuration.
46+
.
47+
[^longer]
48+
49+
[^longer]: This footnote has a longer wrap length so it can
50+
contain more text per line before wrapping
51+
occurs.
52+
53+
Multiple paragraphs should also respect the wrapping
54+
configuration.
55+
.

tests/test_word_wrap.py

Lines changed: 20 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,25 +1,26 @@
1-
import mdformat
1+
from pathlib import Path
2+
import re
23

4+
from markdown_it.utils import read_fixture_file
5+
import mdformat
6+
import pytest
37

4-
def test_word_wrap():
5-
input_text = """\
6-
[^a]
8+
FIXTURE_PATH = Path(__file__).parent / "fixtures_wrap.md"
9+
fixtures = read_fixture_file(FIXTURE_PATH)
710

8-
[^a]: Ooh no, the first line of this first paragraph is still wrapped too wide
9-
unfortunately. Should fix this.
1011

11-
But this second paragraph is wrapped exactly as expected. Woohooo, awesome!
12-
"""
13-
expected_output = """\
14-
[^a]
12+
def _extract_wrap_length(title):
13+
if match := re.search(r"wrap at (\d+)", title):
14+
return int(match.group(1))
15+
return 40
1516

16-
[^a]: Ooh no, the first line of this first
17-
paragraph is still wrapped too wide
18-
unfortunately. Should fix this.
1917

20-
But this second paragraph is wrapped
21-
exactly as expected. Woohooo,
22-
awesome!
23-
"""
24-
output = mdformat.text(input_text, options={"wrap": 40}, extensions={"footnote"})
25-
assert output == expected_output
18+
@pytest.mark.parametrize(
19+
"line,title,text,expected",
20+
fixtures,
21+
ids=[f[1] for f in fixtures],
22+
)
23+
def test_word_wrap(line, title, text, expected):
24+
wrap_length = _extract_wrap_length(title)
25+
output = mdformat.text(text, options={"wrap": wrap_length}, extensions={"footnote"})
26+
assert output.rstrip() == expected.rstrip(), output

0 commit comments

Comments
 (0)