from execnb.nbio import read_nb from fasthtml.common import * from fasthtml.jupyter import JupyUvi from fastcore.utils import L import pygments from pygments import highlight from pygments.lexers import PythonLexer from pygments.formatters import HtmlFormatter
audrey.feldroy.com
The experimental notebooks of Audrey M. Roy Greenfeld. This website and all its notebooks are open-source at github.com/audreyfeldroy/audrey.feldroy.com
# Improving Pygments Code Block Display
by Audrey M. Roy Greenfeld | Sun, Jan 26, 2025
Using Pygments, CSS and Ruff to improve how code blocks are displayed on my daily notebook blog.
Listing My Notebooks' File Paths
My new favorite way of grabbing all my notebooks:
- Set
root
based on if I'm in a notebook or not. (IN_NOTEBOOK
comes fromfasthtml.common
) - Get a fastcore
L
list of my notebook paths sorted from newest to oldest
root = Path() if IN_NOTEBOOK else Path("nbs/") nb_paths = L(root.glob("*.ipynb")).sorted(reverse=True) nb_paths
Get Sample Cells to Work With
Here I grab a specific cell of a notebook that looks good to play with.
nb = read_nb(nb_paths[10])
def is_code(c): return c.cell_type=='code' c = L(nb.cells).filter(is_code)[3] c
{ 'cell_type': 'code',
'execution_count': 16,
'id': '00540ad9',
'idx_': 10,
'metadata': {},
'outputs': [ { 'data': { 'text/plain': [ "(#3) [['Magandang hapon', 'Good "
"afternoon'],['Magandang gabi', "
"'Good evening'],['Paalam', "
"'Goodbye']]"]},
'execution_count': 16,
'metadata': {},
'output_type': 'execute_result'}],
'source': "notes = L(['Magandang hapon', 'Good afternoon'],\n"
" ['Magandang gabi', 'Good evening'],\n"
" ['Paalam', 'Goodbye'])\n"
'notes'}
{'cell_type': 'code',
'execution_count': 16,
'id': '00540ad9',
'metadata': {},
'outputs': [{'data': {'text/plain': ["(#3) [['Magandang hapon', 'Good afternoon'],['Magandang gabi', 'Good evening'],['Paalam', 'Goodbye']]"]},
'execution_count': 16,
'metadata': {},
'output_type': 'execute_result'}],
'source': "notes = L(['Magandang hapon', 'Good afternoon'],\n ['Magandang gabi', 'Good evening'],\n ['Paalam', 'Goodbye'])\nnotes",
'idx_': 10}Style the Cell
My FT function for styling cells that I built up to in How I Fixed CSS Scope Leakage in Pygments Syntax Highlighting:
def StyledCode(c, style='monokai'): "A notebook cell styled as code, with style name as its css class for scope limiting" fm = HtmlFormatter(style=style, cssclass=style) h = highlight(c, PythonLexer(), fm) sd = fm.get_style_defs(f".{style}") return Div(Style(sd), NotStr(h), id=style)
show(StyledCode(c.source))
notes = L(['Magandang hapon', 'Good afternoon'], ['Magandang gabi', 'Good evening'], ['Paalam', 'Goodbye']) notes
Pad the Cell
I just want to pad the top and bottom with 10 pixels here.
def StyledCode(c, style='monokai'): "A notebook cell styled as code, with style name as its css class for scope limiting" fm = HtmlFormatter(style=style, cssclass=f"my-{style}", prestyles="padding:10px 0;") h = highlight(c, PythonLexer(), fm) sd = fm.get_style_defs(f".my-{style}") return Style(sd), NotStr(h)
Pygments lets you add inline styles to <pre>
. That's nice.
st, sc = StyledCode(c.source)
Div(sc)
<div><div class="my-monokai"><pre style="padding:10px 0;"><span></span><span class="n">notes</span> <span class="o">=</span> <span class="n">L</span><span class="p">([</span><span class="s1">'Magandang hapon'</span><span class="p">,</span> <span class="s1">'Good afternoon'</span><span class="p">],</span>
<span class="p">[</span><span class="s1">'Magandang gabi'</span><span class="p">,</span> <span class="s1">'Good evening'</span><span class="p">],</span>
<span class="p">[</span><span class="s1">'Paalam'</span><span class="p">,</span> <span class="s1">'Goodbye'</span><span class="p">])</span>
<span class="n">notes</span>
</pre></div>
</div>
div(('notes = L(['Magandang hapon', 'Good afternoon'],\n ['Magandang gabi', 'Good evening'],\n ['Paalam', 'Goodbye'])\nnotes\n
show(StyledCode(c.source))
notes = L(['Magandang hapon', 'Good afternoon'], ['Magandang gabi', 'Good evening'], ['Paalam', 'Goodbye']) notes
Success: the code is padded on top and bottom with 10px now.
Limiting Line Length on Mobile Devices
On my phone I counted that 52 characters is the maximum I can read on a line of code, before having to scroll. Let's see if Ruff's textwrap breaks lines in a way that keeps Python code valid.
def wrap_text(text, width=52): wrapper = textwrap.TextWrapper(width=width) wrapped_text = wrapper.fill(text) return wrapped_text
wrapped_text = wrap_text(c.source) print(wrapped_text)
wrapped_text = wrap_text(c.source, width=43) print(wrapped_text)
Next Steps
If I continue this approach, the next steps will be:
- Find another way to wrap lines while ensuring they're still valid Python after wrapping
- Choose line length to return based on User-Agent header
- Put it all together
Or I may just implement the padding and move on. We'll see.
© 2024-2025 Audrey M. Roy Greenfeld