Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
67 changes: 66 additions & 1 deletion mdformat_mkdocs/mdit_plugins/_pymd_arithmatex.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,14 +17,19 @@

from __future__ import annotations

import re
from textwrap import dedent
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 collections.abc import Callable

from markdown_it import MarkdownIt
from markdown_it.rules_block import StateBlock

# Token types from the plugins
# Note: dollarmath and texmath share the same token types for inline/block math:
Expand All @@ -36,14 +41,39 @@
TEXMATH_BLOCK_EQNO = "math_block_eqno" # For \[...\] (label) syntax
AMSMATH_BLOCK = "amsmath"

_ESCAPED_BRACKET_RE = re.compile(r"^\\\[([^\n\r]*?)\\\](\S)")
r"""Pattern to detect single-line \[...\] with content after closing bracket.

This identifies escaped brackets like \[test\]: value (not math)"""

_EXPECTED_WRAPPED_RULES = 2


def _is_escaped_bracket(state: StateBlock, start_line: int) -> bool:
r"""Check if \[...\] on this line is escaped brackets, not math.

Returns True if line starts with \[ and \] are on same line with non-whitespace after \].
Only checks lines starting with \[ to avoid interfering with dollar math.
"""
pos = state.bMarks[start_line] + state.tShift[start_line]
if pos + 2 > len(state.src):
return False
line_start = state.src[pos : pos + 2]
if line_start != r"\[":
return False
return bool(_ESCAPED_BRACKET_RE.match(state.src[pos:]))


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 \\[...\\]
- texmath_plugin: for \\(...\\) and \\[...\\] (with fix for issue #72)
- amsmath_plugin: for \\begin{env}...\\end{env}

Raises:
RuntimeError: If texmath rules cannot be found and wrapped.
"""
# Dollar syntax: $...$ and $$...$$
# Defaults provide smart dollar mode (no digits/space adjacent to $)
Expand All @@ -52,5 +82,40 @@ def pymd_arithmatex_plugin(md: MarkdownIt) -> None:
# Bracket syntax: \(...\) and \[...\]
md.use(texmath_plugin, delimiters="brackets")

# Fix for issue #72: Wrap texmath block rules to reject \[test\]: value
# Find the texmath rules (inserted after dollarmath's math_block)
def make_wrapper(
original_fn: Callable[[StateBlock, int, int, bool], bool],
) -> Callable[[StateBlock, int, int, bool], bool]:
def wrapped(state: StateBlock, start: int, end: int, silent: bool) -> bool:
if _is_escaped_bracket(state, start):
return False
return original_fn(state, start, end, silent)

return wrapped

# Wrap math_block_eqno and the second math_block (both from texmath)
# Find them by name, skipping the first math_block (from dollarmath)
found_first_math_block = False
wrapped_count = 0
for idx, rule in enumerate(md.block.ruler.__rules__):
if rule.name == "math_block":
if found_first_math_block:
# This is the second math_block (from texmath)
md.block.ruler.__rules__[idx].fn = make_wrapper(rule.fn)
wrapped_count += 1
else:
found_first_math_block = True
elif rule.name == "math_block_eqno":
# This is from texmath
md.block.ruler.__rules__[idx].fn = make_wrapper(rule.fn)
wrapped_count += 1

if wrapped_count != _EXPECTED_WRAPPED_RULES:
msg = dedent(f"""\
Expected to wrap {_EXPECTED_WRAPPED_RULES} texmath rules (math_block and math_block_eqno), but wrapped {wrapped_count}.
Plugin configuration may have changed.""")
raise RuntimeError(msg)

# LaTeX environments: \begin{env}...\end{env}
md.use(amsmath_plugin)
56 changes: 56 additions & 0 deletions tests/format/fixtures/pymd_arithmatex_edge_cases.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,62 @@ This is not math: $escaped$ and \\(also escaped\\).
Literal dollar: I paid $5.00 for $3.00 worth.
.

Escaped Bracket Notation (Issue #72)
.
- \[test\]: value
- \[label\]:content
.
- \[test\]: value
- \[label\]:content
.

Escaped Brackets vs Math Brackets
.
Single-line escaped bracket: \[test\]: value

Multi-line math block:

\[
a^2 + b^2 = c^2
\]

Math with label (space before parenthesis):

\[
E = mc^2
\] (eq:einstein)

Escaped bracket with letter: \[test\]x

Math at end of line:

\[
x = y
\]
.
Single-line escaped bracket: \[test\]: value

Multi-line math block:

\[
a^2 + b^2 = c^2
\]

Math with label (space before parenthesis):

\[
E = mc^2
\] (eq:einstein)

Escaped bracket with letter: [test]x

Math at end of line:

\[
x = y
\]
.

Math in Lists
.
1. First item with math $x = y$
Expand Down