Sashmark / Blog

Markdown diff code block: the HTML pandoc writes

Bobby Huang ·

HTML that pandoc printed for a fence labeled diff sits under Inside the HTML pandoc wrote. Look in One diff fence and three readers for the input file and its commands. An @@ line inside the fence is tested in Adding a hunk header line. For the unlabeled run, see Dropping the diff label. Two outer fence lengths are compared in Backtick lines inside the diff. Grep steps with their counts are numbered in Counting the class spans with grep.

Key takeaways

  • The section One diff fence and three readers holds the test file plus its commands.
  • The span around each content line is quoted under Inside the HTML pandoc wrote.
  • Read Adding a hunk header line for the run with an @@ line in the fence.
  • Runs with a four-backtick outer fence and a three-backtick one are quoted in Backtick lines inside the diff.
  • Grep steps and the counts they printed are listed in Counting the class spans with grep.

A markdown diff code block is a fenced block that carries diff as its info string. Fed one such block in this test, pandoc produced identical HTML whether the reader was markdown, commonmark or commonmark_x. In each of those runs it wrapped the minus line in one class span and the plus line in another.

A single Mac handled the whole test, one pass per command, and the opening line from pandoc --version gave the build as 3.8.3. Below is the HTML pandoc printed, with the line breaks it chose. Display of that HTML by any program was outside the test. For reading the plus, minus and hunk lines themselves, see the git diff walkthrough for Markdown files.

One diff fence and three readers

The first input is diff.md, and an awk line count for it printed 5. Here is what cat diff.md showed:

```diff
 unchanged line
-old line
+new line
```

Line 1 opens the fence and names diff. Lines 2 to 4 hold the content: a space before unchanged line, a minus before old line and a plus before new line. Line 5 closes the fence.

Then each reader got its own command:

pandoc -f markdown -t html diff.md
pandoc -f commonmark -t html diff.md
pandoc -f commonmark_x -t html diff.md

The test log ends the markdown, commonmark_x and commonmark runs with rc 0. Lines 1842-1843 of the CommonMark spec file I checked, 0.31.2, say the spec does not mandate any particular treatment of the info string. The class list in the next section is simply what pandoc chose to write for this input.

Inside the HTML pandoc wrote

Every reader in the test (markdown, commonmark, commonmark_x) printed these four lines, byte for byte alike:

<div class="sourceCode" id="cb1"><pre
class="sourceCode diff"><code class="sourceCode diff"><span id="cb1-1"><a href="#cb1-1" aria-hidden="true" tabindex="-1"></a> unchanged line</span>
<span id="cb1-2"><a href="#cb1-2" aria-hidden="true" tabindex="-1"></a><span class="st">-old line</span></span>
<span id="cb1-3"><a href="#cb1-3" aria-hidden="true" tabindex="-1"></a><span class="va">+new line</span></span></code></pre></div>

Taken apart, the output from those three readers holds six pieces:

  1. An outer div with the class sourceCode and the id cb1. Pandoc broke the first line inside the pre tag, just before class.
  2. A pre element and a code element, each with the class list sourceCode diff. The word from the info string sits second in that list.
  3. A span around each content line, carrying ids from cb1-1 to cb1-3, and opening with an empty a element. The post on numbered code lines has separate runs on those ids and links.
  4. The minus line, wrapped in <span class="st">. The minus sign stays inside that span with the text.
  5. The plus line, wrapped in <span class="va">. Here too the sign stays inside the span.
  6. The context line, with no inner class span at all. Its leading space comes right after the closing </a> tag.

Pandoc wrote st and va as class names. The output says nothing more about them, and this post adds nothing more either.

Adding a hunk header line

The second file, diff-hunk.md, puts a hunk header above the same three content lines. Its awk line count was 6, and cat printed:

```diff
@@ -1,2 +1,2 @@
 unchanged line
-old line
+new line
```

The commands followed the same pattern, with diff-hunk.md as the input. Output from markdown was identical to the output from commonmark and from commonmark_x, with rc 0 for each:

<div class="sourceCode" id="cb1"><pre
class="sourceCode diff"><code class="sourceCode diff"><span id="cb1-1"><a href="#cb1-1" aria-hidden="true" tabindex="-1"></a><span class="dt">@@ -1,2 +1,2 @@</span></span>
<span id="cb1-2"><a href="#cb1-2" aria-hidden="true" tabindex="-1"></a> unchanged line</span>
<span id="cb1-3"><a href="#cb1-3" aria-hidden="true" tabindex="-1"></a><span class="st">-old line</span></span>
<span id="cb1-4"><a href="#cb1-4" aria-hidden="true" tabindex="-1"></a><span class="va">+new line</span></span></code></pre></div>

In those three runs, the header line got a class span of its own, <span class="dt">. The context line moved to the id cb1-2 and still carried no class span. The minus and plus lines kept st and va, now under cb1-3 and cb1-4.

Dropping the diff label

For diff-nolabel.md, I dropped the word diff from the opening fence and left the three content lines as they were. Its awk count printed 5:

```
 unchanged line
-old line
+new line
```

With -f set to markdown, commonmark or commonmark_x, pandoc printed:

<pre><code> unchanged line
-old line
+new line</code></pre>

With the label gone, those three runs wrote no div, no sourceCode class, no line ids and no st or va span. The space before unchanged line was still in place, and the three lines sat as plain text inside one code element.

The label was the one change between the two files. That means the class spans, in this test, appeared with the word diff and vanished without it, under each of the three readers.

Backtick lines inside the diff

