Skip to main content

mdformat-slw

Build Status PyPI version

An mdformat plugin for semantic line wrapping (slw), which breaks lines after sentence-ending punctuation so that diffs stay small and readable.

Wrapping is on by default. A break is inserted after each end-of-sentence marker (.!?) except when:

  • The line is shorter than the minimum length (40 characters by default), which keeps short sentences together. Set --slw-min-line=0 to always wrap
  • The text can't be wrapped: inline code, links, definition lists, code blocks, tables, and HTML blocks
  • The marker ends an abbreviation, either because multiple markers occur (p.m., e.g.) or because the word is in the language list or in --slw-abbreviations. Matching is case-insensitive

The algorithm collapses consecutive whitespace to a single space (preserving non-breaking spaces and the linebreaks that follow them), finds the protected regions, inserts the sentence breaks, replaces spaces in link text with non-breaking spaces, and finally wraps anything still longer than --slw-wrap without splitting words or indents.

For examples, see ./tests/pre-commit-test.md and ./tests/format/fixtures

mdformat Usage

Add this package wherever you use mdformat and the plugin will be auto-recognized. For additional information on plugins, see the official mdformat documentation here

mdformat document.md

# Recommended when using --slw-wrap, because --wrap=keep turns off mdformat's own wrapping
mdformat document.md --slw-wrap=88 --wrap=keep

pre-commit/prek

repos:
  - repo: https://github.com/executablebooks/mdformat
    rev: 1.0.0
    hooks:
      - id: mdformat
        additional_dependencies:
          - mdformat-slw

uvx

uvx --with=mdformat-slw mdformat

Or with pipx:

pipx install mdformat
pipx inject mdformat mdformat-slw

Configuration

mdformat-slw adds the CLI arguments:

  • --no-wrap-sentences to turn off sentence wrapping
  • --slw-markers for the characters that end a sentence (default: .!?)
  • --slw-wrap for the maximum line width (default: 88, set to 0 to disable)
  • --slw-min-line for the shortest line that may be wrapped (default: 40, set to 0 to always wrap)
  • --slw-lang for the built-in abbreviation list (default: ac, one of ac, en, de, es, fr, or it)
  • --slw-abbreviations for a comma-separated list of extra abbreviations, such as "NASA,FBI,CustomCorp"
  • --slw-abbreviations-only to use only those abbreviations and skip the language list

You can also use the toml configuration (https://mdformat.readthedocs.io/en/stable/users/configuration_file.html):

# .mdformat.toml

[plugin.slw]
no_wrap_sentences = false
slw_markers = ".!?"
slw_wrap = 88
slw_min_line = 40
lang = "en"
abbreviations = "Corp,Inc,NASA"
abbreviations_only = false

[mdformat]
wrap = "keep"

Or the Python API:

import mdformat

mdformat.text("This is a test. It has multiple sentences!", extensions={"slw"})
# 'This is a test. It has multiple sentences!\n'

mdformat.text("This is a test. It has multiple sentences!", extensions={"slw"}, options={"slw_min_line": 0})
# 'This is a test.\nIt has multiple sentences!\n'

Example

Dr. Smith met with Prof. Johnson at 3 p.m. to review the draft. They discussed the wrapping rules etc. and agreed on the defaults.

Becomes:

Dr. Smith met with Prof. Johnson at 3 p.m. to review the draft.
They discussed the wrapping rules etc. and agreed on the defaults.

p.m. and etc. are abbreviations, so no break follows them.

Language Support

Abbreviation lists are built in for ac (Author's Choice, the 77-abbreviation default covering titles, time, Latin, academic, business, and geography terms), en (17), de (54), es (36), fr (42), and it (40).

mdformat-slw targets non-symbolic, left-to-right languages. Sentence detection relies on whitespace after a marker and wrapping relies on space-delimited words, but neither are applicable CJK or RTL scripts. Doing those properly means Unicode line breaking (UAX #14), kinsoku shori, word segmentation, and bidi handling, which is a different project, so they aren't planned:

  • CJK text has no spaces between sentences, so no boundaries are found. The text passes through unchanged (wcwidth accounts for double-width characters when measuring line length), and wrapping only applies if you space the sentences yourself, e.g. これは最初の文です。 これは2番目の文です。, and set --slw-markers=".!?。!?". See tests/format/fixtures/lang_ja.md and lang_ko.md
  • Arabic and other RTL text gets no bidi handling, and the native punctuation (؟ U+061F, ، U+060C, ؛ U+061B) isn't in the default marker set. ASCII markers still work and --slw-markers / --slw-abbreviations can add the rest for partial support

Acknowledgments

This plugin is inspired by and named after razziel89/mdslw, which is an excellent standalone tool for semantic line wrapping. mdslw can't be used as an mdformat plugin because mdformat formats from its own AST, so this package reimplements the idea natively, which lets it run alongside other mdformat plugins and in the same pre-commit hook. Overtime, the implementations have diverged, but I'm always open to submissions and feature requests to continually improve the developer experience.

Contributing

See CONTRIBUTING.md

Metadata

Release files for mdformat-slw 0.4.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for mdformat-slw 0.4.0
File Size Uploaded
mdformat_slw-0.4.0.tar.gz 17.2 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for mdformat-slw 0.4.0
File Interpreter ABI Platform
mdformat_slw-0.4.0-py3-none-any.whl Python 3 none any Details

Total release size: 33.8 kB

Release files / mdformat_slw-0.4.0.tar.gz

Download URL mdformat_slw-0.4.0.tar.gz
Size 17.2 kB
Tags Source
SHA-256 checksum
How to use checksums
93a8e40f47ab2ded82831c18264c3eb0356820602c63f3ab79bbc013c3dfb6a6
BLAKE2b-256 checksum
How to use checksums
dfe4875b5de5ab59c6f9c3d21e49d45245c7da0ebcae0f64b6ced9497e4bfd4a
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Aug 4, 2026.

Transparency log

Release files / mdformat_slw-0.4.0-py3-none-any.whl

Download URL mdformat_slw-0.4.0-py3-none-any.whl
Size 16.6 kB
Tags Python 3
SHA-256 checksum
How to use checksums
8bbd18e8ec5facd383668bcc5a5cc14d53a58f16ffd47134631cef2ae66f8334
BLAKE2b-256 checksum
How to use checksums
04a02b29ed72e9afb60a93ec1d72ae2d079e46582919a5b794b7a22e268ece17
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Aug 4, 2026.

Transparency log

Release history Release notifications | RSS feed

This release

0.4.0 This release

2 release files

0.2.0

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page