Sashmark / Blog

Markdown code block line numbers: a pandoc test

Bobby Huang ·

Grep tallies for the spec, beside a control, appear in Line numbering and the CommonMark spec. Under Mark the lines with the markdown reader, the command for cln.md and the HTML it produced are quoted. HTML from a fence with a start value of 10 is quoted in Begin at line 10 with startFrom. Output with the extension removed sits in With fenced_code_attributes turned off. Under Plain commonmark and commonmark+attributes, the two commonmark runs are compared. For a fence holding only a language name, output appears in A fence with only a language name. In Check the numbering markers with grep, six numbered steps use cat, pandoc and grep.

Key takeaways

  • Under Line numbering and the CommonMark spec, the spec searches and their control are listed.
  • Quoted HTML for cln.md sits in Mark the lines with the markdown reader.
  • Pandoc's command and HTML for the startFrom file appear in Begin at line 10 with startFrom.
  • Results without the attribute extension appear in With fenced_code_attributes turned off.
  • Plain commonmark output and the plus-attributes run are set out in Plain commonmark and commonmark+attributes.

You paste a long snippet into your notes and want to point a reader at line 7. Counting lines by hand gets old fast, so you look for a way to have the converter mark each line for you.

To test markdown code block line numbers, this post feeds three tiny files to pandoc and changes the reader setting between runs. The pandoc build was 3.8.3, running on a Mac with macOS. This post quotes the classes, ids and attributes pandoc wrote into the HTML. This test stops at the HTML; no viewer was involved. The code blocks guide covers fences, info strings and language names in general.

Line numbering and the CommonMark spec

Five line-numbering terms got zero grep matches in the spec file (CommonMark 0.31.2). Each case-insensitive fixed-string count came back 0, for numberLines, startFrom, line number, number lines and numbering. For a control, searching the fixed string info string matched 9 lines of that file.

The classes and ids in the runs below were written by pandoc.

The three test files

The first file is cln.md. Running cat cln.md printed:

```{.python .numberLines}
total = 0
for n in range(3):
    total += n
```

The braces after the backticks hold two classes, .python and .numberLines. The second file, cln-start.md, opens with {.python .numberLines startFrom="10"} and holds two code lines. The third file, cln-plain.md, is a fence that names python and nothing else, with one code line.

Braces on heading lines are a separate case, tested in the custom heading id post.

Mark the lines with the markdown reader

The command for this run:

pandoc -f markdown -t html cln.md

Pandoc printed this, with its own line breaks:

<div class="sourceCode" id="cb1"><pre
class="sourceCode numberSource python numberLines"><code class="sourceCode python"><span id="cb1-1"><a href="#cb1-1"></a>total <span class="op">=</span> <span class="dv">0</span></span>
<span id="cb1-2"><a href="#cb1-2"></a><span class="cf">for</span> n <span class="kw">in</span> <span class="bu">range</span>(<span class="dv">3</span>):</span>
<span id="cb1-3"><a href="#cb1-3"></a>    total <span class="op">+=</span> n</span></code></pre></div>

The pre element got the class list sourceCode numberSource python numberLines. Inside the code element, each source line sits in its own span, with the ids cb1-1, cb1-2 and cb1-3. Each span opens with an empty link whose href points at that same id. No brace character appears anywhere in the output.

Run as pandoc --list-extensions=markdown, the extension listing held +fenced_code_attributes in this test.

Begin at line 10 with startFrom

The second file adds a start value to the braces:

pandoc -f markdown -t html cln-start.md

Its output:

<div class="sourceCode" id="cb1" data-startFrom="10"><pre
class="sourceCode numberSource python numberLines"><code class="sourceCode python" style="counter-reset: source-line 9;"><span id="cb1-10"><a href="#cb1-10"></a>total <span class="op">=</span> <span class="dv">0</span></span>
<span id="cb1-11"><a href="#cb1-11"></a><span class="bu">print</span>(total)</span></code></pre></div>

Three things changed, while the class list on pre stayed the same as in the first run. The outer div gained data-startFrom="10". The code element gained style="counter-reset: source-line 9;". The span ids began at cb1-10 and went on to cb1-11, one for each of the two code lines.

With fenced_code_attributes turned off

Next, the markdown reader ran with that extension switched off:

