Diffs

When one block is an evolution of another, you don’t have to paste the code twice and hand-maintain a changeset. Give each block a unique #+NAME: and add a #+begin_diff block that references them; on export the build replaces it with a box that toggles between a GitHub-style diff and the full source of the evolved block. Both views are syntax-highlighted and line-numbered; in the diff, added lines are blocked out in green and removed lines in red. Any named block works as a reference — src, example, or a special block like shader — and the two may live in different subtrees of the same file.

#+NAME: greet-v1
#+begin_src sh
#!/bin/sh
echo "starting up"
echo "hello"
echo "done"
#+end_src

#+NAME: greet-v2
#+begin_src sh
#!/bin/sh
echo "starting up"
name="world"
echo "hello, $name"
echo "done"
#+end_src

#+attr_diff: :from greet-v1 :to greet-v2
#+begin_diff
#+end_diff

#+attr_diff takes :from and :to (the two block names) plus a few optional settings:

  • :context N — unchanged lines of context around each change (default 3; use a large value to show the whole block as one changeset).
  • :view diff|source — which view renders by default (default diff). This is also the static view shown with JavaScript disabled.
  • :lang LANG — override the language used to highlight the source view (otherwise inferred from the :to block; shader blocks use glsl).

The header reads Diff: from → to or Source: to to match the view, and a button in the top-right toggles between them (it folds away when JavaScript is off, leaving the static :view). The build fails loudly if either name is missing or cannot be resolved, so a typo never silently ships an empty diff. The example above renders:

#!/bin/sh
echo "starting up"
echo "hello"
echo "done"

#!/bin/sh
echo "starting up"
name="world"
echo "hello, $name"
echo "done"
Diff: greet-v1 → greet-v2
--- greet-v1
+++ greet-v2
@@ -1,4 +1,5 @@
 #!/bin/sh
 echo "starting up"
-echo "hello"
+name="world"
+echo "hello, $name"
 echo "done"

The same two blocks can be referenced again — a named block is not consumed by a diff. Here is the very same pair with :view source, so the box opens on the evolved source instead of the changeset:

Source: greet-v2
1
2
3
4
5
#!/bin/sh
echo "starting up"
name="world"
echo "hello, $name"
echo "done"

By default the diff shows only three lines of context around each change, so a longer file whose edits are far apart renders as separate hunks with the unchanged stretch between them collapsed behind a @@ … @@ header. This pair edits a line near the top and another near the bottom:

#!/bin/sh
# greeting service v1
greeting="hello"

main() {
  name="$1"
  echo "$greeting, $name"
  log "$name"
}

log() {
  printf '%s\n' "$1" >> guests.txt
}

main "$@"

#!/bin/sh
# greeting service v2
greeting="hi"

main() {
  name="$1"
  echo "$greeting, $name"
  log "$name"
}

log() {
  printf '%s\n' "$1" >> visitors.log
}

main "$@"
Diff: srv-v1 → srv-v2
--- srv-v1
+++ srv-v2
@@ -1,6 +1,6 @@
 #!/bin/sh
-# greeting service v1
-greeting="hello"
+# greeting service v2
+greeting="hi"

 main() {
   name="$1"
@@ -9,7 +9,7 @@
 }

 log() {
-  printf '%s\n' "$1" >> guests.txt
+  printf '%s\n' "$1" >> visitors.log
 }

 main "$@"

To fill the gap and show the whole file as one changeset, pass a :context at least as large as the file — exactly what the whirlpool note below does with :context 99.

You don’t always need two named blocks. Reference a single block with :from (or :to) and write the change directly in the diff block’s body; that body becomes the other side of the diff. This is handy for a one-off edit you don’t want to give its own named block. :label names the inline side (default edited). For example, against greet-v1 from above:

#+attr_diff: :from greet-v1 :label timestamped
#+begin_diff
#!/bin/sh
echo "starting up"
echo "$(date): hello"
echo "done"
#+end_diff

which renders:

Diff: greet-v1 → timestamped
--- greet-v1
+++ timestamped
@@ -1,4 +1,4 @@
 #!/bin/sh
 echo "starting up"
-echo "hello"
+echo "$(date): hello"
 echo "done"

With :from the body is the new (right-hand) side; with :to it is the original instead. The same :context, :view, and :lang settings apply.

A #+begin_shader block can carry the same #+attr_diff: :from NAME line: the shader still runs live, but its source box becomes a diffbox against NAME. The shader body is written once — run and diffed from a single block — so there is no duplicated copy to keep in sync. The whirlpool note does exactly this.

Info

The diff block is DRY: only the named blocks hold the source, so the changeset can never drift from the code it describes. See it used in the whirlpool shader note, which shows the whole shader as a changeset from the step-function shader on the previous page.

Caveat

The diff and source are handed to the renderer verbatim, so the referenced code must not contain a literal Hugo shortcode opener — a {{ immediately followed by < (or by %). Ordinary {{ braces on their own are fine.

// settings
theme:
fx: