Skip to content

Commit 2c5562a

Browse files
jdmonacoclaude
andcommitted
Add softbreak renderer, migrate to hatchling (v0.3.0)
Convert soft breaks to hard breaks (backslash + newline) so paragraph line breaks render as visible line breaks. Migrate build system from flit_core to hatchling for proper editable install support. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
1 parent a5589db commit 2c5562a

7 files changed

Lines changed: 85 additions & 11 deletions

File tree

mdformat_space_control/__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 space control: EditorConfig indentation, tight lists, frontmatter spacing, and wikilinks."""
22

3-
__version__ = "0.2.0"
3+
__version__ = "0.3.0"
44

55
from .config import (
66
get_current_file,

mdformat_space_control/plugin.py

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -222,6 +222,16 @@ def _render_wikilink(node: RenderTreeNode, context: RenderContext) -> str:
222222
return node.content
223223

224224

225+
def _render_softbreak(node: RenderTreeNode, context: RenderContext) -> str:
226+
"""Render soft breaks with terminal backslash.
227+
228+
Converts soft breaks (plain newlines within paragraphs) to hard breaks
229+
(backslash + newline) to preserve visible line-break rendering and avoid
230+
relying on trailing whitespace.
231+
"""
232+
return "\\" + "\n"
233+
234+
225235
# A mapping from syntax tree node type to a function that renders it.
226236
# This can be used to overwrite renderer functions of existing syntax
227237
# or add support for new syntax.
@@ -230,6 +240,7 @@ def _render_wikilink(node: RenderTreeNode, context: RenderContext) -> str:
230240
"bullet_list": _render_bullet_list,
231241
"ordered_list": _render_ordered_list,
232242
"wikilink": _render_wikilink,
243+
"softbreak": _render_softbreak,
233244
}
234245

235246

pyproject.toml

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,11 @@
11
[build-system]
2-
requires = ["flit_core >=3.2.0,<4"]
3-
build-backend = "flit_core.buildapi"
2+
requires = ["hatchling"]
3+
build-backend = "hatchling.build"
44

55
[project]
66
name = "mdformat_space_control"
7+
version = "0.3.0"
8+
description = "An mdformat plugin for space control: EditorConfig indentation, tight lists, frontmatter spacing, and wikilinks."
79
authors = [
810
{ name = "Joseph Monaco", email = "joe@selfmotion.net" },
911
]
@@ -26,7 +28,6 @@ dependencies = [
2628
"mdformat>=0.7.0",
2729
"editorconfig>=0.12.0",
2830
]
29-
dynamic = ["version", "description"]
3031

3132
[project.optional-dependencies]
3233
test = [
@@ -42,8 +43,7 @@ Homepage = "https://github.com/jdmonaco/mdformat-space-control"
4243
[project.entry-points."mdformat.parser_extension"]
4344
space_control = "mdformat_space_control"
4445

45-
[tool.flit.sdist]
46-
include = []
46+
[tool.hatch.build.targets.sdist]
4747
exclude = [".github/", "tests/"]
4848

4949
[tool.pytest.ini_options]

tests/fixtures.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -219,7 +219,7 @@ checkbox with continuation
219219
continuation text
220220
- [x] Another task
221221
.
222-
- [ ] Task with
222+
- [ ] Task with\
223223
continuation text
224224
- [x] Another task
225225
.

tests/test_editorconfig.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -81,7 +81,7 @@ def test_bullet_list_continuation(self, temp_project):
8181
- Item 2
8282
"""
8383
expected = """\
84-
- Item 1
84+
- Item 1\\
8585
with continuation
8686
- Item 2
8787
"""

tests/test_integration.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -104,11 +104,11 @@ def test_tab_indent_ordered_list_continuation(self):
104104
continuation lines
105105
"""
106106
expected = """\
107-
1. First item with
107+
1. First item with\\
108108
\tcontinuation line
109109
2. Second item
110-
3. Third item with
111-
\tmultiple
110+
3. Third item with\\
111+
\tmultiple\\
112112
\tcontinuation lines
113113
"""
114114
with tempfile.TemporaryDirectory() as tmpdir:

tests/test_softbreak.py

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
"""Tests for soft break to hard break conversion."""
2+
3+
import mdformat
4+
5+
6+
class TestSoftBreakConversion:
7+
"""Tests for converting soft breaks to hard breaks (backslash + newline)."""
8+
9+
def test_soft_break_gets_backslash(self):
10+
"""Plain newline within a paragraph should become backslash + newline."""
11+
input_text = "Line one\nLine two.\n"
12+
expected = "Line one\\\nLine two.\n"
13+
result = mdformat.text(input_text, extensions={"space_control"})
14+
assert result == expected
15+
16+
def test_multiple_soft_breaks(self):
17+
"""Multiple soft breaks in a paragraph should all get backslashes."""
18+
input_text = "Line one\nLine two\nLine three.\n"
19+
expected = "Line one\\\nLine two\\\nLine three.\n"
20+
result = mdformat.text(input_text, extensions={"space_control"})
21+
assert result == expected
22+
23+
def test_bold_label_lines(self):
24+
"""AI-generated bold label lines should preserve line breaks."""
25+
input_text = "**Date**: January 30\n**Time**: 2:00 PM\n**Location**: Room 101\n"
26+
expected = "**Date**: January 30\\\n**Time**: 2:00 PM\\\n**Location**: Room 101\n"
27+
result = mdformat.text(input_text, extensions={"space_control"})
28+
assert result == expected
29+
30+
def test_paragraph_break_unaffected(self):
31+
"""Double newline (paragraph break) should not be affected."""
32+
input_text = "First paragraph.\n\nSecond paragraph.\n"
33+
expected = "First paragraph.\n\nSecond paragraph.\n"
34+
result = mdformat.text(input_text, extensions={"space_control"})
35+
assert result == expected
36+
37+
def test_existing_hard_break_preserved(self):
38+
"""Lines already ending with backslash should remain correct."""
39+
input_text = "Line one\\\nLine two.\n"
40+
expected = "Line one\\\nLine two.\n"
41+
result = mdformat.text(input_text, extensions={"space_control"})
42+
assert result == expected
43+
44+
def test_code_block_newlines_unaffected(self):
45+
"""Newlines inside fenced code blocks should not get backslashes."""
46+
input_text = "```\nline one\nline two\n```\n"
47+
expected = "```\nline one\nline two\n```\n"
48+
result = mdformat.text(input_text, extensions={"space_control"})
49+
assert result == expected
50+
51+
def test_single_line_paragraph_unaffected(self):
52+
"""A single-line paragraph should not be modified."""
53+
input_text = "Just one line.\n"
54+
expected = "Just one line.\n"
55+
result = mdformat.text(input_text, extensions={"space_control"})
56+
assert result == expected
57+
58+
def test_soft_break_in_list_item(self):
59+
"""Soft breaks within list items should get backslashes."""
60+
input_text = "- Line one\n Line two\n"
61+
expected = "- Line one\\\n Line two\n"
62+
result = mdformat.text(input_text, extensions={"space_control"})
63+
assert result == expected

0 commit comments

Comments
 (0)