Style knobs

This page documents each style knob in [fmt]. Every knob defaults to "preserve", which means the canonicalisation pass leaves source bytes unchanged for that construct. Set a non-preserve value to opt into rewriting.

See Formatter policy for the overall design (structural emit + opt-in canonicalisation) and Configuration for the full .mdwright.toml schema.

[fmt] italic

ValueEffect
"preserve" (default)Emphasis delimiters round-trip from source. _foo_ stays _foo_; *foo* stays *foo*.
"asterisk"Rewrite _…_ to *…* when verification preserves the parse.
"underscore"Rewrite *…* to _…_ when verification preserves the parse.

Verification skips when: the rewrite would change the parse of the enclosing paragraph window. The most common case is intraword underscore (id_S, Hom_{cart}): pulldown already treats these as plain text under CM §6.2 rule 6, so no rewrite is proposed and nothing skips. Where rewrites do skip silently is in dense multi-delimiter runs (*_*…*_*-style chains) whose pairing depends on flanking neighbours; verification catches these and leaves the source bytes in place.

[fmt]
italic = "asterisk"

[fmt] strong

ValueEffect
"preserve" (default)Strong delimiters round-trip from source. **foo** stays **foo**; __foo__ stays __foo__.
"asterisk"Rewrite __…__ to **…**.
"underscore"Rewrite **…** to __…__.

Independent of italic. With italic = "asterisk" and strong = "underscore" you get *italic* alongside __strong__. italic and strong are independent knobs.

[fmt]
italic = "asterisk"
strong = "underscore"

[fmt] list-marker

ValueEffect
"preserve" (default)Each unordered list keeps its source bullet character.
"dash"Rewrite each bullet to -.
"asterisk"Rewrite each bullet to *.
"plus"Rewrite each bullet to +.

Marker-local. The document crate exposes one fact per list-item marker. The formatter rewrites those marker bytes only, then verifies the full document before committing the family plan. Nested list markers are separate facts, so an outer list rewrite cannot cover child markers accidentally.

[fmt]
list-marker = "dash"

[fmt] ordered-list

ValueEffect
"preserve" (default)Each ordered list keeps its source numbering. 3. a / 5. b / 9. c stays.
"one"Rewrite markers to 1. when verification preserves the list start. This matches mdformat's default spelling for ordinary lists that already start at 1..
"consistent"Renumber so item k (0-indexed) becomes start_num + k, where start_num is the source's first item's number. 3. a / 5. b / 9. c3. a / 4. b / 5. c.

Marker-local: each ordered item exposes its digit range, list start, and ordinal. The family plan rewrites those digit ranges and commits only after full-document verification. The starting number is preserved; only the increment is canonicalised.

[fmt]
ordered-list = "consistent"

[fmt] thematic-break

ValueEffect
"preserve" (default)Thematic breaks keep their source character (---, ***, ___).
"dash"Rewrite to ---.
"asterisk"Rewrite to ***.
"underscore"Rewrite to ___.
"underscore-70"Rewrite the whole line to 70 underscores, matching mdformat's default thematic-break spelling.

The repeat count and internal spacing are preserved; only the character changes. So * * * becomes _ _ _ under "underscore", not ___. Use "underscore-70" when you want the mdformat spelling.

[fmt]
thematic-break = "dash"

[fmt.refs] style

ValueEffect
"preserve" (default)Each link destination keeps its source form: [ref]: url or [ref]: <url> survives.
"bare"Strip angle brackets where the bare form would still parse. [ref]: <url>[ref]: url.
"angle"Wrap destinations in angle brackets. [ref]: url[ref]: <url>.

Applies to both reference-link definitions ([ref]: dest) and inline link destinations ([text](dest)). Verification skips when the bare form contains whitespace, unbalanced parentheses, or other bytes that would prevent pulldown from parsing it as a bare destination; the angle-wrapped form is kept in those cases.

[fmt.refs]
style = "angle"

[fmt.tables] style

ValueEffect
"preserve" (default)GFM table spacing round-trips from source.
"pad"Pad cells and delimiter rows to mdformat-compatible widths when verification preserves the parse.

Padding is a table-level operation. Inline delimiter and link destination rewrites run first; table padding then reads the current cell bytes and rewrites the table block as one verified replacement. Tables with source cells the document facts cannot account for are left unchanged rather than partially rewritten.

[fmt.tables]
style = "pad"

[fmt] wrap

ValueEffect
"keep" (default)Preserve existing paragraph line breaks.
"no"Collapse soft line breaks inside paragraphs where verification preserves the parse.
integerWrap breakable prose lines at that display-column width.

wrap = 120 means breakable output lines should fit within 120 columns in every formatter profile. The accepted exception is an indivisible atomic token, such as a long code span, URL, math atom, or single long word. Those tokens are left intact rather than split into invalid Markdown.

The default wrap strategy is "stable": ordinary source newlines inside a paragraph are soft break positions, hard breaks stay hard boundaries, and each hard-break-bounded run is filled greedily up to the configured column. Use wrap-strategy = "balanced" when you want mdwright to rebalance paragraphs for more even line lengths.

[fmt]
wrap = 120
[fmt]
wrap = 120
wrap-strategy = "balanced"

[fmt.lists] continuation-indent

ValueEffect
"marker-width" (default)Continuation lines align under the source list marker width.
"four-space"Continuation lines use four spaces after the containing block prefix.

This setting only affects paragraphs that are wrapped inside list items. It is separate from list-marker because the bullet character and continuation indentation are independent style decisions. The mdformat profile defaults this key to "four-space"; explicit config overrides that default.

[fmt]
wrap = 120

[fmt.lists]
continuation-indent = "four-space"

Combined example

[fmt]
profile = "mdformat"

This keeps mdformat's default wrap = keep, sets list continuation indentation to four spaces, and applies mdformat spelling for supported style knobs. Explicit keys override the profile:

[fmt]
profile = "mdformat"
wrap = 120

[fmt.lists]
continuation-indent = "marker-width"

A per-knob spelling can also be written without the profile:

[fmt]
list-marker = "dash"
thematic-break = "underscore-70"
ordered-list = "one"

[fmt.refs]
style = "angle"

[fmt.tables]
style = "pad"

This is mdformat-compatible where mdwright has verified rewrite support. It does not move orphan footnotes or copy mdformat behaviours that would change the parsed document.

How verification skips become visible

When a rewrite would change the parse of the enclosing paragraph window, the canonicalisation pass logs a tracing::warn! with the byte span and skipped rewrite. Capture these in production with RUST_LOG=mdwright_format=warn. A high skip rate on one document usually points at a structural-emit edge case worth filing as a regression input.