Improving Pygments Code Block Display
Using Pygments, CSS and Ruff to improve how code blocks are displayed on my daily notebook blog.
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
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
(#50) [Path('2025-01-26-Improving-Pygments-Code-Block-Display.ipynb'),Path('2025-01-25-This-Site-Is-Now-Powered-by-This-Notebook-Part-2.ipynb'),Path('2025-01-24-Creating-In-Notebook-Images-for-Social-Media-With-PIL-Pillow.ipynb'),Path('2025-01-23-Troubleshooting-MonsterUI-on-This-Site.ipynb'),Path('2025-01-23-This-Site-Is-Now-Powered-by-This-Notebook.ipynb'),Path('2025-01-22-MonsterUI-Buttons-and-Links.ipynb'),Path('2025-01-22-Customizing-FastHTML-Headers-From-Notebook-Contents.ipynb'),Path('2025-01-21-SVG-Animation-in-FastHTML.ipynb'),Path('2025-01-20-Dark-and-Light-Mode-in-FastHTML.ipynb'),Path('2025-01-19-Genanki-and-fastcore.ipynb'),Path('2025-01-18-Alarm-Sounds-App.ipynb'),Path('2025-01-17-Alarm-Clock-Sounds.ipynb'),Path('2025-01-16-Cosine-Similarity-Breakdown-in-LaTeX.ipynb'),Path('2025-01-14-Constructing-SQLite-Tables-for-Notebooks-and-Search.ipynb'),Path('2025-01-13-SQLite-FTS5-Tokenizers-unicode61-and-ascii.ipynb'),Path('2025-01-12-A-Better-Notebook-Index-Page.ipynb'),Path('2025-01-11-NBClassic-Keyboard-Shortcuts-in-Command-and-Dual-Mode.ipynb'),Path('2025-01-10-Understanding-FastHTML-Routes-Requests-and-Redirects.ipynb'),Path('2025-01-09-Reading-and-Writing-Jupyter-Notebooks-With-Python.ipynb'),Path('2025-01-08-HTML-Title-Tag-in-FastHTML.ipynb')...]
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'}
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>
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)
notes = L(['Magandang hapon', 'Good afternoon'],
['Magandang gabi', 'Good evening'], ['Paalam',
'Goodbye']) notes
wrapped_text = wrap_text(c.source, width=43)
print(wrapped_text)
notes = L(['Magandang hapon', 'Good
afternoon'], ['Magandang gabi', 'Good
evening'], ['Paalam', 'Goodbye']) notes
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.