From b7d80370f813e909f998bfbc978548a5a7b9ab8f Mon Sep 17 00:00:00 2001 From: Kyle King Date: Sun, 19 Oct 2025 08:42:30 -0600 Subject: [PATCH 1/4] fix(#58): fix word wrap on definition lists --- mdformat_mkdocs/_normalize_list.py | 12 ++++++----- tests/format/test_wrap.py | 34 ++++++++++++++++++++++++++++++ 2 files changed, 41 insertions(+), 5 deletions(-) diff --git a/mdformat_mkdocs/_normalize_list.py b/mdformat_mkdocs/_normalize_list.py index b50fa1c..46cdba5 100644 --- a/mdformat_mkdocs/_normalize_list.py +++ b/mdformat_mkdocs/_normalize_list.py @@ -436,6 +436,11 @@ def parse_text(*, text: str, inc_numbers: bool, use_sem_break: bool) -> ParsedTe # Outputs string result +def _strip_filler(text: str) -> str: + """Remove filler characters inserted during wrapping.""" + return text.replace(f"{FILLER_CHAR} ", "").replace(FILLER_CHAR, "") + + def _join(*, new_lines: list[tuple[str, str]]) -> str: """Join ParsedText into a single string representation.""" new_indents, new_contents = unzip(new_lines) @@ -443,10 +448,7 @@ def _join(*, new_lines: list[tuple[str, str]]) -> str: new_indents_iter = new_indents # Remove filler characters added by inline formatting for 'wrap' - new_contents_iter = ( - content.replace(f"{FILLER_CHAR} ", "").replace(FILLER_CHAR, "").rstrip() - for content in new_contents - ) + new_contents_iter = (_strip_filler(content).rstrip() for content in new_contents) return "".join( f"{new_indent}{new_content}{EOL}" @@ -470,7 +472,7 @@ def normalize_list( if node.level > 1: # Note: this function is called recursively, # so only process the top-level item - return text + return _strip_filler(text) # Retrieve user-options inc_numbers = bool(get_conf(context.options, "number")) diff --git a/tests/format/test_wrap.py b/tests/format/test_wrap.py index 61ffe84..92ddf6c 100644 --- a/tests/format/test_wrap.py +++ b/tests/format/test_wrap.py @@ -1,3 +1,5 @@ +from textwrap import dedent + import mdformat import pytest @@ -221,6 +223,28 @@ def gcd(a, b): /// """ +DEF_LIST_WITH_NESTED_WRAP = dedent( + """\ + term + + : Definition starts with a paragraph, followed by an unordered list: + + - Foo bar foo bar foo bar foo bar foo bar foo bar foo bar foo bar foo bar + foo bar foo bar foo bar foo bar. + """, +) + +DEF_LIST_WITH_NESTED_WRAP_EXPECTED = dedent( + """\ + term + + : Definition starts with a paragraph, followed by an unordered list: + + - Foo bar foo bar foo bar foo bar foo bar foo bar foo bar foo bar foo bar + foo bar foo bar foo bar foo bar. + """, +) + @pytest.mark.parametrize( ("text", "expected", "align_lists", "wrap"), @@ -255,3 +279,13 @@ def test_wrap(text: str, expected: str, align_lists: bool, wrap: int): ) print_text(output, expected) assert output.lstrip() == expected.lstrip() + + +def test_definition_list_wrap_with_gfm(): + output = mdformat.text( + DEF_LIST_WITH_NESTED_WRAP, + options={"wrap": 80}, + extensions={"mkdocs", "gfm"}, + ) + print_text(output, DEF_LIST_WITH_NESTED_WRAP_EXPECTED) + assert output == DEF_LIST_WITH_NESTED_WRAP_EXPECTED From c4585544e74dce2122a5fcef2f3f2f5717bbc655 Mon Sep 17 00:00:00 2001 From: Kyle King Date: Sun, 26 Oct 2025 19:19:44 -0400 Subject: [PATCH 2/4] feat: two-space indent is possible, but not consistent See https://raw.githubusercontent.com/hukkin/mdformat/refs/heads/master/tests/data/wrap_width_50.md --- mdformat_mkdocs/_normalize_list.py | 38 +++++++++++++++++++++++++++++- tests/format/test_wrap.py | 9 ++++--- 2 files changed, 43 insertions(+), 4 deletions(-) diff --git a/mdformat_mkdocs/_normalize_list.py b/mdformat_mkdocs/_normalize_list.py index 46cdba5..5396310 100644 --- a/mdformat_mkdocs/_normalize_list.py +++ b/mdformat_mkdocs/_normalize_list.py @@ -456,6 +456,40 @@ def _join(*, new_lines: list[tuple[str, str]]) -> str: ) +def _adjust_wrapped_indent(text: str) -> str: + """Add 2 spaces to wrapped continuation lines (lines without list markers). + + This ensures wrapped content is indented 4 spaces (nested content alignment) + instead of 2 spaces (text alignment after the marker). + """ + lines = text.split(EOL) + adjusted_lines = [] + in_code_block = False + + for line in lines: + stripped = line.lstrip() + + # Check if line is a list marker + # A list item starts with "- ", "* ", or a number followed by ". " + is_list_marker = bool( + stripped and + RE_LIST_ITEM.match(stripped), + ) + + # Don't adjust lines inside code blocks, list marker lines, or empty lines + if in_code_block or is_list_marker or not line or line[0] != " ": + adjusted_lines.append(line) + else: + # Add 2 spaces to text continuation lines + adjusted_lines.append(" " + line) + + # Track code blocks AFTER processing the line (including when they start on a list marker line) + if "```" in stripped: + in_code_block = not in_code_block + + return EOL.join(adjusted_lines) + + @rstrip_result def normalize_list( text: str, @@ -472,7 +506,9 @@ def normalize_list( if node.level > 1: # Note: this function is called recursively, # so only process the top-level item - return _strip_filler(text) + # But first adjust indentation for wrapped lines + stripped = _strip_filler(text) + return _adjust_wrapped_indent(stripped) # Retrieve user-options inc_numbers = bool(get_conf(context.options, "number")) diff --git a/tests/format/test_wrap.py b/tests/format/test_wrap.py index 92ddf6c..f367403 100644 --- a/tests/format/test_wrap.py +++ b/tests/format/test_wrap.py @@ -229,8 +229,9 @@ def gcd(a, b): : Definition starts with a paragraph, followed by an unordered list: - - Foo bar foo bar foo bar foo bar foo bar foo bar foo bar foo bar foo bar - foo bar foo bar foo bar foo bar. + - Foo bar foo bar foo bar foo bar foo bar foo bar foo bar foo bar foo bar foo bar foo bar foo bar foo bar. + + - (3) bar foo bar foo bar foo bar foo bar foo bar foo bar foo bar foo bar foo bar foo bar foo bar foo bar. """, ) @@ -241,7 +242,9 @@ def gcd(a, b): : Definition starts with a paragraph, followed by an unordered list: - Foo bar foo bar foo bar foo bar foo bar foo bar foo bar foo bar foo bar - foo bar foo bar foo bar foo bar. + foo bar foo bar foo bar foo bar. + - (3) bar foo bar foo bar foo bar foo bar foo bar foo bar foo bar foo + bar foo bar foo bar foo bar foo bar. """, ) From ed513a720af5b7114a915ba09b246794659486fc Mon Sep 17 00:00:00 2001 From: Kyle King Date: Sun, 26 Oct 2025 19:19:49 -0400 Subject: [PATCH 3/4] Revert "feat: two-space indent is possible, but not consistent" This reverts commit c4585544e74dce2122a5fcef2f3f2f5717bbc655. --- mdformat_mkdocs/_normalize_list.py | 38 +----------------------------- tests/format/test_wrap.py | 9 +++---- 2 files changed, 4 insertions(+), 43 deletions(-) diff --git a/mdformat_mkdocs/_normalize_list.py b/mdformat_mkdocs/_normalize_list.py index 5396310..46cdba5 100644 --- a/mdformat_mkdocs/_normalize_list.py +++ b/mdformat_mkdocs/_normalize_list.py @@ -456,40 +456,6 @@ def _join(*, new_lines: list[tuple[str, str]]) -> str: ) -def _adjust_wrapped_indent(text: str) -> str: - """Add 2 spaces to wrapped continuation lines (lines without list markers). - - This ensures wrapped content is indented 4 spaces (nested content alignment) - instead of 2 spaces (text alignment after the marker). - """ - lines = text.split(EOL) - adjusted_lines = [] - in_code_block = False - - for line in lines: - stripped = line.lstrip() - - # Check if line is a list marker - # A list item starts with "- ", "* ", or a number followed by ". " - is_list_marker = bool( - stripped and - RE_LIST_ITEM.match(stripped), - ) - - # Don't adjust lines inside code blocks, list marker lines, or empty lines - if in_code_block or is_list_marker or not line or line[0] != " ": - adjusted_lines.append(line) - else: - # Add 2 spaces to text continuation lines - adjusted_lines.append(" " + line) - - # Track code blocks AFTER processing the line (including when they start on a list marker line) - if "```" in stripped: - in_code_block = not in_code_block - - return EOL.join(adjusted_lines) - - @rstrip_result def normalize_list( text: str, @@ -506,9 +472,7 @@ def normalize_list( if node.level > 1: # Note: this function is called recursively, # so only process the top-level item - # But first adjust indentation for wrapped lines - stripped = _strip_filler(text) - return _adjust_wrapped_indent(stripped) + return _strip_filler(text) # Retrieve user-options inc_numbers = bool(get_conf(context.options, "number")) diff --git a/tests/format/test_wrap.py b/tests/format/test_wrap.py index f367403..92ddf6c 100644 --- a/tests/format/test_wrap.py +++ b/tests/format/test_wrap.py @@ -229,9 +229,8 @@ def gcd(a, b): : Definition starts with a paragraph, followed by an unordered list: - - Foo bar foo bar foo bar foo bar foo bar foo bar foo bar foo bar foo bar foo bar foo bar foo bar foo bar. - - - (3) bar foo bar foo bar foo bar foo bar foo bar foo bar foo bar foo bar foo bar foo bar foo bar foo bar. + - Foo bar foo bar foo bar foo bar foo bar foo bar foo bar foo bar foo bar + foo bar foo bar foo bar foo bar. """, ) @@ -242,9 +241,7 @@ def gcd(a, b): : Definition starts with a paragraph, followed by an unordered list: - Foo bar foo bar foo bar foo bar foo bar foo bar foo bar foo bar foo bar - foo bar foo bar foo bar foo bar. - - (3) bar foo bar foo bar foo bar foo bar foo bar foo bar foo bar foo - bar foo bar foo bar foo bar foo bar. + foo bar foo bar foo bar foo bar. """, ) From e6f4213020e48f9b54fdf24cc56c4de7c690e695 Mon Sep 17 00:00:00 2001 From: Kyle King Date: Sun, 26 Oct 2025 19:27:15 -0400 Subject: [PATCH 4/4] test: verify multiple depth indentation --- tests/format/test_wrap.py | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/tests/format/test_wrap.py b/tests/format/test_wrap.py index 92ddf6c..04d04cb 100644 --- a/tests/format/test_wrap.py +++ b/tests/format/test_wrap.py @@ -229,8 +229,12 @@ def gcd(a, b): : Definition starts with a paragraph, followed by an unordered list: - - Foo bar foo bar foo bar foo bar foo bar foo bar foo bar foo bar foo bar - foo bar foo bar foo bar foo bar. + - Foo bar foo bar foo bar foo bar foo bar foo bar foo bar foo bar foo bar foo bar foo bar foo bar foo bar. + + - (3) bar foo bar foo bar foo bar foo bar foo bar foo bar foo bar foo bar foo bar foo bar foo bar foo bar. + + - foo bar foo bar foo bar foo bar foo bar foo bar foo bar foo bar foo bar + (split) foo bar foo bar foo bar foo bar. """, ) @@ -242,6 +246,12 @@ def gcd(a, b): - Foo bar foo bar foo bar foo bar foo bar foo bar foo bar foo bar foo bar foo bar foo bar foo bar foo bar. + + - (3) bar foo bar foo bar foo bar foo bar foo bar foo bar foo bar foo bar + foo bar foo bar foo bar foo bar. + + - foo bar foo bar foo bar foo bar foo bar foo bar foo bar foo bar foo + bar (split) foo bar foo bar foo bar foo bar. """, )