Skip to content

Commit e7f2228

Browse files
oprypinpawamoy
andauthored
feat: Support [identifier][] with pymdownx.inlinehilite enabled
Issue-#34: #34 PR-#40: #40 Co-authored-by: Timothée Mazzucotelli <dev@pawamoy.fr>
1 parent 143d768 commit e7f2228

File tree

3 files changed

+46
-12
lines changed

3 files changed

+46
-12
lines changed

pyproject.toml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ classifiers = [
3434
]
3535
dependencies = [
3636
"Markdown>=3.3",
37+
"markupsafe>=2.0.1",
3738
"mkdocs>=1.1",
3839
]
3940

@@ -86,6 +87,8 @@ quality = [
8687
"ruff>=0.0",
8788
]
8889
tests = [
90+
"pygments>=2.16",
91+
"pymdown-extensions>=10.0",
8992
"pytest>=7.4",
9093
"pytest-cov>=4.1",
9194
"pytest-randomly>=3.15",

src/mkdocs_autorefs/references.py

Lines changed: 19 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -4,13 +4,14 @@
44

55
import re
66
from html import escape, unescape
7-
from typing import TYPE_CHECKING, Any, Callable, Match, Tuple
7+
from typing import TYPE_CHECKING, Any, Callable, Match
88
from urllib.parse import urlsplit
99
from xml.etree.ElementTree import Element
1010

11+
import markupsafe
1112
from markdown.extensions import Extension
1213
from markdown.inlinepatterns import REFERENCE_RE, ReferenceInlineProcessor
13-
from markdown.util import INLINE_PLACEHOLDER_RE
14+
from markdown.util import HTML_PLACEHOLDER_RE, INLINE_PLACEHOLDER_RE
1415

1516
if TYPE_CHECKING:
1617
from markdown import Markdown
@@ -24,8 +25,6 @@
2425
in the [`on_post_page` hook][mkdocs_autorefs.plugin.AutorefsPlugin.on_post_page].
2526
"""
2627

27-
EvalIDType = Tuple[Any, Any, Any]
28-
2928

3029
class AutoRefInlineProcessor(ReferenceInlineProcessor):
3130
"""A Markdown extension."""
@@ -36,7 +35,7 @@ def __init__(self, *args: Any, **kwargs: Any) -> None: # noqa: D107
3635
# Code based on
3736
# https://github.com/Python-Markdown/markdown/blob/8e7528fa5c98bf4652deb13206d6e6241d61630b/markdown/inlinepatterns.py#L780
3837

39-
def handleMatch(self, m: Match[str], data: Any) -> Element | EvalIDType: # type: ignore[override] # noqa: N802
38+
def handleMatch(self, m: Match[str], data: str) -> tuple[Element | None, int | None, int | None]: # type: ignore[override] # noqa: N802
4039
"""Handle an element that matched.
4140
4241
Arguments:
@@ -51,7 +50,7 @@ def handleMatch(self, m: Match[str], data: Any) -> Element | EvalIDType: # type
5150
return None, None, None
5251

5352
identifier, end, handled = self.evalId(data, index, text)
54-
if not handled:
53+
if not handled or identifier is None:
5554
return None, None, None
5655

5756
if re.search(r"[/ \x00-\x1f]", identifier):
@@ -61,9 +60,9 @@ def handleMatch(self, m: Match[str], data: Any) -> Element | EvalIDType: # type
6160
# but references with Markdown formatting are not possible anyway.
6261
return None, m.start(0), end
6362

64-
return self.makeTag(identifier, text), m.start(0), end
63+
return self._make_tag(identifier, text), m.start(0), end
6564

66-
def evalId(self, data: str, index: int, text: str) -> EvalIDType: # noqa: N802 (parent's casing)
65+
def evalId(self, data: str, index: int, text: str) -> tuple[str | None, int, bool]: # noqa: N802 (parent's casing)
6766
"""Evaluate the id portion of `[ref][id]`.
6867
6968
If `[ref][]` use `[ref]`.
@@ -86,13 +85,22 @@ def evalId(self, data: str, index: int, text: str) -> EvalIDType: # noqa: N802
8685
# Allow the entire content to be one placeholder, with the intent of catching things like [`Foo`][].
8786
# It doesn't catch [*Foo*][] though, just due to the priority order.
8887
# https://github.com/Python-Markdown/markdown/blob/1858c1b601ead62ed49646ae0d99298f41b1a271/markdown/inlinepatterns.py#L78
89-
if INLINE_PLACEHOLDER_RE.fullmatch(identifier):
90-
identifier = self.unescape(identifier)
88+
if match := INLINE_PLACEHOLDER_RE.fullmatch(identifier):
89+
stashed_nodes: dict[str, Element | str] = self.md.treeprocessors["inline"].stashed_nodes # type: ignore[attr-defined]
90+
el = stashed_nodes.get(match[1])
91+
if isinstance(el, Element) and el.tag == "code":
92+
identifier = "".join(el.itertext())
93+
# Special case: allow pymdownx.inlinehilite raw <code> snippets but strip them back to unhighlighted.
94+
if match := HTML_PLACEHOLDER_RE.fullmatch(identifier):
95+
stash_index = int(match.group(1))
96+
html = self.md.htmlStash.rawHtmlBlocks[stash_index]
97+
identifier = markupsafe.Markup(html).striptags()
98+
self.md.htmlStash.rawHtmlBlocks[stash_index] = escape(identifier)
9199

92100
end = m.end(0)
93101
return identifier, end, True
94102

95-
def makeTag(self, identifier: str, text: str) -> Element: # type: ignore[override] # noqa: N802
103+
def _make_tag(self, identifier: str, text: str) -> Element:
96104
"""Create a tag that can be matched by `AUTO_REF_RE`.
97105
98106
Arguments:

tests/test_references.py

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22

33
from __future__ import annotations
44

5+
from typing import Mapping
6+
57
import markdown
68
import pytest
79

@@ -44,6 +46,7 @@ def run_references_test(
4446
output: str,
4547
unmapped: list[str] | None = None,
4648
from_url: str = "page.html",
49+
extensions: Mapping = {},
4750
) -> None:
4851
"""Help running tests about references.
4952
@@ -54,7 +57,7 @@ def run_references_test(
5457
unmapped: The expected unmapped list.
5558
from_url: The source page URL.
5659
"""
57-
md = markdown.Markdown(extensions=[AutorefsExtension()])
60+
md = markdown.Markdown(extensions=[AutorefsExtension(), *extensions], extension_configs=extensions)
5861
content = md.convert(source)
5962

6063
def url_mapper(identifier: str) -> str:
@@ -92,6 +95,26 @@ def test_reference_implicit_with_code() -> None:
9295
)
9396

