summaryrefslogtreecommitdiff
path: root/blog/css-only-code-blocks/index.html
blob: 5b15937b20009eb93240947338fbe84e0cf45886 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
<!DOCTYPE html>
<html><head>
    <meta charset="utf-8">
    <title>Code listings with nice line wrapping and line numbers from plain CSS | Home</title>
    <meta name="description" content="">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <meta name="mobile-web-app-capable" content="yes">
    <meta name="color-scheme" content="dark light">
    <link rel="stylesheet" href="/style.css">
    
    <link rel="preload" href="/fonts/roboto_slab/RobotoSlab-VariableFont_wght.ttf" as="font" type="font/ttf" crossorigin />
    <link rel="preload" href="/fonts/nyght-serif-main/fonts/WEB/NyghtSerif-Regular.woff2" as="font" type="font/woff2" crossorigin />
    <link rel="preload" href="/fonts/nyght-serif-main/fonts/WEB/NyghtSerif-Bold.woff2" as="font" type="font/woff2" crossorigin />
    <link rel="preload" href="/fonts/nyght-serif-main/fonts/WEB/NyghtSerif-BoldItalic.woff2" as="font" type="font/woff2" crossorigin />
</head>
<body><nav>
    <div class="internal">
        
        <a href="/" title="Home">Home</a>
        <a href="/blog/" title="Blog">Blog</a>
        <a href="/projects/" title="Projects">Projects</a>
        <a href="/about/" title="About">About</a>
    </div>
    <div class="search">
        <div id="search"></div>
    </div>
    <div class="external">
        <a href="https://git.jaseg.de/" title="cgit">cgit</a>
        <a href="https://github.com/jaseg" title="Github">Github</a>
        <a href="https://gitlab.com/neinseg" title="Gitlab">Gitlab</a>
        <a href="https://chaos.social/@jaseg" title="Mastodon">Mastodon</a>
    </span>
</nav>

    <header>
        <h1>Code listings with nice line wrapping and line numbers from plain CSS</h1>
<ul class="breadcrumbs">
    <li><a href="/">jaseg.de</a></li>
    <li><a href="/blog/">Blog</a></li><li><a href="/blog/css-only-code-blocks/">Code listings with nice line wrapping and line numbers from plain CSS</a></li>
</ul>
 <strong>2025-07-23</strong>
    </header>
    <main data-pagefind-body>
        <div class="document">


