mdformat-space-control
An mdformat plugin that provides unified control over Markdown spacing:
- EditorConfig support: Configure list indentation via
.editorconfigfiles - Tight list formatting: Automatically removes unnecessary blank lines between list items
- Frontmatter spacing: Normalizes spacing after YAML frontmatter (works with mdformat-frontmatter)
- Consecutive blank line normalization: Limits runs of 3+ empty lines to a maximum of 2
- Trailing whitespace removal: Strips trailing whitespace outside code blocks
- Escaped link repair: Fixes malformed multi-line links from web-clipped content
- Smart dash conversion: Converts
--to en-dash (–) and---to em-dash (—), preserving code blocks, inline code, HTML comments, HTML tags, URIs, CLI options, wikilinks, link destinations, and math - Wikilink preservation: Handles Obsidian-style
[[links]],[[links|aliases]],[[page#heading]],[[page#^blockid]], and![[embeds]] - Math preservation: Carries
$inline$,$$inline$$, and$$display-math LaTeX through verbatim, so\alpha,_,\,, and\{are not re-escaped - Soft break joining: Joins soft breaks (plain newlines within paragraphs) into single lines, normalizing to single-line paragraphs
Installation
pip install mdformat-space-control
Or with pipx for command-line usage:
pipx install mdformat
pipx inject mdformat mdformat-space-control
Usage
After installation, mdformat will automatically use this plugin:
mdformat your-file.md
EditorConfig Support
Create an .editorconfig file in your project:
# .editorconfig
root = true
[*.md]
indent_style = space
indent_size = 4
Nested lists will use the configured indentation:
Before:
- Item 1
- Nested item
- Item 2
After (with 4-space indent):
- Item 1
- Nested item
- Item 2
Tight List Formatting
Lists with single-paragraph items are automatically formatted as tight lists:
Before:
- Item 1
- Item 2
- Item 3
After:
- Item 1
- Item 2
- Item 3
Multi-paragraph items preserve loose formatting:
- First item with multiple paragraphs
Second paragraph of first item
- Second item
Frontmatter Spacing
When used with mdformat-frontmatter, this plugin removes blank lines between the frontmatter closing delimiter and the first content block:
Before:
---
title: My Document
---
# Introduction
After:
---
title: My Document
---
# Introduction
Install both plugins for this feature:
pip install mdformat-space-control mdformat-frontmatter
EditorConfig Properties
| Property | Status | Notes |
|---|---|---|
indent_style |
Supported | space or tab for list indentation |
indent_size |
Supported | Number of spaces per indent level |
tab_width |
Supported | Used when indent_size = tab |
Python API
When using the Python API, you can set the file context for EditorConfig lookup:
import mdformat
from mdformat_space_control import set_current_file
set_current_file("/path/to/your/file.md")
try:
result = mdformat.text(markdown_text, extensions={"space_control"})
finally:
set_current_file(None)
Smart Dash Conversion
Markdown dash sequences are automatically converted to their Unicode equivalents:
--→ en-dash (–, U+2013)---→ em-dash (—, U+2014)
Before:
The result---unexpected as it was---changed everything.
Pages 10--20 of the report.
After:
The result—unexpected as it was—changed everything.
Pages 10–20 of the report.
Dashes are preserved inside fenced code blocks, inline code spans, HTML comments, HTML tags, URIs (gs://bucket--name), CLI options (--output), wikilink targets (![[images/a--b/frame.jpg]]), markdown link destinations ([text](docs/a--b.md)), and dollar-delimited math ($x--y$, $$ blocks). Thematic breaks (---), GFM table separator rows (| -- | -- |), and frontmatter delimiters are not affected. Sequences of 4+ dashes are left unchanged.
These exclusions apply equally inside blockquotes, at any nesting depth: leading > markers are stripped before block-level patterns are matched, so tables and code fences in a blockquote or Obsidian callout are preserved just as they are at the top level.
Wikilink Preservation
Obsidian-style wikilinks are preserved during formatting:
Link to [[another note]] or [[note|with alias]].
Embed an image: ![[photo.jpg]]
Link to heading: [[note#section]]
Block reference: [[note#^blockid]]
Wikilinks inside markdown link text are correctly handled without duplication:
[![[image.jpg]]](http://example.com)
Literal square brackets inside a target are preserved (e.g. a note titled like an email subject), and are not escaped as stray link syntax:
[[[EXTERNAL] requesting feedback]]
Inside GFM table cells, an alias pipe is escaped to \| so the table parser does not split the wikilink across two columns (this requires mdformat-gfm). The escaped pipe renders correctly in Obsidian, and prose wikilinks keep a bare |:
| Note | Status |
| -- | -- |
| [[path/to/note|Short Alias]] | Active |
Math Preservation
Dollar-delimited math is parsed as opaque tokens and rendered verbatim. Without this, markdown-it treats LaTeX as prose and the CommonMark renderer re-escapes it: \alpha becomes \\alpha, _ becomes \_, and \, \{ \| lose their backslash, which breaks MathJax rendering in Obsidian.
Inline $\mathbf{a}_{t:t+H} \mid \mathbf{o}_t$ and display $$E = mc^2$$ in a paragraph.
$$
\begin{aligned}
L &= \sum_{i=1}^{N} \log p(x_i \mid \theta) \\
&= \tfrac{1}{2} \|w\|_2^2
\end{aligned}
$$
Rules, chosen to match how Obsidian pairs delimiters:
- Any two
$on a line pair, including next to spaces or digits, so$5 and $6is carried verbatim rather than escaped. A lone$and an escaped\$are prose. - A line starting with
$$opens a display block that closes on a line ending with$$; a single-line$$x$$stays on one line. Blocks keep their internal line structure and relative indentation, work inside list items and blockquotes, and may directly follow a paragraph line without a blank line. - An unclosed
$$is not a block and formats as prose. - Equation labels (
$$x$$ (eq1), MyST syntax) are not recognized.
Known gaps: a $ inside an inline code span can still be paired with a $ outside it on the same line, as in MathJax; and | inside math in a GFM table cell must be written \|, as Obsidian requires.
Soft Break Joining
Soft breaks (plain newlines within paragraphs) are joined into single lines with spaces. This normalizes editor-inserted line wraps to single-line paragraphs, matching CommonMark rendering behavior where soft breaks produce spaces in HTML output. The joining applies to paragraphs, list items, and blockquotes.
Before:
This is a paragraph with
a soft line break in the source.
After:
This is a paragraph with a soft line break in the source.
Explicit hard breaks (\ + newline) are preserved unchanged.
Compatible Plugins
This plugin is tested to work alongside:
- mdformat-frontmatter - YAML frontmatter parsing
- mdformat-simple-breaks - Normalizes thematic breaks to
--- - mdformat-gfm - GFM table parsing (prevents hard breaks from being inserted into pipe table rows)
For formatting files in an Obsidian vault, the recommended install is:
pip install mdformat-space-control mdformat-frontmatter mdformat-gfm
Note: Wikilink support is built-in; mdformat-wikilink is not needed.
Development
# Install dependencies
uv sync
# Run tests
uv run python -m pytest
# Run with coverage
uv run python -m pytest --cov=mdformat_space_control
License
MIT - see LICENSE file for details.
Metadata
Release files for mdformat-space-control 0.5.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| mdformat_space_control-0.5.0.tar.gz | 27.5 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| mdformat_space_control-0.5.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 44.8 kB
Release files / mdformat_space_control-0.5.0.tar.gz
| Download URL | mdformat_space_control-0.5.0.tar.gz |
|---|---|
| Size | 27.5 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
52418d30c0b8b204f7135bd859300af321718fe36b31e04a1195d31762a914e0
|
|
BLAKE2b-256 checksum How to use checksums |
70ba4627949b0146dc8e5c3b830822bffc975b131a79352da5cf9393b0b4384e
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.12.15
|
Release files / mdformat_space_control-0.5.0-py3-none-any.whl
| Download URL | mdformat_space_control-0.5.0-py3-none-any.whl |
|---|---|
| Size | 17.3 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
a288ff402096cda1084be3d4775e59c972f5e1b29aaeef4516fef8b66ce56c50
|
|
BLAKE2b-256 checksum How to use checksums |
3d2209a9ae22ad775561e0ef249b5751077f968071f4a04385dc469db3a837de
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.12.15
|