1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180
|
import docutils
from sphinx.util import logging
logger = logging.getLogger(__name__)
class HoverXRefBaseDomain:
hoverxref_types = (
'hoverxref',
'hoverxreftooltip',
'hoverxrefmodal',
)
def _inject_hoverxref_data(self, env, refnode, typ):
from .extension import CSS_CLASSES, CSS_DEFAULT_CLASS
classes = [CSS_DEFAULT_CLASS]
type_class = None
if typ == 'hoverxreftooltip':
type_class = 'tooltip'
elif typ == 'hoverxrefmodal':
type_class = 'modal'
if not type_class:
type_class = env.config.hoverxref_role_types.get(typ)
if not type_class:
default = env.config.hoverxref_default_type
type_class = default
logger.info(
'Using default style (%s) for unknown typ (%s). '
'Define it in hoverxref_role_types.',
default,
typ,
)
# Examples: hxr-tooltip, hxr-modal
classes.append(CSS_CLASSES[type_class])
refnode.replace_attr('classes', classes)
# TODO: log something else here, so we can unique identify this node
logger.debug(
':%s: _hoverxref injected. classes=%s',
typ,
classes,
)
def _is_ignored_ref(self, env, target):
# HACK: skip all references if the builder is non-html. We shouldn't
# have overridden the Domain in first instance at ``setup_domains``
# function, but at that time ``app.builder`` is not yet initialized. If
# we suscribe ourselves to ``builder-initied`` it's too late and our
# override does not take effect. Other builders (e.g. LatexBuilder) may
# fail with internal functions we use (e.g. builder.get_outfilename).
# So, we are skipping it here :(
if env.app.builder.format != 'html':
return True
if target in env.config.hoverxref_ignore_refs:
logger.info(
'Ignoring reference in hoverxref_ignore_refs. target=%s',
target,
)
return True
return False
class HoverXRefPythonDomainMixin(HoverXRefBaseDomain):
def resolve_xref(self, env, fromdocname, builder, typ, target, node, contnode):
refnode = super().resolve_xref(env, fromdocname, builder, typ, target, node, contnode)
if refnode is None:
return refnode
if self._is_ignored_ref(env, target):
return refnode
self._inject_hoverxref_data(env, refnode, typ)
return refnode
class HoverXRefStandardDomainMixin(HoverXRefBaseDomain):
"""
Mixin for ``StandardDomain`` to save the values after the xref resolution.
``:ref:`` are treating as a different node in Sphinx
(``sphinx.addnodes.pending_xref``). These nodes are translated to regular
``docsutils.nodes.reference`` for this domain class.
This class add the required ``hoverxref`` and ``modal``/``tooltip`` to tell
the frontend to show a modal/tooltip on this element.
"""
def resolve_xref(self, env, fromdocname, builder, typ, target, node, contnode):
if typ in self.hoverxref_types:
resolver = self._resolve_ref_xref
return resolver(env, fromdocname, builder, typ, target, node, contnode)
return super().resolve_xref(env, fromdocname, builder, typ, target, node, contnode)
# NOTE: We could override more ``_resolve_xref`` method apply hover in more places
def _resolve_ref_xref(self, env, fromdocname, builder, typ, target, node, contnode):
refnode = super()._resolve_ref_xref(env, fromdocname, builder, typ, target, node, contnode)
if refnode is None:
return refnode
if any([
self._is_ignored_ref(env, target),
not (env.config.hoverxref_auto_ref or typ in self.hoverxref_types)
]):
return refnode
self._inject_hoverxref_data(env, refnode, typ)
return refnode
def _resolve_obj_xref(self, env, fromdocname, builder, typ, target, node, contnode):
refnode = super()._resolve_obj_xref(env, fromdocname, builder, typ, target, node, contnode)
if refnode is None:
return refnode
if any([
self._is_ignored_ref(env, target),
typ not in env.config.hoverxref_roles,
]):
return refnode
self._inject_hoverxref_data(env, refnode, typ)
return refnode
# TODO: combine this method with ``_resolve_obj_xref``
def _resolve_numref_xref(self, env, fromdocname, builder, typ, target, node, contnode):
refnode = super()._resolve_numref_xref(env, fromdocname, builder, typ, target, node, contnode)
if refnode is None:
return refnode
if any([
self._is_ignored_ref(env, target),
typ not in env.config.hoverxref_roles,
]):
return refnode
self._inject_hoverxref_data(env, refnode, typ)
return refnode
class HoverXRefBibtexDomainMixin(HoverXRefBaseDomain):
"""
Mixin for ``BibtexDomain`` to save the values after the xref resolution.
This class add the required ``hoverxref`` and ``modal``/``tooltip`` to tell
the frontend to show a modal/tooltip on this element.
https://github.com/mcmtroffaes/sphinxcontrib-bibtex/blob/2.4.1/src/sphinxcontrib/bibtex/domain.py#L281
"""
def resolve_xref(self, env, fromdocname, builder, typ, target, node, contnode):
textnode = super().resolve_xref(env, fromdocname, builder, typ, target, node, contnode)
if textnode is None:
return textnode
if any([
self._is_ignored_ref(env, target),
not (env.config.hoverxref_auto_ref or typ in self.hoverxref_types or 'cite' in env.config.hoverxref_domains)
]):
return textnode
# The structure of the node generated by bibtex is between two
# ``#text`` nodes and we need to add the classes into the ``reference``
# node to get the ``href=`` attribute from it
#
# (Pdb++) textnode.children
# [<#text: 'Nelson ['>, <reference: <#text: 'Nel87'>>, <#text: ']'>]
refnode_index = textnode.first_child_matching_class(docutils.nodes.reference)
if refnode_index:
refnode = textnode.children[refnode_index]
self._inject_hoverxref_data(env, refnode, typ)
return textnode
|