Math regions

This is mdwright's reason for existing. Generic Markdown formatters mangle LaTeX: they reflow \frac{a}{b} into \\frac{a}{b}, collapse the blank line before \begin{align*}, and apply emphasis rules inside \(\alpha\). mdwright treats math as opaque: recognised before any other pass runs, emitted verbatim.

What counts as math

The default math grammar:

  • Inline. \( … \) (paired backslash-paren, single line).
  • Display. \[ … \] (paired backslash-bracket, may span lines).
  • Environments. \begin{NAME} … \end{NAME} for any NAME matching [A-Za-z][A-Za-z0-9*]*, paired with a non-overlapping \end{NAME}.

$ … $ and $$ … $$ are not math by default. Dollar-delimited math is common in academic prose but collides with literal-dollar use (prices, shell prompts). Opt in via configuration:

[lint]
math.dollar = true

The stray-dollar lint flags lone dollar signs when this option is off, so authors migrating from a dollar-delimited dialect catch the change.

How the scanner runs

The math crate recognises candidate math spans over strings and byte ranges. The document crate supplies Markdown exclusion ranges (code, HTML, other opaque regions), then stores the accepted math regions as document facts with stable coordinates back to the original source. The exact source bytes, including whitespace, casing, comment chars, and trailing backslashes, pass through unchanged. The formatter cannot accidentally apply emphasis, escape, or wrap logic inside a math region: rewrite candidates are verified against the document's math-region signature before they commit. Lint rules that match on text see the same opaque region; latex-command, for instance, only fires outside math.

Block-level math

A math environment whose start delimiter sits at column 1 of an otherwise-blank line is a block. The formatter emits blocks with one blank line above and below, never indented inside a list item unless the source already indented it. This avoids the canonical bug:

input:                          generic formatter:              mdwright:
A paragraph.                    A paragraph.                    A paragraph.
                                \begin{align*}                  
\begin{align*}                  E &= mc^2                       \begin{align*}
E &= mc^2                       \end{align*}                    E &= mc^2
\end{align*}                                                    \end{align*}

Stripping the blank line above \begin{align*} rolls the environment into the paragraph and breaks the rendered DOM.

Math-adjacent rules

Three rules check math without parsing it:

Each runs on the recognised region as a string; none of them care about the math semantics.

Math inside code blocks

If \( appears inside a fenced code block or inline code (`\(x\)`), mdwright does not treat it as math; code regions are recognised earlier still. The math scanner consults the same exclusion ranges the formatter does, so it never produces false positives inside code or HTML.

See also