Not all empty lines are equal. For instance, in .MD files, blank lines convey semantic value, and are used to delineate paragraphs, the start and end of lists, etc.
I’ve written my own ST plugins to clean up various syntaxes. In MD, it collapses consecutive empty lines to one, to shrink vertical whitespace, but retain semantic meaning. In my case, I also want to collapse any empty lines WITHIN a list block, to avoid li > p when output to html.
It is not pretty, and those more skilled in .py would call it clumsy, but it works. It is very specifically scoped for MD.
def normalise_markdown_empty_lines(text):
"""Normalise empty lines while keeping Markdown list blocks compact.
This is an extract of CleanMD's final spacing pass. It does four things:
- collapses runs of empty lines to a single empty line between paragraphs
- ensures an empty line before the first item in a list block
- removes empty lines between consecutive list items
- ensures an empty line after a list block before normal paragraph text
It intentionally treats indented list items as list items too, because
`line.strip()` is used before matching the marker.
"""
lines = text.splitlines()
normalised_lines = []
inside_list = False
found_empty_line = False
for line in lines:
stripped = line.strip()
is_list_item = re.match(r"^([-*+]|\d+\.)\s+", stripped)
if is_list_item:
# The first item in a list block should be separated from the
# preceding paragraph. Further list items stay adjacent.
if not inside_list:
normalised_lines.append("")
normalised_lines.append(line)
inside_list = True
found_empty_line = False
continue
if stripped == "":
# Defer emitting empty lines until we know what follows. This lets
# us collapse repeated empties and discard trailing empty lines.
found_empty_line = True
continue
# For ordinary content, restore one empty line if the source had one
# or if we are leaving a list block.
if found_empty_line or inside_list:
normalised_lines.append("")
normalised_lines.append(line)
inside_list = False
found_empty_line = False
return "\n".join(normalised_lines)
Here are the results:
BEFORE:
# Heading
paragraph text
second paragraph
- list item one
- list item two
Trailing paragraph
AFTER
# Heading
paragraph text
second paragraph
- list item one
- list item two
Trailing paragraph