Source code for pydata_sphinx_theme.pygment

"""Handle pygment css.

inspired by the Furo theme
https://github.com/pradyunsg/furo/blob/main/src/furo/__init__.py
"""

from functools import partial
from pathlib import Path

from pygments.formatters import HtmlFormatter
from pygments.styles import get_all_styles
from sphinx.application import Sphinx

from .utils import get_theme_options_dict, maybe_warn


[docs] def _get_styles(formatter: HtmlFormatter, prefix: str) -> None: """Get styles out of a formatter, where everything has the correct prefix.""" for line in formatter.get_linenos_style_defs(): yield f"{prefix} {line}" yield from formatter.get_background_style_defs(prefix) yield from formatter.get_token_style_defs(prefix)
[docs] def get_pygments_stylesheet(light_style: str, dark_style: str) -> str: """Generate the theme-specific pygments.css. There is no way to tell Sphinx how the theme handles modes. """ light_formatter = HtmlFormatter(style=light_style) dark_formatter = HtmlFormatter(style=dark_style) lines = [] light_prefix = 'html[data-theme="light"] .highlight' lines.extend(_get_styles(light_formatter, prefix=light_prefix)) dark_prefix = 'html[data-theme="dark"] .highlight' lines.extend(_get_styles(dark_formatter, prefix=dark_prefix)) return "\n".join(lines)
[docs] def overwrite_pygments_css(app: Sphinx, exception=None): """Overwrite pygments.css to allow dynamic light/dark switching. Sphinx natively supports config variables `pygments_style` and `pygments_dark_style`. However, quoting from www.sphinx-doc.org/en/master/development/theming.html#creating-themes The pygments_dark_style setting [...is used] when the CSS media query (prefers-color-scheme: dark) evaluates to true. This does not allow for dynamic switching by the user, so at build time we overwrite the pygment.css file so that it embeds 2 versions: - the light theme prefixed with "[data-theme="light"]" - the dark theme prefixed with "[data-theme="dark"]" Fallbacks are defined in this function in case the user-requested (or our theme-specified) pygments theme is not available. """ if exception is not None: return assert app.builder theme_options = get_theme_options_dict(app) warning = partial(maybe_warn, app) pygments_styles = list(get_all_styles()) fallbacks = dict(light="tango", dark="monokai") for light_or_dark, fallback in fallbacks.items(): # make sure our fallbacks work; if not fall(further)back to "default" if fallback not in pygments_styles: fallback = pygments_styles[0] # should resolve to "default" # see if user specified a light/dark pygments theme: style_key = f"pygment_{light_or_dark}_style" style_name = theme_options.get(style_key, None) # if not, use the one we set in `theme.conf`: if style_name is None and hasattr(app.builder, "theme"): style_name = app.builder.theme.get_options()[style_key] # make sure we can load the style if style_name not in pygments_styles: # only warn if user asked for a highlight theme that we can't find if style_name is not None: warning( f"Highlighting style {style_name} not found by pygments, " f"falling back to {fallback}." ) style_name = fallback # assign to the appropriate variable if light_or_dark == "light": light_theme = style_name else: dark_theme = style_name # re-write pygments.css pygment_css = Path(app.builder.outdir) / "_static" / "pygments.css" with pygment_css.open("w") as f: f.write(get_pygments_stylesheet(light_theme, dark_theme))