<p>Code listings in web pages are often a bit of a pain to use. Often, they don't wrap on small screens. Also, copy-pasting
code from a code listing often copies the line numbers along with the code. Finally, many implementations use
heavyweight HTML and/or javascript, making them slow to render (looking at you, gitlab).</p>
<p>For this blog, I wrote an implementation that renders HTML code listings entirely without JavaScript, renders line
numbers using plain CSS such that they don't get selected with the code, and that works with the browser to wrap in a
natural way while still supporting the little line continuation arrows that are used to show that a line was soft
wrapped in text editors.</p>
<p>This blog is rendered as a static site using <a class="reference external" href="https://gohugo.io/">Hugo</a> from a pile of <a class="reference external" href="https://www.sphinx-doc.org/en/master/usage/restructuredtext/index.html">RestructuredText</a> documents. RestructuredText renders
code listings using <a class="reference external" href="https://pygments.org/">Pygments</a> by default. Pygments hard-bakes the line numbers into the generated HTML, so I am using a
<a class="reference external" href="https://en.wikipedia.org/wiki/Monkey_patch">monkey-patched</a> hook that changes the line number rendering to just a bunch of empty <tt class="docutils literal">&lt;span&gt;</tt> elements. The resulting
HTML for a code block then looks like this:</p>
<pre class="code html literal-block">
<span class="lineno"></span><span class="line"><span class="p">&lt;</span><span class="nt">pre</span> <span class="na">class</span><span class="o">=</span><span class="s">&quot;code [language] literal-block&quot;</span><span class="p">&gt;</span></span>
<span class="lineno"></span><span class="line">    <span class="p">&lt;</span><span class="nt">span</span> <span class="na">class</span><span class="o">=</span><span class="s">&quot;lineno&quot;</span><span class="p">&gt;&lt;/</span><span class="nt">span</span><span class="p">&gt;</span></span>
<span class="lineno"></span><span class="line">    <span class="p">&lt;</span><span class="nt">span</span> <span class="na">class</span><span class="o">=</span><span class="s">&quot;line&quot;</span><span class="p">&gt;</span></span>
<span class="lineno"></span><span class="line">        <span class="p">&lt;</span><span class="nt">span</span> <span class="na">class</span><span class="o">=</span><span class="s">&quot;[syntax highlight token]&quot;</span><span class="p">&gt;</span>The <span class="p">&lt;/</span><span class="nt">span</span><span class="p">&gt;&lt;</span><span class="nt">span</span> <span class="na">class</span><span class="o">=</span><span class="s">&quot;[other syntax highlight token]&quot;</span><span class="p">&gt;</span>code!<span class="p">&lt;</span><span class="nt">span</span><span class="p">&gt;</span></span>
<span class="lineno"></span><span class="line">    <span class="p">&lt;/</span><span class="nt">span</span><span class="p">&gt;</span></span>
<span class="lineno"></span><span class="line">    <span class="cm">&lt;!-- ... repeat once for each source line. --&gt;</span></span>
<span class="lineno"></span><span class="line"><span class="p">&lt;/</span><span class="nt">pre</span><span class="p">&gt;</span>
</span></pre>
<p>You can find the (rather short) source of the <tt class="docutils literal">rst2html</tt> wrapper <a class="reference external" href="#rst2html-wrapper">below</a>.</p>
<div class="section" id="the-css">
<h2>The CSS</h2>
<p>This modified HTML structure of the code listing gets accompanied by some CSS to make it flow nicely. Here is a listing
of the complete CSS controlling the listing. The only bit that isn't included here is the actual syntax styling rules
for the pygments tokens.</p>
<pre class="code css literal-block">
<span class="lineno"></span><span class="line"><span class="c">/*****************************************************/</span><span class="w"></span></span>
<span class="lineno"></span><span class="line"><span class="w"></span><span class="c">/* Code block formatting / syntax highlighting rules */</span><span class="w"></span></span>
<span class="lineno"></span><span class="line"><span class="w"></span><span class="c">/*****************************************************/</span><span class="w"></span></span>
<span class="lineno"></span><span class="line"><span class="w"></span></span>
<span class="lineno"></span><span class="line"><span class="w"></span><span class="p">.</span><span class="nc">code</span><span class="w"> </span><span class="p">{</span><span class="w"></span></span>
<span class="lineno"></span><span class="line"><span class="w">    </span><span class="k">font-family</span><span class="p">:</span><span class="w"> </span><span class="s2">&quot;Fira Code&quot;</span><span class="p">;</span><span class="w"></span></span>
<span class="lineno"></span><span class="line"><span class="w">    </span><span class="k">font-size</span><span class="p">:</span><span class="w"> </span><span class="mi">13</span><span class="kt">px</span><span class="p">;</span><span class="w"></span></span>
<span class="lineno"></span><span class="line"><span class="w">    </span><span class="k">text-align</span><span class="p">:</span><span class="w"> </span><span class="kc">left</span><span class="p">;</span><span class="w"> </span><span class="c">/* Override default content &quot;justify&quot; alignment */</span><span class="w"></span></span>
<span class="lineno"></span><span class="line"><span class="w">    </span><span class="k">white-space</span><span class="p">:</span><span class="w"> </span><span class="kc">pre-wrap</span><span class="p">;</span><span class="w"></span></span>
<span class="lineno"></span><span class="line"><span class="w">    </span><span class="k">word-wrap</span><span class="p">:</span><span class="w"> </span><span class="kc">break-word</span><span class="p">;</span><span class="w"></span></span>
<span class="lineno"></span><span class="line"><span class="w">    </span><span class="k">overflow-x</span><span class="p">:</span><span class="w"> </span><span class="kc">auto</span><span class="p">;</span><span class="w"></span></span>
<span class="lineno"></span><span class="line"><span class="w">    </span><span class="k">display</span><span class="p">:</span><span class="w"> </span><span class="k">grid</span><span class="p">;</span><span class="w"></span></span>
<span class="lineno"></span><span class="line"><span class="w">    </span><span class="k">align-items</span><span class="p">:</span><span class="w"> </span><span class="kc">start</span><span class="p">;</span><span class="w"></span></span>
<span class="lineno"></span><span class="line"><span class="w">    </span><span class="k">grid-template-columns</span><span class="p">:</span><span class="w"> </span><span class="n">min-content</span><span class="w"> </span><span class="mi">1</span><span class="n">fr</span><span class="p">;</span><span class="w"></span></span>
<span class="lineno"></span><span class="line"><span class="w"></span><span class="p">}</span><span class="w"></span></span>
<span class="lineno"></span><span class="line"><span class="w"></span></span>
<span class="lineno"></span><span class="line"><span class="w"></span><span class="p">.</span><span class="nc">code</span><span class="w"> </span><span class="o">&gt;</span><span class="w"> </span><span class="p">.</span><span class="nc">line</span><span class="w"> </span><span class="p">{</span><span class="w"></span></span>
<span class="lineno"></span><span class="line"><span class="w">    </span><span class="k">padding-left</span><span class="p">:</span><span class="w"> </span><span class="nb">calc</span><span class="p">(</span><span class="mi">2</span><span class="kt">em</span><span class="w"> </span><span class="o">+</span><span class="w"> </span><span class="mi">5</span><span class="kt">px</span><span class="p">);</span><span class="w"></span></span>
<span class="lineno"></span><span class="line"><span class="w">    </span><span class="k">text-indent</span><span class="p">:</span><span class="w"> </span><span class="mi">-2</span><span class="kt">em</span><span class="p">;</span><span class="w"></span></span>
<span class="lineno"></span><span class="line"><span class="w">    </span><span class="k">padding-top</span><span class="p">:</span><span class="w"> </span><span class="mi">2</span><span class="kt">px</span><span class="p">;</span><span class="w"></span></span>
<span class="lineno"></span><span class="line"><span class="w">    </span><span class="k">min-width</span><span class="p">:</span><span class="w"> </span><span class="mi">15</span><span class="kt">em</span><span class="p">;</span><span class="w"></span></span>
<span class="lineno"></span><span class="line"><span class="w"></span><span class="p">}</span><span class="w"></span></span>
<span class="lineno"></span><span class="line"><span class="w"></span></span>
<span class="lineno"></span><span class="line"><span class="w"></span><span class="c">/* Make individual syntax tokens wrap anywhere */</span><span class="w"></span></span>
<span class="lineno"></span><span class="line"><span class="w"></span><span class="p">.</span><span class="nc">code</span><span class="w"> </span><span class="o">&gt;</span><span class="w"> </span><span class="p">.</span><span class="nc">line</span><span class="w"> </span><span class="o">&gt;</span><span class="w"> </span><span class="nt">span</span><span class="w"> </span><span class="p">{</span><span class="w"></span></span>
<span class="lineno"></span><span class="line"><span class="w">    </span><span class="k">overflow-wrap</span><span class="p">:</span><span class="w"> </span><span class="n">anywhere</span><span class="p">;</span><span class="w"></span></span>
<span class="lineno"></span><span class="line"><span class="w">    </span><span class="k">white-space</span><span class="p">:</span><span class="w"> </span><span class="kc">pre-wrap</span><span class="p">;</span><span class="w"></span></span>
<span class="lineno"></span><span class="line"><span class="w"></span><span class="p">}</span><span class="w"></span></span>
<span class="lineno"></span><span class="line"><span class="w"></span></span>
<span class="lineno"></span><span class="line"><span class="w"></span><span class="c">/* We render line numbers in CSS! */</span><span class="w"></span></span>
<span class="lineno"></span><span class="line"><span class="w"></span><span class="p">.</span><span class="nc">code</span><span class="w"> </span><span class="o">&gt;</span><span class="w"> </span><span class="p">.</span><span class="nc">lineno</span><span class="w"> </span><span class="p">{</span><span class="w"></span></span>
<span class="lineno"></span><span class="line"><span class="w">    </span><span class="k">counter-increment</span><span class="p">:</span><span class="w"> </span><span class="n">lineno</span><span class="p">;</span><span class="w"></span></span>
<span class="lineno"></span><span class="line"><span class="w">    </span><span class="k">word-break</span><span class="p">:</span><span class="w"> </span><span class="n">keep-all</span><span class="p">;</span><span class="w"></span></span>
<span class="lineno"></span><span class="line"><span class="w">    </span><span class="k">margin</span><span class="p">:</span><span class="w"> </span><span class="mi">0</span><span class="p">;</span><span class="w"></span></span>
<span class="lineno"></span><span class="line"><span class="w">    </span><span class="k">padding-left</span><span class="p">:</span><span class="w"> </span><span class="mi">15</span><span class="kt">px</span><span class="p">;</span><span class="w"></span></span>
<span class="lineno"></span><span class="line"><span class="w">    </span><span class="k">padding-right</span><span class="p">:</span><span class="w"> </span><span class="mi">5</span><span class="kt">px</span><span class="p">;</span><span class="w"></span></span>
<span class="lineno"></span><span class="line"><span class="w">    </span><span class="k">overflow</span><span class="p">:</span><span class="w"> </span><span class="kc">clip</span><span class="p">;</span><span class="w"></span></span>
<span class="lineno"></span><span class="line"><span class="w">    </span><span class="k">position</span><span class="p">:</span><span class="w"> </span><span class="kc">relative</span><span class="p">;</span><span class="w"></span></span>
<span class="lineno"></span><span class="line"><span class="w">    </span><span class="k">text-align</span><span class="p">:</span><span class="w"> </span><span class="kc">right</span><span class="p">;</span><span class="w"></span></span>
<span class="lineno"></span><span class="line"><span class="w">    </span><span class="k">color</span><span class="p">:</span><span class="w"> </span><span class="nf">var</span><span class="p">(</span><span class="nv">--c-text-muted</span><span class="p">);</span><span class="w"></span></span>
<span class="lineno"></span><span class="line"><span class="w">    </span><span class="k">border-right</span><span class="p">:</span><span class="w"> </span><span class="mi">1</span><span class="kt">px</span><span class="w"> </span><span class="kc">solid</span><span class="w"> </span><span class="nf">var</span><span class="p">(</span><span class="nv">--c-fg-highlight</span><span class="p">);</span><span class="w"></span></span>
<span class="lineno"></span><span class="line"><span class="w">    </span><span class="k">align-self</span><span class="p">:</span><span class="w"> </span><span class="kc">stretch</span><span class="p">;</span><span class="w"></span></span>
<span class="lineno"></span><span class="line"><span class="w"></span><span class="p">}</span><span class="w"></span></span>
<span class="lineno"></span><span class="line"><span class="w"></span></span>
<span class="lineno"></span><span class="line"><span class="w"></span><span class="c">/* We also handle line continuation markers in CSS. */</span><span class="w"></span></span>
<span class="lineno"></span><span class="line"><span class="w"></span><span class="p">.</span><span class="nc">code</span><span class="w"> </span><span class="o">&gt;</span><span class="w"> </span><span class="p">.</span><span class="nc">lineno</span><span class="p">::</span><span class="nd">after</span><span class="w"> </span><span class="p">{</span><span class="w"></span></span>
<span class="lineno"></span><span class="line"><span class="w">    </span><span class="k">position</span><span class="p">:</span><span class="w"> </span><span class="kc">absolute</span><span class="p">;</span><span class="w"></span></span>
<span class="lineno"></span><span class="line"><span class="w">    </span><span class="k">right</span><span class="p">:</span><span class="w"> </span><span class="mi">5</span><span class="kt">px</span><span class="p">;</span><span class="w"></span></span>
<span class="lineno"></span><span class="line"><span class="w">    </span><span class="k">content</span><span class="p">:</span><span class="w"> </span><span class="s2">&quot;\a↳\a↳\a↳\a↳\a↳\a↳\a↳\a↳\a↳\a↳\a↳\a↳\a↳\a↳\a↳\a↳\a↳\a↳&quot;</span><span class="p">;</span><span class="w"></span></span>
<span class="lineno"></span><span class="line"><span class="w">    </span><span class="k">white-space</span><span class="p">:</span><span class="w"> </span><span class="kc">pre</span><span class="p">;</span><span class="w"></span></span>
<span class="lineno"></span><span class="line"><span class="w">    </span><span class="k">color</span><span class="p">:</span><span class="w"> </span><span class="nf">var</span><span class="p">(</span><span class="nv">--c-text-muted</span><span class="p">);</span><span class="w"></span></span>
<span class="lineno"></span><span class="line"><span class="w"></span><span class="p">}</span><span class="w"></span></span>
<span class="lineno"></span><span class="line"><span class="w"></span></span>
<span class="lineno"></span><span class="line"><span class="w"></span><span class="c">/* Insert the actual line number */</span><span class="w"></span></span>
<span class="lineno"></span><span class="line"><span class="w"></span><span class="p">.</span><span class="nc">code</span><span class="w"> </span><span class="o">&gt;</span><span class="w"> </span><span class="p">.</span><span class="nc">lineno</span><span class="p">::</span><span class="nd">before</span><span class="w"> </span><span class="p">{</span><span class="w"></span></span>
<span class="lineno"></span><span class="line"><span class="w">    </span><span class="k">content</span><span class="p">:</span><span class="w"> </span><span class="nb">counter</span><span class="p">(</span><span class="n">lineno</span><span class="p">);</span><span class="w"></span></span>
<span class="lineno"></span><span class="line"><span class="w"></span><span class="p">}</span><span class="w"></span></span>
<span class="lineno"></span><span class="line"><span class="w"></span></span>
<span class="lineno"></span><span class="line"><span class="w"></span><span class="p">.</span><span class="nc">code</span><span class="p">::</span><span class="nd">before</span><span class="w"> </span><span class="p">{</span><span class="w"></span></span>
<span class="lineno"></span><span class="line"><span class="w">    </span><span class="k">counter-reset</span><span class="p">:</span><span class="w"> </span><span class="n">lineno</span><span class="p">;</span><span class="w"></span></span>
<span class="lineno"></span><span class="line"><span class="w"></span><span class="p">}</span><span class="w"></span></span>
<span class="lineno"></span><span class="line"><span class="w"></span></span>
<span class="lineno"></span><span class="line"><span class="w"></span><span class="p">.</span><span class="nc">code</span><span class="w"> </span><span class="p">.</span><span class="nc">hll</span><span class="w"> </span><span class="p">{}</span><span class="w"></span></span>
<span class="lineno"></span><span class="line"><span class="w"></span><span class="c">/* Following are about 50 lines that define the styling of each kind of pygments syntax highlight token. These lines</span></span>
<span class="lineno"></span><span class="line"><span class="c">   all look like the following: */</span><span class="w"></span></span>
<span class="lineno"></span><span class="line"><span class="w"></span><span class="p">.</span><span class="nc">code</span><span class="w"> </span><span class="p">.</span><span class="nc">c</span><span class="w">   </span><span class="p">{</span><span class="w"> </span><span class="k">color</span><span class="p">:</span><span class="w"> </span><span class="nf">var</span><span class="p">(</span><span class="nv">--c-text</span><span class="p">);</span><span class="w"> </span><span class="k">font-weight</span><span class="p">:</span><span class="w"> </span><span class="mi">400</span><span class="w"> </span><span class="p">}</span><span class="w"> </span><span class="c">/* Comment */</span>
</span></pre>
<p>This CSS does a few things:</p>
<blockquote>
<ol class="arabic simple">
<li>It renders the <tt class="docutils literal">&lt;pre&gt;</tt> code listing element using a two-column CSS <tt class="docutils literal">display: grid</tt> layout. The left column is
used for the line numbers, and the right column is used for the code lines.</li>
<li>It numbers the lines using a <a class="reference external" href="https://developer.mozilla.org/en-US/docs/Web/CSS/counter">CSS Counter</a>. CSS counters are meant for things like numbering headings and such, but
they are a perfect fit for our purpose.</li>
<li>It inserts the counter value as the line number into the <tt class="docutils literal">&lt;span <span class="pre">class=&quot;lineno&quot;&gt;</span></tt> element's <tt class="docutils literal">::before</tt>
pseudo-element. A side effect of using the <tt class="docutils literal">::before</tt> pseudo-element is that without doing anything extra, the
line numbers will remain outside of the normal text selection so they will neither be highlighted when selecting
listing content, nor will they be copied when copy/pasting the listing content.</li>
<li>It inserts a string of <tt class="docutils literal"><span class="pre">&quot;\a↳\a↳\a↳\a↳\a↳\a↳\a↳\a↳\a↳\a↳\a↳\a↳\a↳\a↳\a↳\a↳\a↳\a↳&quot;</span></tt> into the line number span's
<tt class="docutils literal">::after</tt> pseudo-element. This string evaluates to a sequence of unicode arrows separated by line breaks, and
starting with an empty line. The <tt class="docutils literal">::after</tt> pseudo-element is positioned using <tt class="docutils literal">position: absolute</tt>, and the
parent <tt class="docutils literal">&lt;span <span class="pre">class=&quot;lineno&quot;&gt;</span></tt> has <tt class="docutils literal">position: relative</tt> set. This way, the arrow pseudo-element gets placed on
top of the lineno span without affecting the layout at all. By setting <tt class="docutils literal">overflow: clip</tt> on the parent <tt class="docutils literal">&lt;span
<span class="pre">class=&quot;lineno&quot;&gt;</span></tt>, the arrow pseudo-element gets cut off vertically wherever the parent lineno element naturally
ends.</li>
</ol>
</blockquote>
<p>The line number span is inserted into the parent <tt class="docutils literal">&lt;pre&gt;</tt> element's CSS grid using <tt class="docutils literal"><span class="pre">align-self:</span> stretch</tt>, which
causes it to vertically stretch to fill the available space. Since the line number span only contains the line number,
its minimum height is a single line. As a result, it will stretch higher only when the corresponding code line in the
right grid column stretches vertically because of line wrapping. When that happens, part of the arrow pseudo-element
starts showing through from behind the <tt class="docutils literal">overflow: clip</tt> of the line number span, and one arrow gets rendered for each
wrapped listing line.</p>
<p>When the page is too narrow, we don't want the code listing's lines to wrapp into a column of single characters. To
prevent that, we simply set a <tt class="docutils literal"><span class="pre">min-width</span></tt> on the <tt class="docutils literal">&lt;span <span class="pre">class=&quot;line&quot;&gt;</span></tt> in the right column, and set <tt class="docutils literal"><span class="pre">overflow-x:</span>
auto</tt> on the listing <tt class="docutils literal">&lt;pre&gt;</tt>. This results in a horizontal scroll bar appearing whenever the listing gets too narrow.</p>
<p>You can try out the line wrapping by resizing this page!</p>
</div>
<div class="section" id="rst2html-wrapper">
<h2>rst2html wrapper</h2>
<p>Here is the python <tt class="docutils literal">rst2html</tt> wrapper that monkey-patches code rendering. I made hugo invoke this while building the
page by simply overriding the <tt class="docutils literal">PATH</tt> environment variable.</p>
<pre class="code python literal-block">
<span class="lineno"></span><span class="line"><span class="ch">#!/usr/bin/env python3</span><span class="w"></span></span>
<span class="lineno"></span><span class="line"><span class="w"></span><span class="c1"># Based on https://gist.github.com/mastbaum/2655700 for the basic plugin scaffolding</span><span class="w"></span></span>
<span class="lineno"></span><span class="line"><span class="w"></span></span>
<span class="lineno"></span><span class="line"><span class="w"></span><span class="kn">import</span><span class="w"> </span><span class="nn">sys</span><span class="w"></span></span>
<span class="lineno"></span><span class="line"><span class="w"></span><span class="kn">import</span><span class="w"> </span><span class="nn">re</span><span class="w"></span></span>
<span class="lineno"></span><span class="line"><span class="w"></span></span>
<span class="lineno"></span><span class="line"><span class="w"></span><span class="kn">import</span><span class="w"> </span><span class="nn">docutils.core</span><span class="w"></span></span>
<span class="lineno"></span><span class="line"><span class="w"></span><span class="kn">from</span><span class="w"> </span><span class="nn">docutils.transforms</span><span class="w"> </span><span class="kn">import</span> <span class="n">Transform</span><span class="w"></span></span>
<span class="lineno"></span><span class="line"><span class="w"></span><span class="kn">from</span><span class="w"> </span><span class="nn">docutils.nodes</span><span class="w"> </span><span class="kn">import</span> <span class="n">TextElement</span><span class="p">,</span> <span class="n">Inline</span><span class="p">,</span> <span class="n">Text</span><span class="w"></span></span>
<span class="lineno"></span><span class="line"><span class="w"></span><span class="kn">from</span><span class="w"> </span><span class="nn">docutils.parsers.rst</span><span class="w"> </span><span class="kn">import</span> <span class="n">Directive</span><span class="p">,</span> <span class="n">directives</span><span class="w"></span></span>
<span class="lineno"></span><span class="line"><span class="w"></span><span class="kn">from</span><span class="w"> </span><span class="nn">docutils.writers.html4css1</span><span class="w"> </span><span class="kn">import</span> <span class="n">Writer</span><span class="p">,</span> <span class="n">HTMLTranslator</span><span class="w"></span></span>
<span class="lineno"></span><span class="line"><span class="w"></span></span>
<span class="lineno"></span><span class="line"><span class="w"></span></span>
<span class="lineno"></span><span class="line"><span class="w"></span><span class="k">class</span><span class="w"> </span><span class="nc">UnfuckedHTMLTranslator</span><span class="p">(</span><span class="n">HTMLTranslator</span><span class="p">):</span><span class="w"></span></span>
<span class="lineno"></span><span class="line"><span class="w"></span>    <span class="k">def</span><span class="w"> </span><span class="fm">__init__</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="o">*</span><span class="n">args</span><span class="p">,</span> <span class="o">**</span><span class="n">kwargs</span><span class="p">):</span><span class="w"></span></span>
<span class="lineno"></span><span class="line"><span class="w"></span>        <span class="nb">super</span><span class="p">()</span><span class="o">.</span><span class="fm">__init__</span><span class="p">(</span><span class="o">*</span><span class="n">args</span><span class="p">,</span> <span class="o">**</span><span class="n">kwargs</span><span class="p">)</span><span class="w"></span></span>
<span class="lineno"></span><span class="line"><span class="w"></span>        <span class="bp">self</span><span class="o">.</span><span class="n">in_literal_block</span> <span class="o">=</span> <span class="kc">False</span><span class="w"></span></span>
<span class="lineno"></span><span class="line"><span class="w"></span></span>
<span class="lineno"></span><span class="line"><span class="w"></span>    <span class="k">def</span><span class="w"> </span><span class="nf">visit_literal_block</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">node</span><span class="p">):</span><span class="w"></span></span>
<span class="lineno"></span><span class="line"><span class="w"></span>        <span class="c1"># Insert an empty &quot;lineno&quot; span before each line. We insert the line numbers using pure CSS in a ::before</span><span class="w"></span></span>
<span class="lineno"></span><span class="line"><span class="w"></span>        <span class="c1"># pseudo-element. This has the added advantage that the line numbers don't get included in text selection.</span><span class="w"></span></span>
<span class="lineno"></span><span class="line"><span class="w"></span>        <span class="c1"># These line number spans are also used to show line continuation markers when a line is wrapped.</span><span class="w"></span></span>
<span class="lineno"></span><span class="line"><span class="w"></span>        <span class="bp">self</span><span class="o">.</span><span class="n">in_literal_block</span> <span class="o">=</span> <span class="kc">True</span><span class="w"></span></span>
<span class="lineno"></span><span class="line"><span class="w"></span>        <span class="bp">self</span><span class="o">.</span><span class="n">body</span><span class="o">.</span><span class="n">append</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">starttag</span><span class="p">(</span><span class="n">node</span><span class="p">,</span> <span class="s1">'pre'</span><span class="p">,</span> <span class="n">CLASS</span><span class="o">=</span><span class="s1">'literal-block'</span><span class="p">))</span><span class="w"></span></span>
<span class="lineno"></span><span class="line"><span class="w"></span>        <span class="bp">self</span><span class="o">.</span><span class="n">body</span><span class="o">.</span><span class="n">append</span><span class="p">(</span><span class="s1">'&lt;span class=&quot;lineno&quot;&gt;&lt;/span&gt;&lt;span class=&quot;line&quot;&gt;'</span><span class="p">)</span><span class="w"></span></span>
<span class="lineno"></span><span class="line"><span class="w"></span></span>
<span class="lineno"></span><span class="line"><span class="w"></span>    <span class="k">def</span><span class="w"> </span><span class="nf">depart_literal_block</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">node</span><span class="p">):</span><span class="w"></span></span>
<span class="lineno"></span><span class="line"><span class="w"></span>        <span class="bp">self</span><span class="o">.</span><span class="n">in_literal_block</span> <span class="o">=</span> <span class="kc">False</span><span class="w"></span></span>
<span class="lineno"></span><span class="line"><span class="w"></span>        <span class="bp">self</span><span class="o">.</span><span class="n">body</span><span class="o">.</span><span class="n">append</span><span class="p">(</span><span class="s1">'</span><span class="se">\n</span><span class="s1">&lt;/span&gt;&lt;/pre&gt;</span><span class="se">\n</span><span class="s1">'</span><span class="p">)</span><span class="w"></span></span>
<span class="lineno"></span><span class="line"><span class="w"></span></span>
<span class="lineno"></span><span class="line"><span class="w"></span>    <span class="k">def</span><span class="w"> </span><span class="nf">visit_Text</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">node</span><span class="p">):</span><span class="w"></span></span>
<span class="lineno"></span><span class="line"><span class="w"></span>        <span class="k">if</span> <span class="bp">self</span><span class="o">.</span><span class="n">in_literal_block</span><span class="p">:</span><span class="w"></span></span>
<span class="lineno"></span><span class="line"><span class="w"></span>            <span class="k">for</span> <span class="n">match</span> <span class="ow">in</span> <span class="n">re</span><span class="o">.</span><span class="n">finditer</span><span class="p">(</span><span class="s1">'([^</span><span class="se">\n</span><span class="s1">]*)(</span><span class="se">\n</span><span class="s1">|$)'</span><span class="p">,</span> <span class="n">node</span><span class="o">.</span><span class="n">astext</span><span class="p">()):</span><span class="w"></span></span>
<span class="lineno"></span><span class="line"><span class="w"></span>                <span class="n">text</span><span class="p">,</span> <span class="n">end</span> <span class="o">=</span> <span class="n">match</span><span class="o">.</span><span class="n">groups</span><span class="p">()</span><span class="w"></span></span>
<span class="lineno"></span><span class="line"><span class="w"></span></span>
<span class="lineno"></span><span class="line"><span class="w"></span>                <span class="k">if</span> <span class="n">text</span><span class="p">:</span><span class="w"></span></span>
<span class="lineno"></span><span class="line"><span class="w"></span>                    <span class="nb">super</span><span class="p">()</span><span class="o">.</span><span class="n">visit_Text</span><span class="p">(</span><span class="n">Text</span><span class="p">(</span><span class="n">text</span><span class="p">))</span><span class="w"></span></span>
<span class="lineno"></span><span class="line"><span class="w"></span></span>
<span class="lineno"></span><span class="line"><span class="w"></span>                <span class="k">if</span> <span class="n">end</span> <span class="o">==</span> <span class="s1">'</span><span class="se">\n</span><span class="s1">'</span><span class="p">:</span><span class="w"></span></span>
<span class="lineno"></span><span class="line"><span class="w"></span>                    <span class="k">if</span> <span class="nb">isinstance</span><span class="p">(</span><span class="n">node</span><span class="o">.</span><span class="n">parent</span><span class="p">,</span> <span class="n">Inline</span><span class="p">):</span><span class="w"></span></span>
<span class="lineno"></span><span class="line"><span class="w"></span>                        <span class="bp">self</span><span class="o">.</span><span class="n">depart_inline</span><span class="p">(</span><span class="n">node</span><span class="o">.</span><span class="n">parent</span><span class="p">)</span><span class="w"></span></span>
<span class="lineno"></span><span class="line"><span class="w"></span>                    <span class="bp">self</span><span class="o">.</span><span class="n">body</span><span class="o">.</span><span class="n">append</span><span class="p">(</span><span class="sa">f</span><span class="s1">'&lt;/span&gt;</span><span class="se">\n</span><span class="s1">&lt;span class=&quot;lineno&quot;&gt;&lt;/span&gt;&lt;span class=&quot;line&quot;&gt;'</span><span class="p">)</span><span class="w"></span></span>
<span class="lineno"></span><span class="line"><span class="w"></span>                    <span class="k">if</span> <span class="nb">isinstance</span><span class="p">(</span><span class="n">node</span><span class="o">.</span><span class="n">parent</span><span class="p">,</span> <span class="n">Inline</span><span class="p">):</span><span class="w"></span></span>
<span class="lineno"></span><span class="line"><span class="w"></span>                        <span class="bp">self</span><span class="o">.</span><span class="n">visit_inline</span><span class="p">(</span><span class="n">node</span><span class="o">.</span><span class="n">parent</span><span class="p">)</span><span class="w"></span></span>
<span class="lineno"></span><span class="line"><span class="w"></span></span>
<span class="lineno"></span><span class="line"><span class="w"></span>        <span class="k">else</span><span class="p">:</span><span class="w"></span></span>
<span class="lineno"></span><span class="line"><span class="w"></span>            <span class="nb">super</span><span class="p">()</span><span class="o">.</span><span class="n">visit_Text</span><span class="p">(</span><span class="n">node</span><span class="p">)</span><span class="w"></span></span>
<span class="lineno"></span><span class="line"><span class="w"></span></span>
<span class="lineno"></span><span class="line"><span class="w"></span></span>
<span class="lineno"></span><span class="line"><span class="w"></span><span class="n">html_writer</span> <span class="o">=</span> <span class="n">Writer</span><span class="p">()</span><span class="w"></span></span>
<span class="lineno"></span><span class="line"><span class="w"></span><span class="n">html_writer</span><span class="o">.</span><span class="n">translator_class</span> <span class="o">=</span> <span class="n">UnfuckedHTMLTranslator</span><span class="w"></span></span>
<span class="lineno"></span><span class="line"><span class="w"></span><span class="n">docutils</span><span class="o">.</span><span class="n">core</span><span class="o">.</span><span class="n">publish_cmdline</span><span class="p">(</span><span class="n">writer</span><span class="o">=</span><span class="n">html_writer</span><span class="p">)</span>
</span></pre>
</div>
</div>
    </main><footer>
    Copyright © 2025 Jan Sebastian Götte
    / <a href="/about/">About</a>
    / <a href="/imprint/">Imprint</a>
</footer>
<script type="text/javascript" src="/pagefind/pagefind-ui.js" defer></script>
        <script>
            window.addEventListener('DOMContentLoaded', (event) => {
                new PagefindUI({element: "#search", showSubResults: true});
            });
        </script>
        <script type="speculationrules">
        {
            "prerender": [
                {
                    "source": "document",
                    "where": {
                        "and": [
                                {"href_matches": "/*"}
                            ]
                    },
                    "eagerness": "moderate"
                }
            ]
        }
        </script>
    </body>
</html>