Skip to content
Merged
11 changes: 11 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,14 @@ Supports:
- [mkdocstrings Cross-References](https://mkdocstrings.github.io/usage/#cross-references)
- [Python Markdown "Abbreviations"\*](https://squidfunk.github.io/mkdocs-material/reference/tooltips/#adding-abbreviations)
- \*Note: the markup (HTML) rendered for abbreviations is not useful for rendering. If important, I'm open to contributions because the implementation could be challenging
- [Python Markdown "Attribute Lists"](https://python-markdown.github.io/extensions/attr_list)
- Preserves attribute list syntax when using `--wrap` mode
- [PyMdown Extensions "Arithmatex" (Math/LaTeX Support)](https://facelessuser.github.io/pymdown-extensions/extensions/arithmatex) ([Material for MkDocs Math](https://squidfunk.github.io/mkdocs-material/reference/math))
- This plugin combines three math rendering plugins from mdit-py-plugins:
1. **dollarmath**: Handles `$...$` (inline) and `$$...$$` (block) with smart dollar mode that prevents false positives (e.g., `$3.00` is not treated as math)
1. **texmath**: Handles `\(...\)` (inline) and `\[...\]` (block) LaTeX bracket notation
1. **amsmath**: Handles LaTeX environments like `\begin{align}...\end{align}`, `\begin{cases}...\end{cases}`, `\begin{matrix}...\end{matrix}`, etc.
- Can be deactivated entirely with the `--no-mkdocs-math` flag
- [Python Markdown "Snippets"\*](https://facelessuser.github.io/pymdown-extensions/extensions/snippets)
- \*Note: the markup (HTML) renders the plain text without implementing the snippet logic. I'm open to contributions if anyone needs full support for snippets

Expand Down Expand Up @@ -123,6 +131,8 @@ md.render(text)

- `--ignore-missing-references` if set, do not escape link references when no definition is found. This is required when references are dynamic, such as with python mkdocstrings

- `--no-mkdocs-math` if set, deactivate math/LaTeX rendering (Arithmatex). By default, math is enabled. This can be useful if you want to format markdown without processing math syntax.

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

```toml
Expand All @@ -131,6 +141,7 @@ You can also use the toml configuration (https://mdformat.readthedocs.io/en/stab
[plugin.mkdocs]
align_semantic_breaks_in_lists = true
ignore_missing_references = true
no_mkdocs_math = true
```

## Contributing
Expand Down
20 changes: 12 additions & 8 deletions mdformat_mkdocs/_normalize_list.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
from itertools import starmap
from typing import TYPE_CHECKING, Any, Literal, NamedTuple, TypeVar

from more_itertools import unzip, zip_equal
from more_itertools import unzip

from ._helpers import (
EOL,
Expand Down Expand Up @@ -380,7 +380,7 @@ def _insert_newlines(
"""Extend zipped_lines with newlines if necessary."""
newline = ("", "")
new_lines: list[tuple[str, str]] = []
for line, zip_line in zip_equal(parsed_lines, zipped_lines):
for line, zip_line in zip(parsed_lines, zipped_lines, strict=True):
new_lines.append(zip_line)
if (
line.parsed.syntax == Syntax.EDGE_CODE
Expand Down Expand Up @@ -421,12 +421,14 @@ def parse_text(
for indent in map_lookback(_parse_html_line, lines, None)
]
# When both, code_indents take precedence
block_indents = [_c or _h for _c, _h in zip_equal(code_indents, html_indents)]
new_indents = [*starmap(_format_new_indent, zip_equal(lines, block_indents))]
block_indents = [
_c or _h for _c, _h in zip(code_indents, html_indents, strict=True)
]
new_indents = [*starmap(_format_new_indent, zip(lines, block_indents, strict=True))]

new_contents = [
_format_new_content(line, inc_numbers, ci is not None)
for line, ci in zip_equal(lines, code_indents)
for line, ci in zip(lines, code_indents, strict=True)
]

if use_sem_break:
Expand All @@ -437,10 +439,10 @@ def parse_text(
)
new_indents = [
_trim_semantic_indent(indent, s_i, in_defbody)
for indent, s_i in zip_equal(new_indents, semantic_indents)
for indent, s_i in zip(new_indents, semantic_indents, strict=True)
]

new_lines = _insert_newlines(lines, [*zip_equal(new_indents, new_contents)])
new_lines = _insert_newlines(lines, [*zip(new_indents, new_contents, strict=True)])
return ParsedText(
new_lines=new_lines,
debug_original_lines=lines,
Expand Down Expand Up @@ -468,7 +470,9 @@ def _join(*, new_lines: list[tuple[str, str]]) -> str:

return "".join(
f"{new_indent}{new_content}{EOL}"
for new_indent, new_content in zip_equal(new_indents_iter, new_contents_iter)
for new_indent, new_content in zip(
new_indents_iter, new_contents_iter, strict=True
)
)


Expand Down
14 changes: 14 additions & 0 deletions mdformat_mkdocs/mdit_plugins/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,14 @@
)
from ._pymd_abbreviations import PYMD_ABBREVIATIONS_PREFIX, pymd_abbreviations_plugin
from ._pymd_admon import pymd_admon_plugin
from ._pymd_arithmatex import (
AMSMATH_BLOCK,
DOLLARMATH_BLOCK,
DOLLARMATH_BLOCK_LABEL,
DOLLARMATH_INLINE,
TEXMATH_BLOCK_EQNO,
pymd_arithmatex_plugin,
)
from ._pymd_captions import PYMD_CAPTIONS_PREFIX, pymd_captions_plugin
from ._pymd_snippet import PYMD_SNIPPET_PREFIX, pymd_snippet_plugin
from ._python_markdown_attr_list import (
Expand All @@ -31,6 +39,10 @@
)

__all__ = (
"AMSMATH_BLOCK",
"DOLLARMATH_BLOCK",
"DOLLARMATH_BLOCK_LABEL",
"DOLLARMATH_INLINE",
"MATERIAL_ADMON_MARKERS",
"MATERIAL_CONTENT_TAB_MARKERS",
"MKDOCSTRINGS_AUTOREFS_PREFIX",
Expand All @@ -40,6 +52,7 @@
"PYMD_CAPTIONS_PREFIX",
"PYMD_SNIPPET_PREFIX",
"PYTHON_MARKDOWN_ATTR_LIST_PREFIX",
"TEXMATH_BLOCK_EQNO",
"escape_deflist",
"material_admon_plugin",
"material_content_tabs_plugin",
Expand All @@ -48,6 +61,7 @@
"mkdocstrings_crossreference_plugin",
"pymd_abbreviations_plugin",
"pymd_admon_plugin",
"pymd_arithmatex_plugin",
"pymd_captions_plugin",
"pymd_snippet_plugin",
"python_markdown_attr_list_plugin",
Expand Down
56 changes: 56 additions & 0 deletions mdformat_mkdocs/mdit_plugins/_pymd_arithmatex.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
r"""Python-Markdown Extensions: Arithmatex (Math Support).

Uses existing mdit-py-plugins for LaTeX/MathJax mathematical expressions.

Inline math delimiters:
- $...$ (with smart_dollar rules: no whitespace adjacent to $)
- \\(...\\)

Block math delimiters:
- $$...$$
- \\[...\\]
- \\begin{env}...\\end{env}

Docs: <https://facelessuser.github.io/pymdown-extensions/extensions/arithmatex>

"""

from __future__ import annotations

from typing import TYPE_CHECKING

from mdit_py_plugins.amsmath import amsmath_plugin
from mdit_py_plugins.dollarmath import dollarmath_plugin
from mdit_py_plugins.texmath import texmath_plugin

if TYPE_CHECKING:
from markdown_it import MarkdownIt

# Token types from the plugins
# Note: dollarmath and texmath share the same token types for inline/block math:
# - "math_inline" is used for both $...$ and \(...\)
# - "math_block" is used for both $$...$$ and \[...\]
DOLLARMATH_INLINE = "math_inline"
DOLLARMATH_BLOCK = "math_block"
DOLLARMATH_BLOCK_LABEL = "math_block_label" # For $$...$$ (label) syntax
TEXMATH_BLOCK_EQNO = "math_block_eqno" # For \[...\] (label) syntax
AMSMATH_BLOCK = "amsmath"


def pymd_arithmatex_plugin(md: MarkdownIt) -> None:
r"""Register Arithmatex support using existing mdit-py-plugins.

This is a convenience wrapper that configures three existing plugins:
- dollarmath_plugin: for $...$ and $$...$$
- texmath_plugin: for \\(...\\) and \\[...\\]
- amsmath_plugin: for \\begin{env}...\\end{env}
"""
# Dollar syntax: $...$ and $$...$$
# Defaults provide smart dollar mode (no digits/space adjacent to $)
md.use(dollarmath_plugin)

# Bracket syntax: \(...\) and \[...\]
md.use(texmath_plugin, delimiters="brackets")

# LaTeX environments: \begin{env}...\end{env}
md.use(amsmath_plugin)
69 changes: 69 additions & 0 deletions mdformat_mkdocs/plugin.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,13 +12,18 @@
from ._normalize_list import normalize_list as unbounded_normalize_list
from ._postprocess_inline import postprocess_list_wrap
from .mdit_plugins import (
AMSMATH_BLOCK,
DOLLARMATH_BLOCK,
DOLLARMATH_BLOCK_LABEL,
DOLLARMATH_INLINE,
MKDOCSTRINGS_AUTOREFS_PREFIX,
MKDOCSTRINGS_CROSSREFERENCE_PREFIX,
MKDOCSTRINGS_HEADING_AUTOREFS_PREFIX,
PYMD_ABBREVIATIONS_PREFIX,
PYMD_CAPTIONS_PREFIX,
PYMD_SNIPPET_PREFIX,
PYTHON_MARKDOWN_ATTR_LIST_PREFIX,
TEXMATH_BLOCK_EQNO,
escape_deflist,
material_admon_plugin,
material_content_tabs_plugin,
Expand All @@ -27,6 +32,7 @@
mkdocstrings_crossreference_plugin,
pymd_abbreviations_plugin,
pymd_admon_plugin,
pymd_arithmatex_plugin,
pymd_captions_plugin,
pymd_snippet_plugin,
python_markdown_attr_list_plugin,
Expand Down Expand Up @@ -62,6 +68,11 @@ def cli_is_align_semantic_breaks_in_lists(options: ContextOptions) -> bool:
return bool(get_conf(options, "align_semantic_breaks_in_lists")) or False


def cli_is_no_mkdocs_math(options: ContextOptions) -> bool:
"""user-specified flag to disable math/LaTeX rendering."""
return bool(get_conf(options, "no_mkdocs_math")) or False


def add_cli_argument_group(group: argparse._ArgumentGroup) -> None:
"""Add options to the mdformat CLI.

Expand All @@ -80,11 +91,19 @@ def add_cli_argument_group(group: argparse._ArgumentGroup) -> None:
const=True,
help="If set, do not escape link references when no definition is found. This is required when references are dynamic, such as with python mkdocstrings",
)
group.add_argument(
"--no-mkdocs-math",
action="store_const",
const=True,
help="If set, disable math/LaTeX rendering (Arithmatex). By default, math is enabled.",
)


def update_mdit(mdit: MarkdownIt) -> None:
"""Update the parser."""
mdit.use(material_admon_plugin)
if not cli_is_no_mkdocs_math(mdit.options):
mdit.use(pymd_arithmatex_plugin)
mdit.use(pymd_captions_plugin)
mdit.use(material_content_tabs_plugin)
mdit.use(material_deflist_plugin)
Expand All @@ -103,6 +122,49 @@ def _render_node_content(node: RenderTreeNode, context: RenderContext) -> str:
return node.content


def _render_math_inline(node: RenderTreeNode, context: RenderContext) -> str: # noqa: ARG001
"""Render inline math with original delimiters."""
markup = node.markup
content = node.content
if markup == "$":
return f"${content}$"
if markup == "\\(":
return f"\\({content}\\)"
# Fallback
return f"${content}$"


def _render_math_block(node: RenderTreeNode, context: RenderContext) -> str: # noqa: ARG001
"""Render block math with original delimiters."""
markup = node.markup
content = node.content
if markup == "$$":
return f"$$\n{content.strip()}\n$$"
if markup == "\\[":
return f"\\[\n{content.strip()}\n\\]"
# Fallback
return f"$$\n{content.strip()}\n$$"


def _render_math_block_eqno(node: RenderTreeNode, context: RenderContext) -> str: # noqa: ARG001
"""Render block math with equation label."""
markup = node.markup
content = node.content
label = node.info # Label is stored in info field
if markup == "$$":
return f"$$\n{content.strip()}\n$$ ({label})"
if markup == "\\[":
return f"\\[\n{content.strip()}\n\\] ({label})"
# Fallback
return f"$$\n{content.strip()}\n$$ ({label})"


def _render_amsmath(node: RenderTreeNode, context: RenderContext) -> str: # noqa: ARG001
"""Render amsmath environment."""
# Content already includes \begin{} and \end{}
return node.content


def _render_meta_content(node: RenderTreeNode, context: RenderContext) -> str: # noqa: ARG001
"""Return node content without additional processing."""
return node.meta.get("content", "")
Expand Down Expand Up @@ -214,6 +276,13 @@ def render_pymd_caption(node: RenderTreeNode, context: RenderContext) -> str:
"dl": render_material_definition_list,
"dt": render_material_definition_term,
"dd": render_material_definition_body,
# Math support (from mdit-py-plugins)
DOLLARMATH_INLINE: _render_math_inline,
DOLLARMATH_BLOCK: _render_math_block,
DOLLARMATH_BLOCK_LABEL: _render_math_block_eqno,
TEXMATH_BLOCK_EQNO: _render_math_block_eqno,
AMSMATH_BLOCK: _render_amsmath,
# Other plugins
PYMD_CAPTIONS_PREFIX: render_pymd_caption,
MKDOCSTRINGS_AUTOREFS_PREFIX: _render_meta_content,
MKDOCSTRINGS_CROSSREFERENCE_PREFIX: _render_cross_reference,
Expand Down
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ dependencies = [
"mdit-py-plugins >= 0.4.1",
"more-itertools >= 10.5.0",
]
description = "A mdformat plugin for mkdocs and mkdocs-material"
description = "An mdformat plugin for mkdocs and Material for MkDocs"
keywords = ["markdown", "markdown-it", "mdformat", "mdformat_plugin_template"]
license = "MIT"
license-files = ["LICENSE"]
Expand Down
Loading