12

Sphinx can define themes as well as a pygments style to use.

I couldn't however - find a good way for a Sphinx project to define a custom style (color scheme) for pygments to use.

From the docs:

To make the style usable for Pygments, you must

  • either register it as a plugin (see the plugin docs)
  • or drop it into the styles subpackage of your Pygments distribution one style class per style, where the file name is the style name and the class name is StylenameClass.

From what I can tell the first option is what I'm after since it should be possible to extend pygments dynamically. Although from checking the link I'm not sure how this would be done (no examples of how to use the plugin system). The second example involves copying files into pygments which isn't practical especially since the path may not be writable by the user.


I did manage to hack in a style, although it's not a nice solution:

including for completeness

# Sphinx "conf.py"

# Master toctree document
master_doc = 'contents'

# BEGIN MONKEY-PATCH
from pygments.style import Style
from pygments.token import Text, Other, Comment, Whitespace

class MyFancyStyle(Style):
    background_color = "#1e1e27"
    default_style = ""
    styles = {
        Text:                      "#cfbfad",
        Other:                     "#cfbfad",
        Whitespace:                "#434357",
        Comment:                   "#cd8b00",
        Comment.Preproc:           "#409090",
        Comment.PreprocFile:       "bg:#404040 #ffcd8b",
        Comment.Special:           "#808bed",
        # ... snip (just more colors, you get the idea) ...
    }


def pygments_monkeypatch_style(mod_name, cls):
    import sys
    import pygments.styles
    cls_name = cls.__name__
    mod = type(__import__("os"))(mod_name)
    setattr(mod, cls_name, cls)
    setattr(pygments.styles, mod_name, mod)
    sys.modules["pygments.styles." + mod_name] = mod
    from pygments.styles import STYLE_MAP
    STYLE_MAP[mod_name] = mod_name + "::" + cls_name


pygments_monkeypatch_style("my_fancy_style", MyFancyStyle)
pygments_style = "my_fancy_style"
# END MONKEY-PATCH
ideasman42
  • 42,413
  • 44
  • 197
  • 320

5 Answers5

4

In your conf.py specify the Pygments style you want to use. From the Sphinx documentation:

pygments_style

The style name to use for Pygments highlighting of source code. If not set, either the theme’s default style or 'sphinx' is selected for HTML output.

Available names can be retrieved by:

>>> from pygments.styles import get_all_styles
>>> styles = list(get_all_styles())

An online preview of some Sphinx theme and Pygments style combinations is available.

If out-of-the-box Pygments styles are not to your liking, then you can create a custom Pygments style.

Steve Piercy
  • 13,693
  • 1
  • 44
  • 57
  • 1
    I've read over the "create a custom Pygments style." docs, but am not sure how to apply this information - How can `conf.py` register a style plugin? - see: http://pygments.org/docs/plugins - there is some non-python text here, so I'm not sure how I would use this in `conf.py`. – ideasman42 Feb 05 '18 at 23:29
  • My original answer which you edited contained relevant information about specifying a Pygments style. You can create a custom style as described in the Pygments docs to which I linked in my answer, and use that style's name in the `conf.py`. Specifying a Pygments style is not a plug-in, but a config value. What is the non-Python text to which you refer? – Steve Piercy Feb 06 '18 at 02:19
  • The question is *"How to include pygments styles"* including a style is quite different from selecting one from a list of presets. That's why the first pat of the original answer isn't relevant. RE: *You can create a custom style as described in the Pygments docs to which I linked in my answer.* --- How? - the docs are quite poor or maybe I'm mis-reading them, the docs say to use the plugin system or to drop them into the pygments module (which is what I'm doing currently - albeit via monkey-patching). – ideasman42 Feb 06 '18 at 03:03
  • 1
    Updated the question since exact method to include a style isn't obvious. – ideasman42 Feb 06 '18 at 03:06
4

This is how I set it up:

Folder Structure:

docs
├── source
│   ├── conf.py <--
│   ├── _pygments
│   │   ├── style.py <--
│   ├── _static
│   ├── ...

conf.py

(at the very top)

import sys, os
sys.path.append(os.path.abspath("./_pygments"))
pygments_style = 'style.MonokaiStyle'
...

