This notebook shows how to: * Get and use Pygments styles programmatically * Extract and display the source code from Python functions * Apply different Pygments syntax highlighting to different cells of the same notebook with proper CSS scoping * Use Pygments-highlighted code in a FastHTML FastTag
from execnb.nbio import * from fastcore.all import * from fasthtml.common import * from inspect import getsource from IPython.display import display, HTML import pygments from pygments import highlight from pygments.lexers import PythonLexer from pygments.formatters import HtmlFormatter
I was getting all Pygments styles the hard way in my previous notebooks. There's a method for getting the highlight style names via Python:
styles = L(pygments.styles.get_all_styles()) print(styles)
getsource
Let's grab a function to highlight. How about read_nb
from execnb:
rn = getsource(read_nb) rn
We have to print it to see it nicely:
print(rn)
highlight
As in previous posts, we call highlight
to highlight a Python code block like this:
h = highlight(rn, PythonLexer(), HtmlFormatter(style='tango')) h
Then to display that in a notebook:
HTML(h)
Putting highlight
and HTML
into a function together, building up from above:
def show(c): return HTML(highlight(c, PythonLexer(), HtmlFormatter(style='tango')))
show(rn)
To get some source code to highlight without having to read a notebook:
def get_myself(): return getsource(get_myself)
get_myself()
Putting together highlight
, HTML
, and getsource
:
def show(c=None): if not c: c = getsource(show) return HTML(highlight(c, PythonLexer(), HtmlFormatter(style='tango')))
show()
style
ArgI wanted to show my code with a particular Pygments style:
def show(c=None, style='tango'): if not c: c = getsource(show) return HTML(highlight(c, PythonLexer(), HtmlFormatter(style=style)))
show(style='zenburn')
Something's not right here. That showed no colors.
In Pygments, style defs are CSS style definitions:
sd = HtmlFormatter(style='zenburn').get_style_defs() sd[:200]
s = L(sd.splitlines()) s
s[0]
s[6]
s[10]
In style zenburn
, comments are colored in #7f9f7f
. Let's see what this looks like with a Div
FastTag:
cdiv = Div('#7f9f7f', style="background-color:#7f9f7f;") cdiv
HTML(to_xml(cdiv))
def show_color(c): return HTML(to_xml(Div(c, style=f"background-color:{c};")))
Keywords in zenburn
are colored with #efdcbc
:
show_color("#efdcbc")
Putting zenburn
comment and keyword styles in a Style
FastTag:
Style(s[6], s[10])
Recall Pygments highlight
from earlier generates a div
containing pre
full of span
tags:
h = highlight(rn, PythonLexer(), HtmlFormatter(style='tango')) print(h)
This is a nice string of HTML to use with FastTags. I use NotStr
to make it work well with a Div
FastTag:
Div(NotStr(h), id="container")
Adding style:
styled_container = Div(Style(s[6], s[10]), NotStr(h), id="container") styled_container
To display it in-notebook here:
HTML(to_xml(styled_container))
The Pygments get_style_defs
docs say you can specify a CSS selector to prefix styles with:
sd = HtmlFormatter(style='zenburn').get_style_defs('.highlight') sd[:500]
I see all the zenburn style defs with background colors are early on:
Style(sd[:600])
show_color("#484848")
show_color("#353535")
styled_container = Div(Style(sd), NotStr(h), id="container") HTML(to_xml(styled_container))
show
Let's combine everything we've learned into a function:
def show(c=None, style='monokai'): if not c: c = getsource(show) fm = HtmlFormatter(style=style) h = highlight(c, PythonLexer(), fm) sd = fm.get_style_defs('.highlight') styled_container = Div(Style(sd), NotStr(h), id="container") return HTML(to_xml(styled_container))
show(style='monokai')
show(style='lightbulb')
Let's see if we can customize the highlight
class
fm = HtmlFormatter(style='monokai') h = highlight("print('Hi')", PythonLexer(), fm) h
def show(c=None, style='monokai'): if not c: c = getsource(show) fm = HtmlFormatter(style=style) h = highlight(c, PythonLexer(), fm) sd = fm.get_style_defs(f'#{style}') styled_container = Div(Style(sd), NotStr(h), id=style) return HTML(to_xml(styled_container))
show(style='monokai')
show(style='lightbulb')
The above 2 appeared to work correctly, but this didn't, so something's wrong:
show(style='paraiso-light')
print(HtmlFormatter(style='paraiso-light').get_style_defs('#paraiso-light')[:1000])
The background color is supposed to be:
show_color("#a39e9b")
I think get_style_defs('#paraiso-light')
where that ID is on the parent div is too hacky here. I feel like <div class="highlight">
itself should get the ID.
print(HtmlFormatter(style='paraiso-light').get_background_style_defs('#paraiso-light')[:1000])
c = 'print("Hi")' fm = HtmlFormatter(style='paraiso-light', cssclass='audrey') h = highlight(c, PythonLexer(), fm) h
def show(c=None, style='monokai'): if not c: c = getsource(show) fm = HtmlFormatter(style=style, cssclass=style) h = highlight(c, PythonLexer(), fm) sd = fm.get_style_defs(f".{style}") styled_container = Div(Style(sd), NotStr(h), id=style) return HTML(to_xml(styled_container))
show(style='paraiso-light')
show(c="print('Hey')", style="dracula")
show(style="dracula")
show(style="gruvbox-dark")
show(style="solarized-dark")
Success! The cells above are syntax-highlighted without their CSS interfering with each other.
I've created a function for displaying Pygments syntax-highlighted code in Jupyter notebooks with properly-scoped CSS. To do this, I discovered I could:
HtmlFormatter
's cssclass
parameter to change the name of the outer highlight
div to the Pygments style name.get_style_defs
to scope style definitions to that name, to prevent CSS conflictsshow
function for use in future notebooksdef show(c=None, style='monokai'): if not c: c = getsource(show) fm = HtmlFormatter(style=style, cssclass=style) h = highlight(c, PythonLexer(), fm) sd = fm.get_style_defs(f".{style}") styled_container = Div(Style(sd), NotStr(h), id=style) return HTML(to_xml(styled_container))
You can use this to show code blocks in Jupyter notebooks, allowing different Pygments syntax highlighting themes in the same notebook. All without CSS leaking between Pygments styles.