1414from __future__ import annotations
1515
1616import os
17- import sys
17+ import re
18+ from re import Match
1819from typing import TYPE_CHECKING , Any
1920from warnings import catch_warnings , simplefilter
2021
2122from mkdocs .config import Config
2223from mkdocs .config import config_options as opt
23- from mkdocs .plugins import BasePlugin
24+ from mkdocs .plugins import BasePlugin , CombinedEvent , event_priority
2425from mkdocs .utils import write_file
2526from mkdocs_autorefs import AutorefsConfig , AutorefsPlugin
2627
2728from mkdocstrings ._internal .extension import MkdocstringsExtension
2829from mkdocstrings ._internal .handlers .base import BaseHandler , Handlers
2930from mkdocstrings ._internal .loggers import get_logger
3031
31- if sys .version_info < (3 , 10 ):
32- pass
33- else :
34- pass
35-
3632if TYPE_CHECKING :
3733 from jinja2 .environment import Environment
3834 from mkdocs .config .defaults import MkDocsConfig
35+ from mkdocs .structure .files import Files
3936
4037
4138_logger = get_logger (__name__ )
@@ -148,6 +145,7 @@ def on_config(self, config: MkDocsConfig) -> MkDocsConfig | None:
148145
149146 handlers ._download_inventories ()
150147
148+ AutorefsPlugin .record_backlinks = True
151149 autorefs : AutorefsPlugin
152150 try :
153151 # If autorefs plugin is explicitly enabled, just use it.
@@ -170,6 +168,7 @@ def on_config(self, config: MkDocsConfig) -> MkDocsConfig | None:
170168
171169 config .extra_css .insert (0 , self .css_filename ) # So that it has lower priority than user files.
172170
171+ self ._autorefs = autorefs
173172 self ._handlers = handlers
174173 return config
175174
@@ -194,29 +193,65 @@ def plugin_enabled(self) -> bool:
194193 """
195194 return self .config .enabled
196195
197- def on_env (self , env : Environment , config : MkDocsConfig , * args : Any , ** kwargs : Any ) -> None : # noqa: ARG002
198- """Extra actions that need to happen after all Markdown rendering and before HTML rendering.
199-
200- Hook for the [`on_env` event](https://www.mkdocs.org/user-guide/plugins/#on_env).
201-
202- - Write mkdocstrings' extra files into the site dir.
203- - Gather results from background inventory download tasks.
204- """
205- if not self .plugin_enabled :
206- return
196+ @event_priority (50 ) # Early, before autorefs' starts applying cross-refs and collecting backlinks.
197+ def _on_env_load_inventories (self , env : Environment , config : MkDocsConfig , * args : Any , ** kwargs : Any ) -> None : # noqa: ARG002
198+ if self .plugin_enabled and self ._handlers :
199+ register = config .plugins ["autorefs" ].register_url # type: ignore[attr-defined]
200+ for identifier , url in self ._handlers ._yield_inventory_items ():
201+ register (identifier , url )
207202
208- if self ._handlers :
203+ @event_priority (- 20 ) # Late, not important.
204+ def _on_env_add_css (self , env : Environment , config : MkDocsConfig , * args : Any , ** kwargs : Any ) -> None : # noqa: ARG002
205+ if self .plugin_enabled and self ._handlers :
209206 css_content = "\n " .join (handler .extra_css for handler in self .handlers .seen_handlers )
210207 write_file (css_content .encode ("utf-8" ), os .path .join (config .site_dir , self .css_filename ))
211208
212- if self .inventory_enabled :
213- _logger .debug ("Creating inventory file objects.inv" )
214- inv_contents = self .handlers .inventory .format_sphinx ()
215- write_file (inv_contents , os .path .join (config .site_dir , "objects.inv" ))
209+ @event_priority (- 20 ) # Late, not important.
210+ def _on_env_write_inventory (self , env : Environment , config : MkDocsConfig , * args : Any , ** kwargs : Any ) -> None : # noqa: ARG002
211+ if self .plugin_enabled and self ._handlers and self .inventory_enabled :
212+ _logger .debug ("Creating inventory file objects.inv" )
213+ inv_contents = self .handlers .inventory .format_sphinx ()
214+ write_file (inv_contents , os .path .join (config .site_dir , "objects.inv" ))
216215
217- register = config .plugins ["autorefs" ].register_url # type: ignore[attr-defined]
218- for identifier , url in self ._handlers ._yield_inventory_items ():
219- register (identifier , url )
216+ @event_priority (- 100 ) # Last, after autorefs has finished applying cross-refs and collecting backlinks.
217+ def _on_env_apply_backlinks (self , env : Environment , / , * , config : MkDocsConfig , files : Files ) -> Environment : # noqa: ARG002
218+ regex = re .compile (r"<backlinks\s+identifier=\"([^\"]+)\"\s+handler=\"([^\"]+)\"\s*/?>" )
219+
220+ def repl (match : Match ) -> str :
221+ handler_name = match .group (2 )
222+ handler = self .handlers .get_handler (handler_name )
223+
224+ # The handler doesn't implement backlinks,
225+ # return early to avoid computing them.
226+ if handler .render_backlinks .__func__ is BaseHandler .render_backlinks : # type: ignore[attr-defined]
227+ return ""
228+
229+ identifier = match .group (1 )
230+ aliases = handler .get_aliases (identifier )
231+ backlinks = self ._autorefs .get_backlinks (identifier , * aliases , from_url = file .page .url ) # type: ignore[union-attr]
232+
233+ # No backlinks, avoid calling the handler's method.
234+ if not backlinks :
235+ return ""
236+
237+ return handler .render_backlinks (backlinks )
238+
239+ for file in files :
240+ if file .page and file .page .content :
241+ _logger .debug ("Applying backlinks in page %s" , file .page .file .src_path )
242+ file .page .content = regex .sub (repl , file .page .content )
243+
244+ return env
245+
246+ on_env = CombinedEvent (_on_env_load_inventories , _on_env_add_css , _on_env_write_inventory , _on_env_apply_backlinks )
247+ """Extra actions that need to happen after all Markdown-to-HTML page rendering.
248+
249+ Hook for the [`on_env` event](https://www.mkdocs.org/user-guide/plugins/#on_env).
250+
251+ - Gather results from background inventory download tasks.
252+ - Write mkdocstrings' extra files (CSS, inventory) into the site directory.
253+ - Apply backlinks to the HTML output of each page.
254+ """
220255
221256 def on_post_build (
222257 self ,
0 commit comments