9497

98+
def test_reference_implicit_with_code_inlinehilite_plain() -> None:
99+
"""Check implicit references (identifier in backticks, wrapped by inlinehilite)."""
100+
run_references_test(
101+
extensions={"pymdownx.inlinehilite": {}},
102+
url_map={"pathlib.Path": "pathlib.html#Path"},
103+
source="This [`pathlib.Path`][].",
104+
output='<p>This <a class="autorefs autorefs-internal" href="pathlib.html#Path"><code>pathlib.Path</code></a>.</p>',
105+
)
106+
107+
108+
def test_reference_implicit_with_code_inlinehilite_python() -> None:
109+
"""Check implicit references (identifier in backticks, syntax-highlighted by inlinehilite)."""
110+
run_references_test(
111+
extensions={"pymdownx.inlinehilite": {"style_plain_text": "python"}, "pymdownx.highlight": {}},
112+
url_map={"pathlib.Path": "pathlib.html#Path"},
113+
source="This [`pathlib.Path`][].",
114+
output='<p>This <a class="autorefs autorefs-internal" href="pathlib.html#Path"><code class="highlight">pathlib.Path</code></a>.</p>',
115+
)
116+
117+
95118
def test_reference_with_punctuation() -> None:
96119
"""Check references with punctuation."""
97120
run_references_test(

0 commit comments

Comments
 (0)