Skip to content

Commit

Permalink
Add the ability to set pygments lexer options via traitlets
Browse files Browse the repository at this point in the history
  • Loading branch information
peytondmurray committed Jan 23, 2025
1 parent 2302544 commit e4d3cf1
Show file tree
Hide file tree
Showing 4 changed files with 38 additions and 7 deletions.
10 changes: 10 additions & 0 deletions nbconvert/exporters/html.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
from bs4 import BeautifulSoup
from jupyter_core.paths import jupyter_path
from traitlets import Bool, Unicode, default, validate
from traitlets import Dict as TraitletsDict
from traitlets.config import Config

if tuple(int(x) for x in jinja2.__version__.split(".")[:3]) < (3, 0, 0):
Expand Down Expand Up @@ -183,6 +184,14 @@ def _template_name_default(self):

output_mimetype = "text/html"

lexer_options = TraitletsDict(
{},
help=(
"Options to be passed to the pygments lexer for highlighting markdown code blocks. "
"See https://pygments.org/docs/lexers/#available-lexers for available options."
),
).tag(config=True)

@property
def default_config(self):
c = Config(
Expand Down Expand Up @@ -239,6 +248,7 @@ def markdown2html(self, context, source):
path=path,
anchor_link_text=self.anchor_link_text,
exclude_anchor_links=self.exclude_anchor_links,
**self.lexer_options,
)
return MarkdownWithMath(renderer=renderer).render(source)

Expand Down
10 changes: 8 additions & 2 deletions nbconvert/filters/highlight.py
Original file line number Diff line number Diff line change
Expand Up @@ -136,7 +136,9 @@ def __call__(self, source, language=None, metadata=None, strip_verbatim=False):
return latex


def _pygments_highlight(source, output_formatter, language="ipython", metadata=None):
def _pygments_highlight(
source, output_formatter, language="ipython", metadata=None, **lexer_options
):
"""
Return a syntax-highlighted version of the input source
Expand All @@ -149,6 +151,10 @@ def _pygments_highlight(source, output_formatter, language="ipython", metadata=N
language to highlight the syntax of
metadata : NotebookNode cell metadata
metadata of the cell to highlight
lexer_options : dict
Options to pass to the pygments lexer. See
https://pygments.org/docs/lexers/#available-lexers for more information about
valid lexer options
"""
from pygments import highlight
from pygments.lexers import get_lexer_by_name
Expand Down Expand Up @@ -179,7 +185,7 @@ def _pygments_highlight(source, output_formatter, language="ipython", metadata=N

if lexer is None:
try:
lexer = get_lexer_by_name(language, stripall=False)
lexer = get_lexer_by_name(language, **lexer_options)
except ClassNotFound:
warn("No lexer found for language %r. Treating as plain text." % language, stacklevel=2)
from pygments.lexers.special import TextLexer
Expand Down
4 changes: 3 additions & 1 deletion nbconvert/filters/markdown_mistune.py
Original file line number Diff line number Diff line change
Expand Up @@ -293,13 +293,15 @@ def __init__(
anchor_link_text: str = "¶",
path: str = "",
attachments: Optional[Dict[str, Dict[str, str]]] = None,
**lexer_options,
):
"""Initialize the renderer."""
super().__init__(escape, allow_harmful_protocols)
self.embed_images = embed_images
self.exclude_anchor_links = exclude_anchor_links
self.anchor_link_text = anchor_link_text
self.path = path
self.lexer_options = lexer_options
if attachments is not None:
self.attachments = attachments
else:
Expand All @@ -317,7 +319,7 @@ def block_code(self, code: str, info: Optional[str] = None) -> str:
try:
if info.strip().split(None, 1):
lang = info.strip().split(maxsplit=1)[0]
lexer = get_lexer_by_name(lang, stripall=False)
lexer = get_lexer_by_name(lang, **self.lexer_options)
except ClassNotFound:
code = f"{lang}\n{code}"
lang = None
Expand Down
21 changes: 17 additions & 4 deletions tests/exporters/test_html.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@

import re

import pytest
from nbformat import v4
from traitlets.config import Config

Expand Down Expand Up @@ -264,7 +265,15 @@ def test_language_code_error(self):
assert '<html lang="en">' in output


def test_syntax_highlight_leading_whitespace():
@pytest.mark.parametrize(
("lexer_options"),
[
{"stripall": True},
{"stripall": False},
{},
],
)
def test_syntax_highlight_leading_whitespace(lexer_options):
"""Test that syntax highlight doesn't strip leading spaces."""
nb = v4.reads(r"""
{
Expand All @@ -291,9 +300,13 @@ def test_syntax_highlight_leading_whitespace():
"nbformat_minor": 5
}
""")
output, _ = HTMLExporter().from_notebook_node(nb)
output, _ = HTMLExporter(lexer_options=lexer_options).from_notebook_node(nb)
# Check that the second code block has the leading spaces
assert "<pre><code> 1+2×⍳3\n3 5 7\n</code></pre>" in output

# Check that the APL-formatted code block has the leading spaces
assert '<span class="w"> </span>' in output
if lexer_options.get("stripall"):
# Check that the APL-formatted code block has leading spaces stripped
assert '<span class="w"> </span>' not in output
else:
# Check that the APL-formatted code block has the leading spaces
assert '<span class="w"> </span>' in output

0 comments on commit e4d3cf1

Please sign in to comment.