From a70398e5f7a0147a5e40f32494912c8447bc0265 Mon Sep 17 00:00:00 2001 From: cjchanh <248582807+cjchanh@users.noreply.github.com> Date: Wed, 19 Aug 2026 03:27:52 -0600 Subject: [PATCH 1/2] =?UTF-8?q?=E2=9C=A8=20Add=20`highlight=5Fverbatim`=20?= =?UTF-8?q?option=20to=20skip=20the=20
=20wrapper=20(#256)?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 CHANGELOG.md                              |   4 +
 docs/using.md                             |  39 ++++++++
 markdown_it/renderer.py                   |  11 ++-
 markdown_it/utils.py                      |  14 +++
 tests/test_api/test_highlight_verbatim.py | 112 ++++++++++++++++++++++
 5 files changed, 177 insertions(+), 3 deletions(-)
 create mode 100644 tests/test_api/test_highlight_verbatim.py

diff --git a/CHANGELOG.md b/CHANGELOG.md
index 4d50cb3e..6de0c3b9 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,5 +1,9 @@
 # Change Log
 
+## Unreleased
+
+* ✨ Add `highlight_verbatim` option to pass highlighter output through verbatim, without the `
` wrapper, in [#256](https://github.com/executablebooks/markdown-it-py/issues/256)
+
 ## 4.2.0 - 2026-05-07
 
 * ✨ Add `make_fence_rule()` factory for configurable fence markers in [#394](https://github.com/executablebooks/markdown-it-py/pull/394)
diff --git a/docs/using.md b/docs/using.md
index 507f49c1..557cc7c9 100644
--- a/docs/using.md
+++ b/docs/using.md
@@ -303,6 +303,45 @@ def function(renderer, tokens, idx, options, env):
 
 +++
 
+### Code highlighting
+
+Fenced code blocks are rendered as `
...
` by default. +You can customize this with the `highlight` option, which should be a function +`(content, lang, attrs) -> str` returning escaped HTML: + +```python +from markdown_it import MarkdownIt + +def highlight(content, lang, attrs): + return f'{content}' + +md = MarkdownIt("commonmark", {"highlight": highlight}) +md.render("```python\nprint('hi')\n```") +``` + +If the highlighter returns a string starting with `...
`. + +If your highlighter produces a complete block of HTML that does not start +with `` wrapper, or a `
` with custom
+attributes), set the Python-only `highlight_verbatim` option to `True` to
+skip the `
` wrapper entirely and pass the highlighter output
+through verbatim (a trailing newline is added, as with the pre-continues
+heuristic):
+
+```python
+md = MarkdownIt("commonmark", {"highlight": highlight, "highlight_verbatim": True})
+md.render("```python\nprint('hi')\n```")
+```
+
+Note that `highlight_verbatim` only applies when the `highlight` function
+returns a non-empty string; if it returns an empty string (or `None`), the
+content is escaped and wrapped in `
` as usual.
+
++++
+
 You can inject render methods into the instantiated render class.
 
 ```{jupyter-execute}
diff --git a/markdown_it/renderer.py b/markdown_it/renderer.py
index f690b091..160b1136 100644
--- a/markdown_it/renderer.py
+++ b/markdown_it/renderer.py
@@ -277,9 +277,14 @@ def fence(
                 langAttrs = arr[1]
 
         if options.highlight:
-            highlighted = options.highlight(
-                token.content, langName, langAttrs
-            ) or escapeHtml(token.content)
+            highlighted = options.highlight(token.content, langName, langAttrs)
+            if highlighted:
+                if options.get("highlight_verbatim", False):
+                    # Pass the highlighter output through verbatim, without
+                    # the 
 wrapper.
+                    return highlighted + "\n"
+            else:
+                highlighted = escapeHtml(token.content)
         else:
             highlighted = escapeHtml(token.content)
 
diff --git a/markdown_it/utils.py b/markdown_it/utils.py
index 09e60163..cbd1d321 100644
--- a/markdown_it/utils.py
+++ b/markdown_it/utils.py
@@ -36,6 +36,20 @@ class OptionsType(TypedDict):
     """CSS language prefix for fenced blocks."""
     highlight: Callable[[str, str, str], str] | None
     """Highlighter function: (content, lang, attrs) -> str."""
+    highlight_verbatim: NotRequired[bool]
+    """Pass highlighter output through verbatim, without the ``
`` wrapper.
+
+    When ``True`` and the ``highlight`` function returns a non-empty string,
+    the returned HTML is used as-is (a trailing newline is added), instead of
+    being wrapped in ``
...
``. This is useful when the + highlighter produces a complete block of HTML (e.g. starting with a + ``
`` or a ``
`` with custom attributes) that should not be
+    wrapped again.
+
+    This is a Python only option. The default is ``False``, in which case the
+    output is only passed through verbatim if it already starts with `` MarkdownIt:
+    return MarkdownIt("commonmark", {"highlight": highlight, **options})
+
+
+def test_default_wraps_non_pre_output():
+    """Default behavior: highlighter output not starting with 
{content}
" + + md = _md(highlight) + assert md.render("```python\nhl\n```") == ( + "
hl\n
\n" + ) + + +def test_default_pre_continues_heuristic(): + """Default behavior: output starting with
{content}
" + + md = _md(highlight) + assert md.render("```python\nhl\n```") == "
hl\n
\n" + + +def test_verbatim_passes_through_non_pre_output(): + """With highlight_verbatim, non-
{content}"
+
+    md = _md(highlight, highlight_verbatim=True)
+    assert md.render("```python\nhl\n```") == "
hl\n
\n" + + +def test_verbatim_passes_through_pre_output(): + """With highlight_verbatim,
{content}
" + + md = _md(highlight, highlight_verbatim=True) + assert md.render("```python\nhl\n```") == "
hl\n
\n" + + +def test_verbatim_skips_lang_class_injection(): + """With highlight_verbatim, no language class is injected into the output.""" + + def highlight(content, lang, attrs): + assert lang == "python" + return f"
{content}
" + + md = _md(highlight, highlight_verbatim=True) + assert md.render("```python\nhl\n```") == "
hl\n
\n" + + +def test_verbatim_falls_back_when_highlighter_returns_empty(): + """Falsy highlighter output still falls back to the escaped
 wrapper."""
+
+    def highlight(content, lang, attrs):
+        return ""
+
+    md = _md(highlight, highlight_verbatim=True)
+    assert md.render("```python\nhl\n```") == (
+        '
hl\n
\n' + ) + + +def test_verbatim_without_highlighter_uses_default_wrapper(): + """Without a highlighter, highlight_verbatim has no effect.""" + + md = _md(None, highlight_verbatim=True) + assert md.render("```python\nhl\n```") == ( + '
hl\n
\n' + ) + + +def test_verbatim_can_be_set_after_construction(): + """The option can be toggled on the instance after construction.""" + + def highlight(content, lang, attrs): + return f"
{content}
" + + md = _md(highlight) + assert md.render("```\nhl\n```") == "
hl\n
\n" + md.options["highlight_verbatim"] = True + assert md.render("```\nhl\n```") == "
hl\n
\n" + md.options["highlight_verbatim"] = False + assert md.render("```\nhl\n```") == "
hl\n
\n" + + +def test_verbatim_output_ends_with_newline(): + """A trailing newline is added, matching the pre-continues heuristic.""" + + def highlight(content, lang, attrs): + return "
hl
" # no trailing newline + + md = _md(highlight, highlight_verbatim=True) + assert md.render("```\nhl\n```") == "
hl
\n" From e077639300ef0e4d0eef1de1997f8d8342c58241 Mon Sep 17 00:00:00 2001 From: cjchanh <248582807+cjchanh@users.noreply.github.com> Date: Wed, 19 Aug 2026 14:07:32 -0600 Subject: [PATCH 2/2] =?UTF-8?q?=F0=9F=90=9B=20FIX:=20highlight=5Fverbatim?= =?UTF-8?q?=20is=20byte-exact=20=E2=80=94=20no=20renderer-added=20newline?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The verbatim path appended "\n" to the highlighter's return, breaking byte-exactness (the very contract of the option). Verbatim now returns the highlighter's bytes untouched; four tests that pinned the added newline are corrected, and a byte-exactness regression test added. --- markdown_it/renderer.py | 6 ++-- tests/test_api/test_highlight_verbatim.py | 44 +++++++++++++++++++---- 2 files changed, 40 insertions(+), 10 deletions(-) diff --git a/markdown_it/renderer.py b/markdown_it/renderer.py index 160b1136..e7202339 100644 --- a/markdown_it/renderer.py +++ b/markdown_it/renderer.py @@ -280,9 +280,9 @@ def fence( highlighted = options.highlight(token.content, langName, langAttrs) if highlighted: if options.get("highlight_verbatim", False): - # Pass the highlighter output through verbatim, without - # the
 wrapper.
-                    return highlighted + "\n"
+                    # Pass the highlighter output through verbatim —
+                    # byte-exact, no wrapper, no added newline.
+                    return highlighted
             else:
                 highlighted = escapeHtml(token.content)
         else:
diff --git a/tests/test_api/test_highlight_verbatim.py b/tests/test_api/test_highlight_verbatim.py
index 384dbff8..de3ace0c 100644
--- a/tests/test_api/test_highlight_verbatim.py
+++ b/tests/test_api/test_highlight_verbatim.py
@@ -43,7 +43,7 @@ def highlight(content, lang, attrs):
         return f"
{content}
" md = _md(highlight, highlight_verbatim=True) - assert md.render("```python\nhl\n```") == "
hl\n
\n" + assert md.render("```python\nhl\n```") == "
hl\n
" def test_verbatim_passes_through_pre_output(): @@ -53,7 +53,7 @@ def highlight(content, lang, attrs): return f"
{content}
" md = _md(highlight, highlight_verbatim=True) - assert md.render("```python\nhl\n```") == "
hl\n
\n" + assert md.render("```python\nhl\n```") == "
hl\n
" def test_verbatim_skips_lang_class_injection(): @@ -64,7 +64,7 @@ def highlight(content, lang, attrs): return f"
{content}
" md = _md(highlight, highlight_verbatim=True) - assert md.render("```python\nhl\n```") == "
hl\n
\n" + assert md.render("```python\nhl\n```") == "
hl\n
" def test_verbatim_falls_back_when_highlighter_returns_empty(): @@ -97,16 +97,46 @@ def highlight(content, lang, attrs): md = _md(highlight) assert md.render("```\nhl\n```") == "
hl\n
\n" md.options["highlight_verbatim"] = True - assert md.render("```\nhl\n```") == "
hl\n
\n" + assert md.render("```\nhl\n```") == "
hl\n
" md.options["highlight_verbatim"] = False assert md.render("```\nhl\n```") == "
hl\n
\n" -def test_verbatim_output_ends_with_newline(): - """A trailing newline is added, matching the pre-continues heuristic.""" +def test_verbatim_preserves_absence_of_trailing_newline(): + """No trailing newline is added — verbatim is byte-exact. + + The pre-continues heuristic path keeps its historical ``+ "\\n"`` + (wrapped blocks are renderer-owned); the verbatim path returns the + highlighter's bytes untouched, newline or none. + """ def highlight(content, lang, attrs): return "
hl
" # no trailing newline md = _md(highlight, highlight_verbatim=True) - assert md.render("```\nhl\n```") == "
hl
\n" + assert md.render("```\nhl\n```") == "
hl
" + + +def test_verbatim_is_byte_exact(): + """Verbatim means byte-exact: render output == highlighter return. + + Regression for the trailing-newline defect (matrix-03): with + highlight_verbatim, the renderer must not append, strip, or alter + a single byte of the highlighter's return value — with or without + a trailing newline. + """ + + def hl_no_nl(content, lang, attrs): + return "
no-newline
" + + def hl_with_nl(content, lang, attrs): + return "
with-newline
\n" + + assert ( + _md(hl_no_nl, highlight_verbatim=True).render("```x\nc\n```") + == "
no-newline
" + ) + assert ( + _md(hl_with_nl, highlight_verbatim=True).render("```x\nc\n```") + == "
with-newline
\n" + )