pandoc -f markdown-fenced_code_attributes -t html cln.md

Pandoc wrote a single line:

<p><code>{.python .numberLines} total = 0 for n in range(3):     total += n</code></p>

The fence did not become a code block in this run. Pandoc wrote one inline code element inside a paragraph, with the brace text and the three code lines joined on one line. No sourceCode class and no line ids were written. The code lines lost their line breaks, so the indent before total += n now sits inside a run of five spaces.

The same setting on cln-start.md gave <p><code>{.python .numberLines startFrom="10"} total = 0 print(total)</code></p>. The start value stayed inside the brace text, and no data-startFrom attribute was written.

Plain commonmark and commonmark+attributes

Plain commonmark came next:

pandoc -f commonmark -t html cln.md

Its output:

<pre class="{.python"><code>total = 0
for n in range(3):
    total += n</code></pre>

Here the fence stayed a code block. The first word of the info string, {.python, went into the class of the pre element as written, brace and dot included. The code lines stayed plain text, with no spans and no line ids. The cln-start.md run gave the same pre class and its two code lines. The extension list from pandoc --list-extensions=commonmark showed -attributes.

Adding the extension changed that, so in this test the commonmark reader needed +attributes before it wrote the numbering classes. Swapping in pandoc -f commonmark+attributes -t html cln.md gave the same HTML as the markdown run above, line for line. The cln-start.md run under commonmark+attributes matched its markdown run too, including data-startFrom="10" and the style value.

A fence with only a language name

The third file has no braces:

pandoc -f markdown -t html cln-plain.md

Pandoc printed:

<div class="sourceCode" id="cb1"><pre
class="sourceCode python"><code class="sourceCode python"><span id="cb1-1"><a href="#cb1-1" aria-hidden="true" tabindex="-1"></a>total <span class="op">=</span> <span class="dv">0</span></span></code></pre></div>

The four reader settings in this test printed these same two lines for cln-plain.md. The class list on pre is sourceCode python, without numberSource or numberLines. Pandoc still wrapped the line in a span with the id cb1-1. This time the empty link carried aria-hidden="true" and tabindex="-1", and the runs with .numberLines had neither.

A line id showed up here without the numbering class, so the class list is the marker to check.

Check the numbering markers with grep

Run through these steps when a .numberLines fence gives HTML you didn't expect.

  1. Show cln.md with cat cln.md. The braces should sit on the opening fence line, right after the backticks.
  2. Convert it with pandoc -f markdown -t html cln.md and look for numberLines in the class list on the pre element.
  3. Count the line ids. pandoc -f markdown -t html cln.md | grep -c 'id="cb1-' printed 3. With -f commonmark that count was 0, and with -f commonmark+attributes it was 3.
  4. Count the class. pandoc -f markdown -t html cln.md | grep -c 'numberLines' printed 1. With -f markdown-fenced_code_attributes it printed 1 as well, because the braces and their contents were kept as inline code. Read the matching line before you trust that count.
  5. Check the start value. pandoc -f markdown -t html cln-start.md | grep -c 'data-startFrom' printed 1, and plain commonmark printed 0.
  6. Compare a plain fence. pandoc -f markdown -t html cln-plain.md | grep -c 'numberLines' printed 0, while pandoc -f markdown -t html cln-plain.md | grep -c 'id="cb1-' printed 1.

Where Sashmark fits

Sashmark's v1 is being planned so a person could change words where they sit on the rendered page. When saved, a file would get a clean Round-Trip from that same plan, and an unedited line would end up holding its old bytes. Marking new changes since you last opened the file to read it would fall to a planned Changes View that would need no git. It is in development and has not been released. The app itself has no AI features. See the planned features, the help pages and the roadmap for where each part stands.

More at sashmark.com.

Questions

Does Markdown have syntax for code block line numbers?
Search tallies over the spec, and a control tally, are given under Line numbering and the CommonMark spec.
Which pandoc reader writes the numberLines class?
The command and pandoc's HTML for cln.md are quoted in Mark the lines with the markdown reader.
Can the line count start at 10?
One run with a start value of 10 sits in Begin at line 10 with startFrom.
Why did my braces end up as text?
Under With fenced_code_attributes turned off, pandoc's HTML for the two brace fences is quoted.

What Sashmark plans for v1: Features · Roadmap

View as Markdown