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 inpane=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:
#![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
}
}
#![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:
def sum_squares(xs):
return sum(x * x for x in xs)
func sumSquares(xs []int) int {
total := 0
for _, x := range xs {
total += x * x
}
return total
}
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:
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;
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;