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.
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 from fasthtml.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><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 ))
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.