Skip to content
Open
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
9 changes: 9 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,14 @@
# Change Log

## Unreleased

- 🐛 FIX: `texmath` no longer loops forever on block math opened inside a blockquote and closed after it (#156)

The block rules matched their regex against the rest of the source, so the closing
delimiter could lie outside the current block; the rule then reported a match
without advancing `state.line`. The closing line is now searched within the current
block only, otherwise the rule does not match (no auto-closing, as in `amsmath`).

## 0.7.0 - 2026-07-19

- ✨ NEW: Add section reference plugin (`section_ref`) (#144)
Expand Down
41 changes: 22 additions & 19 deletions mdit_py_plugins/texmath/index.py
Original file line number Diff line number Diff line change
Expand Up @@ -128,25 +128,28 @@ def make_block_func(rule: RuleDictType) -> Callable[[StateBlock, int, int, bool]
def _func(state: StateBlock, begLine: int, endLine: int, silent: bool) -> bool:
begin = state.bMarks[begLine] + state.tShift[begLine]
res = applyRule(rule, state.src, begin, state.parentType == "blockquote")
if res:
if not silent:
token = state.push(rule["name"], "math", 0)
token.block = True
token.content = res[1]
token.info = res[len(res.groups())]
token.markup = rule["tag"]

line = begLine
endpos = begin + res.end() - 1

while line < endLine:
if endpos >= state.bMarks[line] and endpos <= state.eMarks[line]:
# line for end of block math found ...
state.line = line + 1
break
line += 1

return bool(res)
if not res:
return False

# find the line holding the end of the match, within this block only
endpos = begin + res.end() - 1
line = begLine
while line < endLine and endpos > state.eMarks[line]:
line += 1
if line >= endLine or endpos < state.bMarks[line]:
# the closing delimiter lies outside the current block,
# e.g. math opened inside a blockquote and closed after it
return False

if not silent:
token = state.push(rule["name"], "math", 0)
token.block = True
token.content = res[1]
token.info = res[len(res.groups())]
token.markup = rule["tag"]

state.line = line + 1
return True

return _func

Expand Down
12 changes: 12 additions & 0 deletions tests/fixtures/texmath_bracket.md
Original file line number Diff line number Diff line change
Expand Up @@ -410,3 +410,15 @@ Indented by 4 spaces, DISABLE-CODEBLOCKS
</eqn>
</section>
.

display equation opened in blockquote and closed outside it (valid=False)
.
> \[ a=1

\]
.
<blockquote>
<p>[ a=1</p>
</blockquote>
<p>]</p>
.
28 changes: 28 additions & 0 deletions tests/fixtures/texmath_dollar.md
Original file line number Diff line number Diff line change
Expand Up @@ -454,3 +454,31 @@ Indented by 4 spaces, DISABLE-CODEBLOCKS
<eqn>a</eqn>
</section>
.

display equation opened in blockquote and closed outside it (valid=False)
.
> $$ a=1

$$
.
<blockquote>
<p>$$ a=1</p>
</blockquote>
<p>$$</p>
.

display equation opened in nested blockquote and closed outside it (valid=False)
.
- > $$ a=1

$$ (1)
.
<ul>
<li>
<blockquote>
<p>$$ a=1</p>
</blockquote>
</li>
</ul>
<p>$$ (1)</p>
.
17 changes: 17 additions & 0 deletions tests/test_texmath.py
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,23 @@ def test_plugin_parse(data_regression):
data_regression.check([t.as_dict() for t in tokens])


@pytest.mark.parametrize(
"delimiters,opening,closing",
[
("brackets", "\\[", "\\]"),
("dollars", "$$", "$$"),
("kramdown", "$$", "$$"),
("gitlab", "```math", "```"),
("julia", "```math", "```"),
],
)
def test_block_closed_outside_blockquote(delimiters, opening, closing):
"""A math block opened inside a blockquote cannot close after it."""
md = MarkdownIt("commonmark").use(texmath_plugin, delimiters=delimiters)
tokens = md.parse(f"> {opening} a=1\n\n{closing}\n")
assert not [t for t in tokens if t.type.startswith("math_block")]


@pytest.mark.parametrize(
"line,title,input,expected",
read_fixture_file(FIXTURE_PATH.joinpath("texmath_dollar.md")),
Expand Down