Skip to content

Commit 457a0fc

Browse files
authored
Merge pull request #11 from KyleKing/fix-9-wrap
2 parents da2c6e9 + 2867933 commit 457a0fc

File tree

5 files changed

+178
-3
lines changed

5 files changed

+178
-3
lines changed

mdformat_mkdocs/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
"""An mdformat plugin for mkdocs."""
22

3-
__version__ = "1.0.4rc1"
3+
__version__ = "1.0.4rc2"
44

55
from .plugin import ( # noqa: F401
66
POSTPROCESSORS,

mdformat_mkdocs/plugin.py

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,12 @@
1717
1818
"""
1919

20+
FILLER_CHAR = "𝕏"
21+
"""A spacer that is inserted and then removed to ensure proper word wrap."""
22+
23+
FILLER = FILLER_CHAR * (_MKDOCS_INDENT_COUNT - 2) # `mdformat` default is two spaces
24+
"""A spacer that is inserted and then removed to ensure proper word wrap."""
25+
2026

2127
def add_cli_options(parser: argparse.ArgumentParser) -> None:
2228
"""Add options to the mdformat CLI, to be stored in `mdit.options["mdformat"]`."""
@@ -85,10 +91,62 @@ def _normalize_list(text: str, node: RenderTreeNode, context: RenderContext) ->
8591
if _ALIGN_SEMANTIC_BREAKS_IN_LISTS and not list_match and is_semantic_indent:
8692
removed_indents = -1 if is_numbered else -2
8793
new_indent = new_indent[:removed_indents]
94+
new_line = new_line.replace(f"{FILLER_CHAR} ", "").replace(FILLER_CHAR, "")
8895
rendered += f"{new_indent}{new_line.strip()}{eol}"
8996
return rendered.rstrip()
9097

9198

99+
def _postprocess_inline(text: str, node: RenderTreeNode, context: RenderContext) -> str:
100+
"""Postprocess inline tokens.
101+
102+
Fix word wrap for lists to account for the change in indentation.
103+
We fool word wrap by prefixing an unwrappable dummy string of the same length.
104+
This prefix needs to be later removed (in `_list_item_renderer`).
105+
106+
Adapted from: https://github.com/hukkin/mdformat-gfm/blob/cf316a121b6cf35cbff7b0ad6e171f287803f8cb/src/mdformat_gfm/plugin.py#L86-L111
107+
108+
"""
109+
if not context.do_wrap:
110+
return text
111+
wrap_mode = context.options["mdformat"]["wrap"]
112+
if (
113+
not isinstance(wrap_mode, int)
114+
or FILLER_CHAR in text # noqa: W503
115+
or (node.parent and node.parent.type != "paragraph") # noqa: W503
116+
or (node.parent.parent and node.parent.parent.type != "list_item") # noqa: W503
117+
):
118+
return text
119+
120+
_counter = -1
121+
parent = node.parent
122+
while parent and parent.type == "paragraph":
123+
parent = parent.parent
124+
_counter += 1
125+
indent_count = max(_counter, 0)
126+
127+
soft_break = "\x00"
128+
text = text.lstrip(soft_break).lstrip()
129+
filler = (FILLER * indent_count)[:-1] if indent_count else ""
130+
newline_filler = filler + FILLER if indent_count else FILLER[:-1]
131+
if len(text) > wrap_mode:
132+
indent_length = _MKDOCS_INDENT_COUNT * indent_count
133+
wrapped_length = -123
134+
words = []
135+
for word in text.split(soft_break):
136+
next_length = wrapped_length + len(word)
137+
if not words:
138+
words = [filler, word]
139+
wrapped_length = indent_length + len(word)
140+
elif next_length > wrap_mode:
141+
words += [word, newline_filler]
142+
wrapped_length = indent_length + len(word)
143+
else:
144+
words.append(word)
145+
wrapped_length = next_length + 1
146+
return soft_break.join(_w for _w in words if _w)
147+
return f"{filler}{soft_break}{text}" if filler else text
148+
149+
92150
# A mapping from `RenderTreeNode.type` to a `Render` function that can
93151
# render the given `RenderTreeNode` type. These override the default
94152
# `Render` funcs defined in `mdformat.renderer.DEFAULT_RENDERERS`.
@@ -102,6 +160,7 @@ def _normalize_list(text: str, node: RenderTreeNode, context: RenderContext) ->
102160
POSTPROCESSORS: Mapping[str, Postprocess] = {
103161
"bullet_list": _normalize_list,
104162
"ordered_list": _normalize_list,
163+
"inline": _postprocess_inline,
105164
}
106165

107166
# See: https://github.com/executablebooks/mdformat/blob/5d9b573ce33bae219087984dd148894c774f41d4/src/mdformat/plugins.py

tests/test_align_semantic_breaks_in_lists.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414
def test_align_semantic_breaks_in_lists(line, title, text, expected):
1515
output = mdformat.text(
1616
text,
17-
options={"align_semantic_breaks_in_lists": True},
17+
options={"align_semantic_breaks_in_lists": True, "wrap": "keep"},
1818
extensions={"mkdocs"},
1919
)
2020
assert output.rstrip() == expected.rstrip()

tests/test_fixtures.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,5 +12,5 @@
1212
"line,title,text,expected", fixtures, ids=[f[1] for f in fixtures]
1313
)
1414
def test_fixtures(line, title, text, expected):
15-
output = mdformat.text(text, extensions={"mkdocs"})
15+
output = mdformat.text(text, extensions={"mkdocs"}, options={"wrap": "keep"})
1616
assert output.rstrip() == expected.rstrip()

tests/test_wrap.py

Lines changed: 116 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,116 @@
1+
import mdformat
2+
import pytest
3+
4+
# Note: indented text that starts with a number is parsed as the start of a numbered list
5+
6+
CASE_1 = """
7+
# Content
8+
9+
- Test Testing Test Testing Test Testing Test Testing Test Testing
10+
Test Testing
11+
- Test Testing Test Testing Test Testing Test Testing Test Testing
12+
Test Testing Test Testing Test Testing Test Testing Test Testing
13+
Test Testing
14+
15+
1. Test Testing Test Testing Test Testing Test Testing Test Testing
16+
Test Testing
17+
1. Test Testing Test Testing Test Testing Test Testing Test Testing
18+
Test Testing Test Testing Test Testing Test Testing Test Testing
19+
Test Testing
20+
"""
21+
22+
CASE_1_FALSE_40 = """
23+
# Content
24+
25+
- Test Testing Test Testing Test Testing
26+
Test Testing Test Testing Test
27+
Testing
28+
- Test Testing Test Testing Test
29+
Testing Test Testing Test Testing
30+
Test Testing Test Testing Test
31+
Testing Test Testing Test Testing
32+
Test Testing
33+
34+
1. Test Testing Test Testing Test
35+
Testing Test Testing Test Testing
36+
Test Testing
37+
1. Test Testing Test Testing Test
38+
Testing Test Testing Test Testing
39+
Test Testing Test Testing Test
40+
Testing Test Testing Test Testing
41+
Test Testing
42+
"""
43+
44+
CASE_1_FALSE_80 = """
45+
# Content
46+
47+
- Test Testing Test Testing Test Testing Test Testing Test Testing Test Testing
48+
- Test Testing Test Testing Test Testing Test Testing Test Testing Test
49+
Testing Test Testing Test Testing Test Testing Test Testing Test Testing
50+
51+
1. Test Testing Test Testing Test Testing Test Testing Test Testing Test Testing
52+
1. Test Testing Test Testing Test Testing Test Testing Test Testing Test Testing
53+
Test Testing Test Testing Test Testing Test Testing Test Testing
54+
"""
55+
56+
CASE_1_TRUE_40 = """
57+
# Content
58+
59+
- Test Testing Test Testing Test Testing
60+
Test Testing Test Testing Test
61+
Testing
62+
- Test Testing Test Testing Test
63+
Testing Test Testing Test Testing
64+
Test Testing Test Testing Test
65+
Testing Test Testing Test Testing
66+
Test Testing
67+
68+
1. Test Testing Test Testing Test
69+
Testing Test Testing Test Testing
70+
Test Testing
71+
1. Test Testing Test Testing Test
72+
Testing Test Testing Test Testing
73+
Test Testing Test Testing Test
74+
Testing Test Testing Test Testing
75+
Test Testing
76+
"""
77+
78+
CASE_1_TRUE_80 = """
79+
# Content
80+
81+
- Test Testing Test Testing Test Testing Test Testing Test Testing Test Testing
82+
- Test Testing Test Testing Test Testing Test Testing Test Testing Test
83+
Testing Test Testing Test Testing Test Testing Test Testing Test Testing
84+
85+
1. Test Testing Test Testing Test Testing Test Testing Test Testing Test Testing
86+
1. Test Testing Test Testing Test Testing Test Testing Test Testing Test Testing
87+
Test Testing Test Testing Test Testing Test Testing Test Testing
88+
"""
89+
90+
91+
@pytest.mark.parametrize(
92+
"text,expected,align_lists,wrap",
93+
[
94+
(CASE_1, CASE_1_FALSE_40, False, 40),
95+
(CASE_1, CASE_1_FALSE_80, False, 80),
96+
(CASE_1, CASE_1_TRUE_40, True, 40),
97+
(CASE_1, CASE_1_TRUE_80, True, 80),
98+
],
99+
ids=[
100+
"CASE_1_FALSE_40",
101+
"CASE_1_FALSE_80",
102+
"CASE_1_TRUE_40",
103+
"CASE_1_TRUE_80",
104+
],
105+
)
106+
def test_wrap(text: str, expected: str, align_lists: bool, wrap: int):
107+
output = mdformat.text(
108+
text,
109+
options={"align_semantic_breaks_in_lists": align_lists, "wrap": wrap},
110+
extensions={"mkdocs"},
111+
)
112+
113+
print(output.strip())
114+
print("-- Expected --")
115+
print(expected.strip())
116+
assert output.strip() == expected.strip()

0 commit comments

Comments
 (0)