22
33from __future__ import annotations
44
5+ import re
56import textwrap
67from functools import partial
78from typing import TYPE_CHECKING
1011
1112from ._helpers import ContextOptions , get_conf
1213from ._normalize_list import normalize_list as unbounded_normalize_list
13- from ._postprocess_inline import postprocess_list_wrap
14+ from ._postprocess_inline import postprocess_list_wrap as _postprocess_list_wrap
1415from .mdit_plugins import (
1516 AMSMATH_BLOCK ,
1617 DOLLARMATH_BLOCK ,
@@ -134,29 +135,42 @@ def _render_math_inline(node: RenderTreeNode, context: RenderContext) -> str: #
134135 return f"${ content } $"
135136
136137
138+ def _strip_blockquote_markers (content : str ) -> str :
139+ """Strip blockquote markers from math block content.
140+
141+ markdown-it includes "> " prefixes when block math appears inside blockquotes.
142+ """
143+ lines = content .split ("\n " )
144+ return "\n " .join (
145+ line .removeprefix ("> " ) if line .startswith ("> " ) else line for line in lines
146+ ).strip ()
147+
148+
137149def _render_math_block (node : RenderTreeNode , context : RenderContext ) -> str : # noqa: ARG001
138150 """Render block math with original delimiters."""
139151 markup = node .markup
140- content = node .content
152+ cleaned_content = _strip_blockquote_markers (node .content )
153+
141154 if markup == "$$" :
142- return f"$$\n { content . strip () } \n $$"
155+ return f"$$\n { cleaned_content } \n $$"
143156 if markup == "\\ [" :
144- return f"\\ [\n { content . strip () } \n \\ ]"
157+ return f"\\ [\n { cleaned_content } \n \\ ]"
145158 # Fallback
146- return f"$$\n { content . strip () } \n $$"
159+ return f"$$\n { cleaned_content } \n $$"
147160
148161
149162def _render_math_block_eqno (node : RenderTreeNode , context : RenderContext ) -> str : # noqa: ARG001
150163 """Render block math with equation label."""
151164 markup = node .markup
152- content = node .content
153- label = node .info # Label is stored in info field
165+ label = node .info
166+ cleaned_content = _strip_blockquote_markers (node .content )
167+
154168 if markup == "$$" :
155- return f"$$\n { content . strip () } \n $$ ({ label } )"
169+ return f"$$\n { cleaned_content } \n $$ ({ label } )"
156170 if markup == "\\ [" :
157- return f"\\ [\n { content . strip () } \n \\ ] ({ label } )"
171+ return f"\\ [\n { cleaned_content } \n \\ ] ({ label } )"
158172 # Fallback
159- return f"$$\n { content . strip () } \n $$ ({ label } )"
173+ return f"$$\n { cleaned_content } \n $$ ({ label } )"
160174
161175
162176def _render_amsmath (node : RenderTreeNode , context : RenderContext ) -> str : # noqa: ARG001
@@ -176,40 +190,30 @@ def _render_inline_content(node: RenderTreeNode, context: RenderContext) -> str:
176190 return inline .content
177191
178192
179- def _render_code_inline (node : RenderTreeNode , context : RenderContext ) -> str :
180- r"""Render inline code, cleaning up whitespace from newline normalization.
181-
182- `markdown-it` normalizes newlines in inline code to spaces. This can result in
183- unintended trailing spaces from original newlines before closing backticks.
184- Per mdformat's own logic, trailing spaces are only intentional if there are
185- also leading spaces. So we strip trailing spaces when there's no leading space.
193+ def _render_text (node : RenderTreeNode , context : RenderContext ) -> str :
194+ r"""Re-escape dollar signs that mdformat core stripped.
186195
187- Example: `code\n` (newline) → `code ` (parsed) → `code` (rendered)
188-
189- This could break at any time, so this is a best effort to resolve issues like:
190- https://github.com/KyleKing/mdformat-mkdocs/issues/34#issuecomment-3589835341
196+ mdformat removes "unnecessary" backslash escapes (\$ -> $), but with math enabled
197+ those bare $ become math delimiters. Compares text content against the parent
198+ inline token (which preserves backslashes) to detect and restore the escapes.
191199
200+ Related: https://github.com/KyleKing/mdformat-mkdocs/issues/77
192201 """
193- default_renderer = DEFAULT_RENDERERS .get ("code_inline " )
202+ default_renderer = DEFAULT_RENDERERS .get ("text " )
194203 if default_renderer is None :
195204 return node .content
196205
197- result = default_renderer (node , context )
198-
199- # Only process single-backtick code (not double-backtick code with embedded backticks)
200- if not (result .startswith ("`" ) and result .endswith ("`" ) and "``" not in result ):
201- return result
206+ text = default_renderer (node , context )
202207
203- content = result [1 :- 1 ] # Strip opening and closing backticks
204- has_leading_space = content .startswith (" " )
205- has_trailing_space = content .endswith (" " )
208+ if cli_is_no_mkdocs_math (context .options ):
209+ return text
206210
207- # Strip trailing space only if there's no leading space and content is not all whitespace
208- # This preserves the mdformat rule: spaces are only intentional when both are present
209- if has_trailing_space and not has_leading_space and content . strip () :
210- return f"` { content . rstrip ( ' ' ) } `"
211+ if node . parent and node . parent . type == "inline" :
212+ parent_content = node . parent . content
213+ if "$" in text and r"\$" in parent_content :
214+ text = re . sub ( r"(?<!\\)\$" , r"\$" , text )
211215
212- return result
216+ return text
213217
214218
215219def _render_heading_autoref (node : RenderTreeNode , context : RenderContext ) -> str :
@@ -307,12 +311,12 @@ def render_pymd_caption(node: RenderTreeNode, context: RenderContext) -> str:
307311 "admonition_title" : render_admon_title ,
308312 "admonition_mkdocs" : add_extra_admon_newline ,
309313 "admonition_mkdocs_title" : render_admon_title ,
310- "code_inline" : _render_code_inline ,
311314 "content_tab_mkdocs" : add_extra_admon_newline ,
312315 "content_tab_mkdocs_title" : render_admon_title ,
316+ "dd" : render_material_definition_body ,
313317 "dl" : render_material_definition_list ,
314318 "dt" : render_material_definition_term ,
315- "dd " : render_material_definition_body ,
319+ "text " : _render_text ,
316320 # Math support (from mdit-py-plugins)
317321 DOLLARMATH_INLINE : _render_math_inline ,
318322 DOLLARMATH_BLOCK : _render_math_block ,
@@ -330,10 +334,15 @@ def render_pymd_caption(node: RenderTreeNode, context: RenderContext) -> str:
330334}
331335
332336
333- normalize_list = partial (
334- unbounded_normalize_list , # type: ignore[has-type]
335- check_if_align_semantic_breaks_in_lists = cli_is_align_semantic_breaks_in_lists ,
336- )
337+ if TYPE_CHECKING :
338+ normalize_list : Postprocess
339+ postprocess_list_wrap : Postprocess
340+ else :
341+ normalize_list = partial (
342+ unbounded_normalize_list ,
343+ check_if_align_semantic_breaks_in_lists = cli_is_align_semantic_breaks_in_lists ,
344+ )
345+ postprocess_list_wrap = _postprocess_list_wrap
337346
338347# A mapping from `RenderTreeNode.type` to a `Postprocess` that does
339348# postprocessing for the output of the `Render` function. Unlike
@@ -342,7 +351,7 @@ def render_pymd_caption(node: RenderTreeNode, context: RenderContext) -> str:
342351# will run in series.
343352POSTPROCESSORS : Mapping [str , Postprocess ] = {
344353 "bullet_list" : normalize_list ,
345- "inline" : postprocess_list_wrap , # type: ignore[has-type]
354+ "inline" : postprocess_list_wrap ,
346355 "ordered_list" : normalize_list ,
347356 "paragraph" : escape_deflist ,
348357}
0 commit comments