flowmark 0.6.2__tar.gz → 0.6.3__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (146) hide show
  1. {flowmark-0.6.2 → flowmark-0.6.3}/PKG-INFO +1 -1
  2. {flowmark-0.6.2 → flowmark-0.6.3}/src/flowmark/formats/flowmark_markdown.py +40 -2
  3. {flowmark-0.6.2 → flowmark-0.6.3}/tests/test_fenced_code_blocks.py +130 -0
  4. flowmark-0.6.3/tests/test_strikethrough.py +100 -0
  5. {flowmark-0.6.2 → flowmark-0.6.3}/tests/test_tag_formatting.py +5 -7
  6. {flowmark-0.6.2 → flowmark-0.6.3}/tests/testdocs/testdoc.expected.auto.md +40 -0
  7. {flowmark-0.6.2 → flowmark-0.6.3}/tests/testdocs/testdoc.expected.cleaned.md +40 -0
  8. {flowmark-0.6.2 → flowmark-0.6.3}/tests/testdocs/testdoc.expected.plain.md +38 -0
  9. {flowmark-0.6.2 → flowmark-0.6.3}/tests/testdocs/testdoc.expected.semantic.md +40 -0
  10. {flowmark-0.6.2 → flowmark-0.6.3}/tests/testdocs/testdoc.orig.md +34 -0
  11. {flowmark-0.6.2 → flowmark-0.6.3}/.copier-answers.yml +0 -0
  12. {flowmark-0.6.2 → flowmark-0.6.3}/.cursor/rules/automatic-shortcut-triggers.mdc +0 -0
  13. {flowmark-0.6.2 → flowmark-0.6.3}/.cursor/rules/backward-compatibility-rules.mdc +0 -0
  14. {flowmark-0.6.2 → flowmark-0.6.3}/.cursor/rules/convex-rules.mdc +0 -0
  15. {flowmark-0.6.2 → flowmark-0.6.3}/.cursor/rules/flowmark.mdc +0 -0
  16. {flowmark-0.6.2 → flowmark-0.6.3}/.cursor/rules/general-coding-rules.mdc +0 -0
  17. {flowmark-0.6.2 → flowmark-0.6.3}/.cursor/rules/general-comment-rules.mdc +0 -0
  18. {flowmark-0.6.2 → flowmark-0.6.3}/.cursor/rules/general-eng-assistant-rules.mdc +0 -0
  19. {flowmark-0.6.2 → flowmark-0.6.3}/.cursor/rules/general-style-rules.mdc +0 -0
  20. {flowmark-0.6.2 → flowmark-0.6.3}/.cursor/rules/general-testing-rules.mdc +0 -0
  21. {flowmark-0.6.2 → flowmark-0.6.3}/.cursor/rules/python-rules-opinionated.mdc +0 -0
  22. {flowmark-0.6.2 → flowmark-0.6.3}/.cursor/rules/python-rules.mdc +0 -0
  23. {flowmark-0.6.2 → flowmark-0.6.3}/.cursor/rules/tool-development-rules.mdc +0 -0
  24. {flowmark-0.6.2 → flowmark-0.6.3}/.cursor/rules/typescript-cli-tool-rules.mdc +0 -0
  25. {flowmark-0.6.2 → flowmark-0.6.3}/.cursor/rules/typescript-rules.mdc +0 -0
  26. {flowmark-0.6.2 → flowmark-0.6.3}/.github/workflows/ci.yml +0 -0
  27. {flowmark-0.6.2 → flowmark-0.6.3}/.github/workflows/publish.yml +0 -0
  28. {flowmark-0.6.2 → flowmark-0.6.3}/.gitignore +0 -0
  29. {flowmark-0.6.2 → flowmark-0.6.3}/.speculate/copier-answers.yml +0 -0
  30. {flowmark-0.6.2 → flowmark-0.6.3}/.speculate/settings.yml +0 -0
  31. {flowmark-0.6.2 → flowmark-0.6.3}/.vscode/settings.json +0 -0
  32. {flowmark-0.6.2 → flowmark-0.6.3}/LICENSE +0 -0
  33. {flowmark-0.6.2 → flowmark-0.6.3}/Makefile +0 -0
  34. {flowmark-0.6.2 → flowmark-0.6.3}/README.md +0 -0
  35. {flowmark-0.6.2 → flowmark-0.6.3}/devtools/benchmark.py +0 -0
  36. {flowmark-0.6.2 → flowmark-0.6.3}/devtools/lint.py +0 -0
  37. {flowmark-0.6.2 → flowmark-0.6.3}/docs/development.md +0 -0
  38. {flowmark-0.6.2 → flowmark-0.6.3}/docs/development.npm.sample.md +0 -0
  39. {flowmark-0.6.2 → flowmark-0.6.3}/docs/docs-overview.md +0 -0
  40. {flowmark-0.6.2 → flowmark-0.6.3}/docs/general/agent-guidelines/general-tdd-guidelines.md +0 -0
  41. {flowmark-0.6.2 → flowmark-0.6.3}/docs/general/agent-guidelines/golden-testing-guidelines.md +0 -0
  42. {flowmark-0.6.2 → flowmark-0.6.3}/docs/general/agent-guidelines/typescript-dependency-injection-guidelines.md +0 -0
  43. {flowmark-0.6.2 → flowmark-0.6.3}/docs/general/agent-guidelines/typescript-testing-guidelines.md +0 -0
  44. {flowmark-0.6.2 → flowmark-0.6.3}/docs/general/agent-rules/automatic-shortcut-triggers.md +0 -0
  45. {flowmark-0.6.2 → flowmark-0.6.3}/docs/general/agent-rules/backward-compatibility-rules.md +0 -0
  46. {flowmark-0.6.2 → flowmark-0.6.3}/docs/general/agent-rules/convex-rules.md +0 -0
  47. {flowmark-0.6.2 → flowmark-0.6.3}/docs/general/agent-rules/general-coding-rules.md +0 -0
  48. {flowmark-0.6.2 → flowmark-0.6.3}/docs/general/agent-rules/general-comment-rules.md +0 -0
  49. {flowmark-0.6.2 → flowmark-0.6.3}/docs/general/agent-rules/general-eng-assistant-rules.md +0 -0
  50. {flowmark-0.6.2 → flowmark-0.6.3}/docs/general/agent-rules/general-style-rules.md +0 -0
  51. {flowmark-0.6.2 → flowmark-0.6.3}/docs/general/agent-rules/general-testing-rules.md +0 -0
  52. {flowmark-0.6.2 → flowmark-0.6.3}/docs/general/agent-rules/python-rules-opinionated.md +0 -0
  53. {flowmark-0.6.2 → flowmark-0.6.3}/docs/general/agent-rules/python-rules.md +0 -0
  54. {flowmark-0.6.2 → flowmark-0.6.3}/docs/general/agent-rules/tool-development-rules.md +0 -0
  55. {flowmark-0.6.2 → flowmark-0.6.3}/docs/general/agent-rules/typescript-cli-tool-rules.md +0 -0
  56. {flowmark-0.6.2 → flowmark-0.6.3}/docs/general/agent-rules/typescript-rules.md +0 -0
  57. {flowmark-0.6.2 → flowmark-0.6.3}/docs/general/agent-setup/shortcut:setup-beads.md +0 -0
  58. {flowmark-0.6.2 → flowmark-0.6.3}/docs/general/agent-setup/shortcut:setup-github-cli.md +0 -0
  59. {flowmark-0.6.2 → flowmark-0.6.3}/docs/general/agent-shortcuts/shortcut:cleanup-all.md +0 -0
  60. {flowmark-0.6.2 → flowmark-0.6.3}/docs/general/agent-shortcuts/shortcut:cleanup-remove-trivial-tests.md +0 -0
  61. {flowmark-0.6.2 → flowmark-0.6.3}/docs/general/agent-shortcuts/shortcut:cleanup-update-docstrings.md +0 -0
  62. {flowmark-0.6.2 → flowmark-0.6.3}/docs/general/agent-shortcuts/shortcut:coding-spike.md +0 -0
  63. {flowmark-0.6.2 → flowmark-0.6.3}/docs/general/agent-shortcuts/shortcut:commit-code.md +0 -0
  64. {flowmark-0.6.2 → flowmark-0.6.3}/docs/general/agent-shortcuts/shortcut:create-or-update-pr-with-validation-plan.md +0 -0
  65. {flowmark-0.6.2 → flowmark-0.6.3}/docs/general/agent-shortcuts/shortcut:create-or-update-validation-plan.md +0 -0
  66. {flowmark-0.6.2 → flowmark-0.6.3}/docs/general/agent-shortcuts/shortcut:create-pr-simple.md +0 -0
  67. {flowmark-0.6.2 → flowmark-0.6.3}/docs/general/agent-shortcuts/shortcut:implement-beads.md +0 -0
  68. {flowmark-0.6.2 → flowmark-0.6.3}/docs/general/agent-shortcuts/shortcut:implement-spec.md +0 -0
  69. {flowmark-0.6.2 → flowmark-0.6.3}/docs/general/agent-shortcuts/shortcut:merge-upstream.md +0 -0
  70. {flowmark-0.6.2 → flowmark-0.6.3}/docs/general/agent-shortcuts/shortcut:new-architecture-doc.md +0 -0
  71. {flowmark-0.6.2 → flowmark-0.6.3}/docs/general/agent-shortcuts/shortcut:new-implementation-beads-from-spec.md +0 -0
  72. {flowmark-0.6.2 → flowmark-0.6.3}/docs/general/agent-shortcuts/shortcut:new-implementation-spec.md +0 -0
  73. {flowmark-0.6.2 → flowmark-0.6.3}/docs/general/agent-shortcuts/shortcut:new-plan-spec.md +0 -0
  74. {flowmark-0.6.2 → flowmark-0.6.3}/docs/general/agent-shortcuts/shortcut:new-research-brief.md +0 -0
  75. {flowmark-0.6.2 → flowmark-0.6.3}/docs/general/agent-shortcuts/shortcut:new-validation-spec.md +0 -0
  76. {flowmark-0.6.2 → flowmark-0.6.3}/docs/general/agent-shortcuts/shortcut:precommit-process.md +0 -0
  77. {flowmark-0.6.2 → flowmark-0.6.3}/docs/general/agent-shortcuts/shortcut:refine-spec.md +0 -0
  78. {flowmark-0.6.2 → flowmark-0.6.3}/docs/general/agent-shortcuts/shortcut:review-all-code-specs-docs-convex.md +0 -0
  79. {flowmark-0.6.2 → flowmark-0.6.3}/docs/general/agent-shortcuts/shortcut:review-pr-and-fix-with-beads.md +0 -0
  80. {flowmark-0.6.2 → flowmark-0.6.3}/docs/general/agent-shortcuts/shortcut:review-pr.md +0 -0
  81. {flowmark-0.6.2 → flowmark-0.6.3}/docs/general/agent-shortcuts/shortcut:revise-architecture-doc.md +0 -0
  82. {flowmark-0.6.2 → flowmark-0.6.3}/docs/general/agent-shortcuts/shortcut:update-spec.md +0 -0
  83. {flowmark-0.6.2 → flowmark-0.6.3}/docs/general/agent-shortcuts/shortcut:update-specs-status.md +0 -0
  84. {flowmark-0.6.2 → flowmark-0.6.3}/docs/general/research/archive/.empty +0 -0
  85. {flowmark-0.6.2 → flowmark-0.6.3}/docs/general/research/current/.empty +0 -0
  86. {flowmark-0.6.2 → flowmark-0.6.3}/docs/general/research/current/research-cli-golden-testing.md +0 -0
  87. {flowmark-0.6.2 → flowmark-0.6.3}/docs/general/research/current/research-code-coverage-typescript.md +0 -0
  88. {flowmark-0.6.2 → flowmark-0.6.3}/docs/general/research/current/research-convex-db-limits-best-practices.md +0 -0
  89. {flowmark-0.6.2 → flowmark-0.6.3}/docs/general/research/current/research-modern-typescript-cli-patterns.md +0 -0
  90. {flowmark-0.6.2 → flowmark-0.6.3}/docs/general/research/current/research-modern-typescript-monorepo-patterns.md +0 -0
  91. {flowmark-0.6.2 → flowmark-0.6.3}/docs/installation.md +0 -0
  92. {flowmark-0.6.2 → flowmark-0.6.3}/docs/markdoc-tag-support-plan.md +0 -0
  93. {flowmark-0.6.2 → flowmark-0.6.3}/docs/project/architecture/archive/.empty +0 -0
  94. {flowmark-0.6.2 → flowmark-0.6.3}/docs/project/architecture/current/.empty +0 -0
  95. {flowmark-0.6.2 → flowmark-0.6.3}/docs/project/architecture/template-architecture.md +0 -0
  96. {flowmark-0.6.2 → flowmark-0.6.3}/docs/project/research/archive/.empty +0 -0
  97. {flowmark-0.6.2 → flowmark-0.6.3}/docs/project/research/current/.empty +0 -0
  98. {flowmark-0.6.2 → flowmark-0.6.3}/docs/project/research/template-research-brief.md +0 -0
  99. {flowmark-0.6.2 → flowmark-0.6.3}/docs/project/specs/active/.empty +0 -0
  100. {flowmark-0.6.2 → flowmark-0.6.3}/docs/project/specs/active/plan-2026-01-14-list-spacing-control.md +0 -0
  101. {flowmark-0.6.2 → flowmark-0.6.3}/docs/project/specs/active/valid-2026-01-14-list-spacing-control.md +0 -0
  102. {flowmark-0.6.2 → flowmark-0.6.3}/docs/project/specs/backlog/.empty +0 -0
  103. {flowmark-0.6.2 → flowmark-0.6.3}/docs/project/specs/done/.empty +0 -0
  104. {flowmark-0.6.2 → flowmark-0.6.3}/docs/project/specs/done/plan-2026-01-14-atomic-tag-wrapping.md +0 -0
  105. {flowmark-0.6.2 → flowmark-0.6.3}/docs/project/specs/done/plan-2026-01-14-markdoc-tag-compatibility.md +0 -0
  106. {flowmark-0.6.2 → flowmark-0.6.3}/docs/project/specs/done/valid-2026-01-14-atomic-tag-wrapping.md +0 -0
  107. {flowmark-0.6.2 → flowmark-0.6.3}/docs/project/specs/paused/.empty +0 -0
  108. {flowmark-0.6.2 → flowmark-0.6.3}/docs/project/specs/template-bugfix.md +0 -0
  109. {flowmark-0.6.2 → flowmark-0.6.3}/docs/project/specs/template-implementation-spec.md +0 -0
  110. {flowmark-0.6.2 → flowmark-0.6.3}/docs/project/specs/template-plan-spec.md +0 -0
  111. {flowmark-0.6.2 → flowmark-0.6.3}/docs/project/specs/template-validation-spec.md +0 -0
  112. {flowmark-0.6.2 → flowmark-0.6.3}/docs/publishing.md +0 -0
  113. {flowmark-0.6.2 → flowmark-0.6.3}/pyproject.toml +0 -0
  114. {flowmark-0.6.2 → flowmark-0.6.3}/src/flowmark/__init__.py +0 -0
  115. {flowmark-0.6.2 → flowmark-0.6.3}/src/flowmark/cli.py +0 -0
  116. {flowmark-0.6.2 → flowmark-0.6.3}/src/flowmark/formats/frontmatter.py +0 -0
  117. {flowmark-0.6.2 → flowmark-0.6.3}/src/flowmark/linewrapping/atomic_patterns.py +0 -0
  118. {flowmark-0.6.2 → flowmark-0.6.3}/src/flowmark/linewrapping/block_heuristics.py +0 -0
  119. {flowmark-0.6.2 → flowmark-0.6.3}/src/flowmark/linewrapping/line_wrappers.py +0 -0
  120. {flowmark-0.6.2 → flowmark-0.6.3}/src/flowmark/linewrapping/markdown_filling.py +0 -0
  121. {flowmark-0.6.2 → flowmark-0.6.3}/src/flowmark/linewrapping/protocols.py +0 -0
  122. {flowmark-0.6.2 → flowmark-0.6.3}/src/flowmark/linewrapping/sentence_split_regex.py +0 -0
  123. {flowmark-0.6.2 → flowmark-0.6.3}/src/flowmark/linewrapping/tag_handling.py +0 -0
  124. {flowmark-0.6.2 → flowmark-0.6.3}/src/flowmark/linewrapping/text_filling.py +0 -0
  125. {flowmark-0.6.2 → flowmark-0.6.3}/src/flowmark/linewrapping/text_wrapping.py +0 -0
  126. {flowmark-0.6.2 → flowmark-0.6.3}/src/flowmark/py.typed +0 -0
  127. {flowmark-0.6.2 → flowmark-0.6.3}/src/flowmark/reformat_api.py +0 -0
  128. {flowmark-0.6.2 → flowmark-0.6.3}/src/flowmark/transforms/doc_cleanups.py +0 -0
  129. {flowmark-0.6.2 → flowmark-0.6.3}/src/flowmark/transforms/doc_transforms.py +0 -0
  130. {flowmark-0.6.2 → flowmark-0.6.3}/src/flowmark/typography/ellipses.py +0 -0
  131. {flowmark-0.6.2 → flowmark-0.6.3}/src/flowmark/typography/smartquotes.py +0 -0
  132. {flowmark-0.6.2 → flowmark-0.6.3}/tests/test_alerts.py +0 -0
  133. {flowmark-0.6.2 → flowmark-0.6.3}/tests/test_cleanups.py +0 -0
  134. {flowmark-0.6.2 → flowmark-0.6.3}/tests/test_ellipses.py +0 -0
  135. {flowmark-0.6.2 → flowmark-0.6.3}/tests/test_escape_handling.py +0 -0
  136. {flowmark-0.6.2 → flowmark-0.6.3}/tests/test_filling.py +0 -0
  137. {flowmark-0.6.2 → flowmark-0.6.3}/tests/test_frontmatter.py +0 -0
  138. {flowmark-0.6.2 → flowmark-0.6.3}/tests/test_heading_spacing.py +0 -0
  139. {flowmark-0.6.2 → flowmark-0.6.3}/tests/test_list_spacing.py +0 -0
  140. {flowmark-0.6.2 → flowmark-0.6.3}/tests/test_ref_docs.py +0 -0
  141. {flowmark-0.6.2 → flowmark-0.6.3}/tests/test_sentences.py +0 -0
  142. {flowmark-0.6.2 → flowmark-0.6.3}/tests/test_smartquotes.py +0 -0
  143. {flowmark-0.6.2 → flowmark-0.6.3}/tests/test_width_options.py +0 -0
  144. {flowmark-0.6.2 → flowmark-0.6.3}/tests/test_wrapping.py +0 -0
  145. {flowmark-0.6.2 → flowmark-0.6.3}/tests/testdocs/.gitignore +0 -0
  146. {flowmark-0.6.2 → flowmark-0.6.3}/uv.lock +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: flowmark