A diff of a Markdown file can carry fence lines of its own. The file diff-nested.md wraps a few of them in a four-backtick outer fence labeled diff. Its awk count printed 6:

````diff
 Intro text.
-```text
+```sh
 ```
````

The minus and plus lines put three backticks right after their sign. The fourth content line is one space followed by three backticks.

The three runs, one per reader, printed:

<div class="sourceCode" id="cb1"><pre
class="sourceCode diff"><code class="sourceCode diff"><span id="cb1-1"><a href="#cb1-1" aria-hidden="true" tabindex="-1"></a> Intro text.</span>
<span id="cb1-2"><a href="#cb1-2" aria-hidden="true" tabindex="-1"></a><span class="st">-```text</span></span>
<span id="cb1-3"><a href="#cb1-3" aria-hidden="true" tabindex="-1"></a><span class="va">+```sh</span></span>
<span id="cb1-4"><a href="#cb1-4" aria-hidden="true" tabindex="-1"></a> ```</span></code></pre></div>

Four content lines went in, and those three readers each wrote four numbered outer spans plus two inner class spans. The two backtick lines with a sign kept st and va, and the space-and-backticks line stayed plain context text under cb1-4.

Next, diff-nested3.md holds the same lines inside a three-backtick outer fence. Its awk count also printed 6:

```diff
 Intro text.
-```text
+```sh
 ```
```

Here the readers split. The markdown run printed:

<div class="sourceCode" id="cb1"><pre
class="sourceCode diff"><code class="sourceCode diff"><span id="cb1-1"><a href="#cb1-1" aria-hidden="true" tabindex="-1"></a> Intro text.</span>
<span id="cb1-2"><a href="#cb1-2" aria-hidden="true" tabindex="-1"></a><span class="st">-```text</span></span>
<span id="cb1-3"><a href="#cb1-3" aria-hidden="true" tabindex="-1"></a><span class="va">+```sh</span></span></code></pre></div>
<p>```</p>

Under markdown, the code block ended after cb1-3, and the last line of the file became a paragraph holding three backticks. Runs with commonmark_x and with plain commonmark printed those same first four lines, and then <pre><code></code></pre> in the spot that held the markdown paragraph. All six runs in this section gave rc 0.

Per lines 1820-1823 of that same CommonMark release, a closing fence needs a backtick count no smaller than the opening fence's, and per line 1828 it may sit behind up to three indent spaces. Measured against that, the line with one space and three backticks qualifies as a closer for the three-backtick file and falls short for the four-backtick file. Pandoc's output under the three readers lines up with that: the four-backtick file kept the line as content, and the three-backtick file stopped before it.

A context line without its space

The file diff-nospace.md is diff.md with the space removed from the start of the context line. Running diff diff.md diff-nospace.md printed:

2c2
<  unchanged line
---
> unchanged line

That command returned rc 1, since the two files differ. Pandoc printed this under each of the three readers:

<div class="sourceCode" id="cb1"><pre
class="sourceCode diff"><code class="sourceCode diff"><span id="cb1-1"><a href="#cb1-1" aria-hidden="true" tabindex="-1"></a>unchanged line</span>
<span id="cb1-2"><a href="#cb1-2" aria-hidden="true" tabindex="-1"></a><span class="st">-old line</span></span>
<span id="cb1-3"><a href="#cb1-3" aria-hidden="true" tabindex="-1"></a><span class="va">+new line</span></span></code></pre></div>

In these runs the text unchanged line follows </a> directly. As with diff.md, it got no class span, and the minus and plus lines kept st and va. For markdown, commonmark_x and commonmark alike, the missing space is the one byte that differs from the diff.md output.

Counting the class spans with grep

Below are the commands the extra script ran, one after another, from the test folder. To try them, replace the file names with yours.

pandoc -f markdown -t html diff.md -o diff-out-markdown.html
grep -c 'class="st"' diff-out-markdown.html
grep -c 'class="va"' diff-out-markdown.html
grep -c 'sourceCode diff' diff-out-markdown.html
pandoc -f markdown -t html diff-nolabel.md -o diff-out-nolabel.html
grep -c 'class="st"' diff-out-nolabel.html
grep -c '<pre' diff-out-nolabel.html
  1. Save the markdown output of diff.md to a file. That step logged rc 0, as did the matching step for commonmark and commonmark_x.
  2. Tally lines that hold class="st", then lines that hold class="va". Both tallies came back 1 in the output file for each reader.
  3. Count sourceCode diff. That printed 1 for the same three files. The second output line holds the string twice, and grep -c counts matching lines, so it adds one.
  4. Run the unlabeled file as a control under markdown. The st count printed 0 and the <pre count printed 1, which shows the zero came from a file that did produce a code block.

A sibling post from this batch runs the same three readers on collapsible sections in Markdown.

Where Sashmark fits

Still under construction, Sashmark aims to let a reader edit right inside the rendered text. Saving through its Round-Trip would then rewrite just the characters you touched, with every other part of the file left as it stood. Its roadmap also lists Changes View, which would surface the passages an agent reworded during your absence, no git setup required.

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

Why did my diff fence come out without class spans?
Set your fence beside the run in Dropping the diff label.
Does a hunk header line get a class?
The run in Adding a hunk header line quotes the span pandoc wrote for it.
Can a diff block hold a fence line?
See Backtick lines inside the diff for a four-backtick run and a three-backtick run.
What if a context line lost its leading space?
That file's output is quoted in A context line without its space.

What Sashmark plans for v1: Features · Roadmap

View as Markdown