style.py

from pygments.style import Style
from pygments.token import Keyword, Name, Comment, String, Error, Text, \
     Number, Operator, Generic, Whitespace, Punctuation, Other, Literal

class MonokaiStyle(Style):
    """
    This style mimics the Monokai color scheme.
    """

    background_color = "#272822"
    highlight_color = "#49483e"

    styles = {
        # No corresponding class for the following:
        Text:                      "#f8f8f2", # class:  ''
        Whitespace:                "",        # class: 'w'
        Error:                     "#960050 bg:#1e0010", # class: 'err'
        Other:                     "",        # class 'x'
        ...
    }

You can choose a predefined style here (as I did) and put the corresponding *.py file from the official pygment repo into the _pygment folder. Or you can define your own style, renaming the class to your liking (don't forget to adapt the import clause in conf.py to the new names)

EDIT: This is an even better resource for style lookup.

glades
  • 3,778
  • 1
  • 12
  • 34
  • 1
    And note there's nothing special about the `_pygments` directory here, you can even put `style.py` in the same directory alongside `conf.py` if you use `sys.path.append(os.path.abspath("."))` – Ahmed Fasih Jan 07 '22 at 06:33
0

I had a similar need, though what I really wanted was to slightly change an existing style (called the base style below). I was able to extent the code in the question for my needs. I post it here for anyone else coming across this problem.

# Sphinx "conf.py"

# Syntax highlighting of code blocks
import pygments.styles, pygments.token
def monkeypatch_pygments(name, base_name='default', attrs={}):
    import importlib, sys
    base_module = importlib.import_module('.'.join(['pygments', 'styles', base_name]))

    def name_to_class_name(name):
        return name.capitalize() + 'Style'
    base_class = getattr(base_module, name_to_class_name(base_name))
    styles = getattr(base_class, 'styles', {}).copy()
    styles.update(attrs.pop('styles', {}))
    attrs['styles'] = styles
    class_name = name_to_class_name(name)
    Style = type(class_name, (base_class,), attrs)
    module = type(base_module)(name)
    setattr(module, class_name, Style)
    setattr(pygments.styles, name, module)
    pygments.styles.STYLE_MAP[name] = f'{name}::{class_name}'
    sys.modules['.'.join(['pygments', 'styles', name])] = module
pygments_style = 'custom'  # Arbitrary name of new style
monkeypatch_pygments(
    pygments_style,
    'friendly',  # Name of base style to use
    {
        # Changes to base style
        'background_color': '#f6f6f6',
        'styles': {
            pygments.token.Comment:       'italic #688F98',
            pygments.token.Name.Variable: '#d27a0a',
        },
    },
)

In the above example, the friendly style is used as the base style. Its 'background_color' and a few items within the 'styles' dictionary are redefined. Note that items not specified in 'styles' will be taken from the base style. The base style itself is not changed.

jmd_dk
  • 12,125
  • 9
  • 63
  • 94
0

The "proper" way to include a pygments style as a plugin is to make a new package and install it via setuptools.

  • Use the usual process to create a setup.cfg (https://setuptools.readthedocs.io/en/latest/userguide/declarative_config.html)

  • Add the following section to it:

    [options.entry_points]
    pygments.styles =
         my_fancy_style = mypackage.mystyle:MyFancyStyle
    
  • Create your style as class MyFancyStyle in mypackage/mystyle.py

  • Install the package: pip install -e .

  • Profit! You can now refer use "my_fancy_style" anywhere a Pygments style is expected. Phew.

Clément
  • 12,299
  • 15
  • 75
  • 115
0

I adopt the solution of Glades and it works well, except in the case that the style-python package is installed (style-1.1.0 in my case). It lead to the ERROR (sorry for the part of french in the ouput) :

Une exception a été levée :
  File "/home/bp/.local/lib/python3.8/site-packages/style/styled_string_builder.py", line 44, in __getattr__
    raise AttributeError('%r object has no attribute %r' % (self.__class__.__name__, attr))
AttributeError: '_StyledStringBuilder' object has no attribute 'MonokaiStyle'

Only solution I find is just to uninstall this package named style :

$ pip uninstall style
bp91
  • 31
  • 2