3
- Version: 0.6.2
3
+ Version: 0.6.3
4
4
  Summary: Better auto-formatting for Markdown and plaintext
5
5
  Project-URL: Repository, https://github.com/jlevy/flowmark
6
6
  Author-email: Joshua Levy <joshua@cal.berkeley.edu>
@@ -92,6 +92,35 @@ class ExtendedParseInfo(NamedTuple):
92
92
  fence_len: int
93
93
 
94
94
 
95
+ class CustomStrikethrough(gfm_elements.Strikethrough):
96
+ """
97
+ Fixed Strikethrough that implements GFM flanking delimiter rules.
98
+
99
+ Marko's default pattern `(?<!~)(~|~~)([^~]+)\\1(?!~)` does not enforce
100
+ that the closing tilde must be right-flanking (not preceded by whitespace)
101
+ or that the opening tilde must be left-flanking (not followed by whitespace).
102
+ This causes `~60 seconds, ~130 words` to be incorrectly parsed as
103
+ strikethrough, since the space before the second `~` makes it left-flanking
104
+ only, not right-flanking, so it cannot close the span.
105
+
106
+ The fixed pattern adds:
107
+ - `(?!\\s)` after the opening delimiter (left-flanking: not followed by whitespace)
108
+ - `(?<!\\s)` before the closing delimiter (right-flanking: not preceded by whitespace)
109
+ - Non-greedy `+?` for correct minimal matching
110
+ """
111
+
112
+ pattern: re.Pattern[str] = re.compile(r"(?<!~)(~{1,2})(?!\s)([^~]+?)(?<!\s)\1(?!~)")
113
+ priority: int = 5
114
+ parse_children: bool = True
115
+ parse_group: int = 2
116
+
117
+ @override
118
+ @classmethod
119
+ def get_type(cls, snake_case: bool = False) -> str:
120
+ # Ensure renderer dispatch uses "strikethrough" not "custom_strikethrough".
121
+ return "strikethrough" if snake_case else "Strikethrough"
122
+
123
+
95
124
  class CustomFencedCode(block.FencedCode):
