Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Side-by-side blocks

Code blocks can be shown next to each other, each under a title: a change before and after, the same idea in several languages, or one block as tree-sitter and highlight.js highlight it.

Panes

Give each block a pane annotation holding its title. Blocks with a pane annotation that follow each other, separated only by blank lines, form one group:

```rust,pane="Before"
let total = a + b;
```

```rust,pane="After"
let total = a.saturating_add(b);
```

The group is shown as equal columns, each under its title, centred above the block. On screens narrower than 760 pixels the panes stack instead.

  • Titles. Quote a title that contains spaces or commas, as in pane="Before, as written"; a single word needs no quotes, as in pane=Before. A title is plain text, shown exactly as written.
  • Any number of panes. A group can hold two blocks or more, each in its own language.
  • Each pane renders as its block would on its own. A block in a configured language is highlighted with tree-sitter. A block marked notreesitter, in a language that is not configured, or in an editable playground is left to mdBook. Other annotations keep their meaning, so Rust panes keep their Run button and hidden lines.
  • Ending a group. Anything other than a blank line between two blocks, such as a paragraph, a heading, or an HTML comment (<!-- -->), ends the group, so two groups can follow each other.
  • Top-level blocks only. Like highlighting, panes apply to fences at the start of a line. A block inside a list item or a blockquote is left to mdBook as written.

Comparing with highlight.js

The compare annotation shows one block twice: on the left highlighted with tree-sitter, on the right as mdBook highlights it with highlight.js.

```rust,compare
fn main() {
    println!("side by side");
}
```

It is a shorthand for two panes from the same code:

```rust,pane="tree-sitter"
fn main() {
    println!("side by side");
}
```

```rust,notreesitter,pane="highlight.js"
fn main() {
    println!("side by side");
}
```

Styling

A group becomes the following HTML, with the layout in the embedded stylesheet:

<div class="treesitter-side-by-side">
  <div class="treesitter-pane">
    <div class="treesitter-pane-label">Before</div>
    <pre>…</pre>
  </div>
  …
</div>

Rules in additional-css override the layout, for example to widen the gap between panes or align titles to the left:

.treesitter-side-by-side { column-gap: 2.5rem; }
.treesitter-pane-label { text-align: left; }

A highlight_ts.css in the theme directory replaces the embedded stylesheet, layout included, so a replacement that is started from mdbook-tsitter css keeps it.

Examples

Before and after

The two panes from Panes, with the change in full:

Before
#![allow(unused)]
fn main() {
fn total_length(words: &[String]) -> usize {
    let mut total = 0;
    for i in 0..words.len() {
        total += words[i].len();
    }
    total
}
}
After
#![allow(unused)]
fn main() {
fn total_length(words: &[String]) -> usize {
    words.iter().map(String::len).sum()
}
}

One idea, several languages

Three panes, each in its own language:

Python
def sum_squares(xs):
    return sum(x * x for x in xs)
Go
func sumSquares(xs []int) int {
	total := 0
	for _, x := range xs {
		total += x * x
	}
	return total
}
Haskell
sumSquares :: [Int] -> Int
sumSquares = sum . map (^ 2)

Comparing with highlight.js

A compare block. The language chapters show longer comparisons, written as two panes each:

tree-sitter
type Shape =
  | { kind: "circle"; radius: number }
  | { kind: "square"; side: number };

const area = (shape: Shape): number =>
  shape.kind === "circle"
    ? Math.PI * shape.radius ** 2
    : shape.side ** 2;
highlight.js
type Shape =
  | { kind: "circle"; radius: number }
  | { kind: "square"; side: number };

const area = (shape: Shape): number =>
  shape.kind === "circle"
    ? Math.PI * shape.radius ** 2
    : shape.side ** 2;