96
125
  """
97
126
  Extended FencedCode that preserves the fence character and length.
@@ -353,7 +382,14 @@ class MarkdownNormalizer(Renderer):
353
382
  fence = fence_char * fence_len
354
383
 
355
384
  lines = [f"{self._prefix}{fence}{lang_text}"]
356
- lines.extend(f"{self._second_prefix}{line}" for line in code_content.splitlines())
385
+ # Don't add prefix to empty lines to avoid trailing whitespace.
386
+ # Use rstrip() to preserve structural prefixes like ">" for blockquotes.
387
+ empty_line_prefix = self._second_prefix.rstrip()
388
+ for line in code_content.splitlines():
389
+ if line:
390
+ lines.append(f"{self._second_prefix}{line}")
391
+ else:
392
+ lines.append(empty_line_prefix)
357
393
  lines.append(f"{self._second_prefix}{fence}")
358
394
  self._prefix = self._second_prefix
359
395
  # After rendering a code block, don't suppress the next item break
@@ -668,8 +704,10 @@ def flowmark_markdown(
668
704
  # Using Marko's full extension system is tricky with our customizations so simpler
669
705
  # to do this manually.
670
706
  custom_parser = CustomParser()
671
- # Add GFM support.
707
+ # Add GFM support, using our fixed Strikethrough with proper flanking rules.
672
708
  for e in GFM.elements:
709
+ if e is gfm_elements.Strikethrough:
710
+ e = CustomStrikethrough
673
711
  assert (
674
712
  e not in custom_parser.block_elements and e not in custom_parser.inline_elements
675
713
  )
@@ -231,3 +231,133 @@ def test_minimum_backticks_computed_from_content():
231
231
  # Note: This is technically invalid markdown input (the parser may mishandle it)
232
232
  # but we test the code rendering logic's ability to compute minimum needed backticks
233
233
  pass # This is tested indirectly by other tests
234
+
235
+
236
+ def test_empty_lines_in_code_block_no_trailing_whitespace():
237
+ """Test that empty lines in code blocks remain truly empty (no trailing whitespace)."""
238
+ input_doc = dedent(
239
+ """
240
+ ```python
241
+ line1
242
+
243
+ line2
244
+ ```
245
+ """
246
+ ).strip()
247
+
248
+ expected_doc = (
249
+ dedent(
250
+ """
251
+ ```python
252
+ line1
253
+
254
+ line2
255
+ ```
256
+ """
257
+ ).strip()
258
+ + "\n"
259
+ )
260
+
261
+ result = fill_markdown(input_doc, semantic=True)
262
+ assert result == expected_doc
263
+ # Verify the empty line has no trailing whitespace
264
+ assert result.split("\n")[2] == ""
265
+
266
+
267
+ def test_empty_lines_in_nested_code_block_no_trailing_whitespace():
268
+ """Test that empty lines in code blocks inside list items have no trailing whitespace.
269
+
270
+ This is the key case: when a code block is nested inside a list item,
271
+ _second_prefix is non-empty (e.g. " "), and empty lines must not get that prefix.
272
+ """
273
+ input_doc = dedent(
274
+ """
275
+ - Example:
276
+
277
+ ```python
278
+ def foo():
279
+ pass
280
+
281
+ def bar():
282
+ pass
283
+ ```
284
+ """
285
+ ).strip()
286
+
287
+ expected_doc = (
288
+ dedent(
289
+ """
290
+ - Example:
291
+
292
+ ```python
293
+ def foo():
294
+ pass
295
+
296
+ def bar():
297
+ pass
298
+ ```
299
+ """
300
+ ).strip()
301
+ + "\n"
302
+ )
303
+
304
+ result = fill_markdown(input_doc, semantic=True)
305
+ assert result == expected_doc
306
+ # Verify the empty line between functions has no trailing whitespace
307
+ lines = result.split("\n")
308
+ empty_line_idx = next(
309
+ i
310
+ for i in range(len(lines))
311
+ if lines[i - 1].endswith("pass") and lines[i + 1].strip().startswith("def bar")
312
+ )
313
+ assert lines[empty_line_idx] == ""
314
+
315
+
316
+ def test_empty_lines_in_quoted_code_block_no_trailing_whitespace():
317
+ """Test that empty lines in code blocks inside blockquotes preserve > but strip trailing space."""
318
+ input_doc = dedent(
319
+ """
320
+ > ```python
321
+ > line1
322
+ >
323
+ > line2
324
+ > ```
325
+ """
326
+ ).strip()
327
+
328
+ # The empty line should have ">" (blockquote marker) but no trailing space.
329
+ expected_doc = "> ```python\n> line1\n>\n> line2\n> ```\n"
330
+
331
+ result = fill_markdown(input_doc, semantic=True)
332
+ assert result == expected_doc
333
+
334
+
335
+ def test_multiple_empty_lines_in_code_block():
336
+ """Test code blocks with multiple consecutive empty lines stay clean."""
337
+ input_doc = dedent(
338
+ """
339
+ ```python
340
+ line1
341
+
342
+
343
+ line2
344
+ ```
345
+ """
346
+ ).strip()
347
+
348
+ expected_doc = (
349
+ dedent(
350
+ """
351
+ ```python
352
+ line1
353
+
354
+
355
+ line2
356
+ ```
357
+ """
358
+ ).strip()
359
+ + "\n"
360
+ )
361
+
362
+ result = fill_markdown(input_doc, semantic=True)
363
+ assert result == expected_doc
@@ -0,0 +1,100 @@
1
+ """Test strikethrough and tilde handling.
2
+
3
+ Tests that flowmark correctly distinguishes between:
4
+ - Actual GFM strikethrough: ~~text~~ or ~text~
5
+ - Literal tildes used as "approximately": ~60 seconds, ~130 words
6
+ """
7
+
8
+ from flowmark.formats.flowmark_markdown import flowmark_markdown
9
+ from flowmark.linewrapping.markdown_filling import fill_markdown
10
+
11
+
12
+ def test_literal_tildes_before_numbers():
13
+ """Tildes before numbers (meaning 'approximately') should be preserved as literal."""
14
+ md = flowmark_markdown()
15
+
16
+ result = md("Target: ~60 seconds, ~130 words total\n")
17
+ assert result == "Target: ~60 seconds, ~130 words total\n"
18
+
19
+
20
+ def test_literal_tildes_not_converted_to_double():
21
+ """The bug: ~60 seconds, ~130 should NOT become ~~60 seconds, ~~130."""
22
+ result = fill_markdown("Target: ~60 seconds, ~130 words total")
23
+ assert "~~" not in result
24
+ assert result.strip() == "Target: ~60 seconds, ~130 words total"
25
+
26
+
27
+ def test_double_tilde_strikethrough():
28
+ """Standard ~~strikethrough~~ should be preserved."""
29
+ md = flowmark_markdown()
30
+
31
+ result = md("This is ~~strikethrough~~ text\n")
32
+ assert result == "This is ~~strikethrough~~ text\n"
33
+
34
+
35
+ def test_single_tilde_strikethrough():
36
+ """Single-tilde ~strikethrough~ is valid GFM; flowmark normalizes to ~~double~~."""
37
+ md = flowmark_markdown()
38
+
39
+ result = md("This is ~strikethrough~ text\n")
40
+ assert result == "This is ~~strikethrough~~ text\n"
41
+
42
+
43
+ def test_multiple_strikethroughs():
44
+ """Multiple strikethrough spans in a single line."""
45
+ md = flowmark_markdown()
46
+
47
+ result = md("~one~ and ~two~ items\n")
48
+ assert result == "~~one~~ and ~~two~~ items\n"
49
+
50
+
51
+ def test_single_tilde_no_closer():
52
+ """A single tilde with no matching closer should remain literal."""
53
+ md = flowmark_markdown()
54
+
55
+ result = md("About ~50% of users\n")
56
+ assert result == "About ~50% of users\n"
57
+
58
+
59
+ def test_tildes_with_space_before_closer():
60
+ """Tildes where the 'closer' is preceded by whitespace should not be strikethrough."""
61
+ md = flowmark_markdown()
62
+
63
+ # The second ~ is preceded by a space, so it's not right-flanking and can't close.
64
+ result = md("costs ~100 to ~200\n")
65
+ assert result == "costs ~100 to ~200\n"
66
+
67
+
68
+ def test_tilde_space_after_opener():
69
+ """A tilde followed by a space is not left-flanking, so no strikethrough."""
70
+ md = flowmark_markdown()
71
+
72
+ result = md("~ spaced ~\n")
73
+ assert result == "~ spaced ~\n"
74
+
75
+
76
+ def test_tilde_space_before_closer():
77
+ """A tilde preceded by a space is not right-flanking, so no strikethrough."""
78
+ md = flowmark_markdown()
79
+
80
+ result = md("~foo ~\n")
81
+ assert result == "~foo ~\n"
82
+
83
+
84
+ def test_escaped_tildes_preserved():
85
+ """Backslash-escaped tildes should remain escaped."""
86
+ md = flowmark_markdown()
87
+
88
+ result = md("Target: \\~60 seconds, \\~130 words total\n")
89
+ assert result == "Target: \\~60 seconds, \\~130 words total\n"
90
+
91
+
92
+ def test_strikethrough_in_paragraph():
93
+ """Strikethrough within a longer paragraph should be preserved during wrapping."""
94
+ result = fill_markdown(
95
+ "This paragraph has some ~~deleted text~~ in it and also mentions ~50 users."
96
+ )
97
+ assert "~~deleted text~~" in result
98
+ assert "~50 users" in result
99
+ # Make sure ~50 doesn't become ~~50
100
+ assert "~~50" not in result
@@ -676,13 +676,11 @@ def test_list_item_with_tag_on_continuation_line():
676
676
  # The tag should NOT have an extra blank line before it
677
677
  # The continuation line with the tag should be preserved as-is
678
678
  assert "\n\n <!-- #kg-32zz -->" not in result, (
679
- f"Extra blank line incorrectly added before tag on continuation line.\n"
680
- f"Result:\n{result}"
679
+ f"Extra blank line incorrectly added before tag on continuation line.\nResult:\n{result}"
681
680
  )
682
681
 
683
682
  # The proper indented continuation should be preserved
684
- assert "tsconfig.base.json)\n <!-- #kg-32zz -->" in result or \
685
- "(tsconfig.base.json)\n<!-- #kg-32zz -->" in result, (
686
- f"Tag continuation line not preserved correctly.\n"
687
- f"Result:\n{result}"
688
- )
683
+ assert (
684
+ "tsconfig.base.json)\n <!-- #kg-32zz -->" in result
685
+ or "(tsconfig.base.json)\n<!-- #kg-32zz -->" in result
686
+ ), f"Tag continuation line not preserved correctly.\nResult:\n{result}"
@@ -484,6 +484,7 @@ More line spacing challenges with multi-paragraph items:
484
484
  ```shell
485
485
  # Use z in place of cd: switch directories (first time):
486
486
  z ~/some/long/path/to/foo
487
+
487
488
  # Thereafter it's faster:
488
489
  z foo
489
490
  ```
@@ -1665,6 +1666,45 @@ Your feedback is valuable to us.
1665
1666
 
1666
1667
  {% /form %}
1667
1668
 
1669
+ ## Strikethrough and Tildes
1670
+
1671
+ Standard double-tilde ~~strikethrough~~ is the most common form and should always be
1672
+ preserved. Single-tilde ~~strikethrough~~ is also valid GFM and should be normalized to
1673
+ double tildes.
1674
+
1675
+ Here are some ~~deleted words~~ in a sentence.
1676
+ And ~~more deleted words~~ in another sentence.
1677
+
1678
+ Multiple ~~first~~ and ~~second~~ strikethroughs on one line work fine.
1679
+
1680
+ ### Tildes as “approximately” (not strikethrough)
1681
+
1682
+ Tildes before numbers are commonly used to mean “approximately” and must not be treated
1683
+ as strikethrough:
1684
+
1685
+ Target: ~60 seconds, ~130 words total
1686
+
1687
+ The meeting lasted ~45 minutes.
1688
+ We had ~20 attendees.
1689
+ The cost was ~$500.
1690
+
1691
+ Performance improved from ~200ms to ~50ms, a ~4x improvement.
1692
+
1693
+ There were ~1,000 users and ~500 active sessions.
1694
+
1695
+ ### Escaped tildes
1696
+
1697
+ Escaped tildes should be preserved: \~not strikethrough\~
1698
+
1699
+ ### Edge cases
1700
+
1701
+ A lone tilde ~ in text is fine.
1702
+
1703
+ Tildes at the end: the value is ~~100~~.
1704
+
1705
+ A ~~long strikethrough that spans many words in a single paragraph and may get wrapped
1706
+ across lines during formatting~~ should be handled.
1707
+
1668
1708
  ## Summary
1669
1709
 
1670
1710
  All these corner cases should format consistently and predictably.
@@ -484,6 +484,7 @@ More line spacing challenges with multi-paragraph items:
484
484
  ```shell
485
485
  # Use z in place of cd: switch directories (first time):
486
486
  z ~/some/long/path/to/foo
487
+
487
488
  # Thereafter it's faster:
488
489
  z foo
489
490
  ```
@@ -1665,6 +1666,45 @@ Your feedback is valuable to us.
1665
1666
 
1666
1667
  {% /form %}
1667
1668
 
1669
+ ## Strikethrough and Tildes
1670
+
1671
+ Standard double-tilde ~~strikethrough~~ is the most common form and should always be
1672
+ preserved. Single-tilde ~~strikethrough~~ is also valid GFM and should be normalized to
1673
+ double tildes.
1674
+
1675
+ Here are some ~~deleted words~~ in a sentence.
1676
+ And ~~more deleted words~~ in another sentence.
1677
+
1678
+ Multiple ~~first~~ and ~~second~~ strikethroughs on one line work fine.
1679
+
1680
+ ### Tildes as "approximately" (not strikethrough)
1681
+
1682
+ Tildes before numbers are commonly used to mean "approximately" and must not be treated
1683
+ as strikethrough:
1684
+
1685
+ Target: ~60 seconds, ~130 words total
1686
+
1687
+ The meeting lasted ~45 minutes.
1688
+ We had ~20 attendees.
1689
+ The cost was ~$500.
1690
+
1691
+ Performance improved from ~200ms to ~50ms, a ~4x improvement.
1692
+
1693
+ There were ~1,000 users and ~500 active sessions.
1694
+
1695
+ ### Escaped tildes
1696
+
1697
+ Escaped tildes should be preserved: \~not strikethrough\~
1698
+
1699
+ ### Edge cases
1700
+
1701
+ A lone tilde ~ in text is fine.
1702
+
1703
+ Tildes at the end: the value is ~~100~~.
1704
+
1705
+ A ~~long strikethrough that spans many words in a single paragraph and may get wrapped
1706
+ across lines during formatting~~ should be handled.
1707
+
1668
1708
  ## Summary
1669
1709
 
1670
1710
  All these corner cases should format consistently and predictably.
@@ -461,6 +461,7 @@ More line spacing challenges with multi-paragraph items:
461
461
  ```shell
462
462
  # Use z in place of cd: switch directories (first time):
463
463
  z ~/some/long/path/to/foo
464
+
464
465
  # Thereafter it's faster:
465
466
  z foo
466
467
  ```
@@ -1616,6 +1617,43 @@ us.
1616
1617
 
1617
1618
  {% /form %}
1618
1619
 
1620
+ ## Strikethrough and Tildes
1621
+
1622
+ Standard double-tilde ~~strikethrough~~ is the most common form and should always be
1623
+ preserved. Single-tilde ~~strikethrough~~ is also valid GFM and should be normalized to
1624
+ double tildes.
1625
+
1626
+ Here are some ~~deleted words~~ in a sentence. And ~~more deleted words~~ in another
1627
+ sentence.
1628
+
1629
+ Multiple ~~first~~ and ~~second~~ strikethroughs on one line work fine.
1630
+
1631
+ ### Tildes as "approximately" (not strikethrough)
1632
+
1633
+ Tildes before numbers are commonly used to mean "approximately" and must not be treated
1634
+ as strikethrough:
1635
+
1636
+ Target: ~60 seconds, ~130 words total
1637
+
1638
+ The meeting lasted ~45 minutes. We had ~20 attendees. The cost was ~$500.
1639
+
1640
+ Performance improved from ~200ms to ~50ms, a ~4x improvement.
1641
+
1642
+ There were ~1,000 users and ~500 active sessions.
1643
+
1644
+ ### Escaped tildes
1645
+
1646
+ Escaped tildes should be preserved: \~not strikethrough\~
1647
+
1648
+ ### Edge cases
1649
+
1650
+ A lone tilde ~ in text is fine.
1651
+
1652
+ Tildes at the end: the value is ~~100~~.
1653
+
1654
+ A ~~long strikethrough that spans many words in a single paragraph and may get wrapped
1655
+ across lines during formatting~~ should be handled.
1656
+
1619
1657
  ## Summary
1620
1658
 
1621
1659
  All these corner cases should format consistently and predictably.
@@ -484,6 +484,7 @@ More line spacing challenges with multi-paragraph items:
484
484
  ```shell
485
485
  # Use z in place of cd: switch directories (first time):
486
486
  z ~/some/long/path/to/foo
487
+
487
488
  # Thereafter it's faster:
488
489
  z foo
489
490
  ```
@@ -1665,6 +1666,45 @@ Your feedback is valuable to us.
1665
1666
 
1666
1667
  {% /form %}
1667
1668
 
1669
+ ## Strikethrough and Tildes
1670
+
1671
+ Standard double-tilde ~~strikethrough~~ is the most common form and should always be
1672
+ preserved. Single-tilde ~~strikethrough~~ is also valid GFM and should be normalized to
1673
+ double tildes.
1674
+
1675
+ Here are some ~~deleted words~~ in a sentence.
1676
+ And ~~more deleted words~~ in another sentence.
1677
+
1678
+ Multiple ~~first~~ and ~~second~~ strikethroughs on one line work fine.
1679
+
1680
+ ### Tildes as "approximately" (not strikethrough)
1681
+
1682
+ Tildes before numbers are commonly used to mean "approximately" and must not be treated
1683
+ as strikethrough:
1684
+
1685
+ Target: ~60 seconds, ~130 words total
1686
+
1687
+ The meeting lasted ~45 minutes.
1688
+ We had ~20 attendees.
1689
+ The cost was ~$500.
1690
+
1691
+ Performance improved from ~200ms to ~50ms, a ~4x improvement.
1692
+
1693
+ There were ~1,000 users and ~500 active sessions.
1694
+
1695
+ ### Escaped tildes
1696
+
1697
+ Escaped tildes should be preserved: \~not strikethrough\~
1698
+
1699
+ ### Edge cases
1700
+
1701
+ A lone tilde ~ in text is fine.
1702
+
1703
+ Tildes at the end: the value is ~~100~~.
1704
+
1705
+ A ~~long strikethrough that spans many words in a single paragraph and may get wrapped
1706
+ across lines during formatting~~ should be handled.
1707
+
1668
1708
  ## Summary
1669
1709
 
1670
1710
  All these corner cases should format consistently and predictably.
@@ -349,6 +349,7 @@ More line spacing challenges with multi-paragraph items:
349
349
  ```shell
350
350
  # Use z in place of cd: switch directories (first time):
351
351
  z ~/some/long/path/to/foo
352
+
352
353
  # Thereafter it's faster:
353
354
  z foo
354
355
  ```
@@ -1353,6 +1354,39 @@ Your feedback is valuable to us.
1353
1354
 
1354
1355
  {% /form %}
1355
1356
 
1357
+ ## Strikethrough and Tildes
1358
+
1359
+ Standard double-tilde ~~strikethrough~~ is the most common form and should always be preserved.
1360
+ Single-tilde ~strikethrough~ is also valid GFM and should be normalized to double tildes.
1361
+
1362
+ Here are some ~~deleted words~~ in a sentence. And ~more deleted words~ in another sentence.
1363
+
1364
+ Multiple ~~first~~ and ~~second~~ strikethroughs on one line work fine.
1365
+
1366
+ ### Tildes as "approximately" (not strikethrough)
1367
+
1368
+ Tildes before numbers are commonly used to mean "approximately" and must not be treated as strikethrough:
1369
+
1370
+ Target: ~60 seconds, ~130 words total
1371
+
1372
+ The meeting lasted ~45 minutes. We had ~20 attendees. The cost was ~$500.
1373
+
1374
+ Performance improved from ~200ms to ~50ms, a ~4x improvement.
1375
+
1376
+ There were ~1,000 users and ~500 active sessions.
1377
+
1378
+ ### Escaped tildes
1379
+
1380
+ Escaped tildes should be preserved: \~not strikethrough\~
1381
+
1382
+ ### Edge cases
1383
+
1384
+ A lone tilde ~ in text is fine.
1385
+
1386
+ Tildes at the end: the value is ~100~.
1387
+
1388
+ A ~~long strikethrough that spans many words in a single paragraph and may get wrapped across lines during formatting~~ should be handled.
1389
+
1356
1390
  ## Summary
1357
1391
 
1358
1392
 
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes