1### 7.11 Tree rendering — rails layout
3`treeWith options r` renders the history as text. It is implemented in the
4binary because it is presentation, not semantics. It reads the `Repo` value,
5the `trunk` revset (§Trunk below), the immutable set (§7.5), `diff` sizes,
6and jj's commit metadata by id (committer time, author).
8The layout is a *rails* graph: the trunk is always the first column (lane 0),
9every other commit sits in a lane to its right, rows are in time order, and a
10lane stays occupied for as long as the branch it carries is alive. Text
11columns are at fixed positions, independent of how deep a commit is.
16{ detail : Int -- 0, 1, or 2; as before
17, margin : Bool -- age and author column
18, elide : Bool -- fold uninteresting runs and collapse far subtrees
19, icons : Bool -- pictographic glyph set
20, color : Text -- "auto", "always", "never"
21, lanes : Int -- lane columns, including the trunk lane; ≥ 1
22, author : Bool -- full author name column
23, date : Bool -- absolute commit date column (YYYY-MM-DD)
24, files : Bool } -- number of files changed column
27Missing fields crash. `lanes` is the *fixed* width of the rails area in
28lanes; it does not adapt to the history (see §Overflow). The reference
29`config.j` sets `lanes = 4`. The `author`, `date`, and `files` columns are off
30by default; `treeData` enables all three.
34`j` evaluates the config's `trunk` revset (this is a fifth definition read by
35the binary, alongside `user`, `tree`, `immutable`, `labelled`).
37* If `trunk r` names exactly one commit `h`, the trunk **T** is `h` and its
38 ancestors — a single line from the root to `h`, since history is a tree.
39 With the reference config this is exactly the immutable set, so lane 0 is
40 "what cannot be rewritten".
41* If `trunk r` is empty (no remote), **T** is the root alone.
42* If it names several commits, `treeWith` crashes:
43 `treeWith: trunk names N revisions`.
45The root is always in T. `h` is the *trunk head*.
47#### Step 1 — the display tree (elision)
49The rendering works on a *display tree* derived from the whole history
50(`top r`). With `elide = false` it is the history itself: one node per
51commit. With `elide = true` it is the history after the rules of §Elision,
52which produce three kinds of node:
54| node | rows | time | children |
56| **commit** | one | its committer time | its children in the display tree |
57| **run** `╎ n` — `n` consecutive uninteresting commits on one line of descent | one | the time of its first commit | the one child of its last commit |
58| **collapsed** — a commit whose descendants are hidden, shown as `⋯ n` | one | its committer time | none |
60A run never straddles the boundary of T (the trunk head is interesting, so the
61trunk's last commit ends any run; a single-child root is dropped, §The root).
62A run of trunk commits is in T; any other run is not.
64Nodes with no stored counterpart (minted in this program, as in a dry run)
67#### Step 2 — row order
69Rows are a topological order of the display tree, oldest first:
73while ready is non-empty:
74 n ← the node of ready with the least time
75 (ties: the node that comes first in preorder, using jj's sibling order)
77 ready ← ready ∪ children(n)
80Consequently a parent is always above its children, the root is the first
81row, the trunk is emitted in order, and everything with time ∞ comes after
82everything with a time. Sibling order affects only ties.
84The row order is computed in full before any lane is assigned; the lane
85rules below refer to it ("placed before", "last child").
89There are `lanes` lanes, numbered from 0. Each lane is either empty or holds
90a *rail*: a pending edge from a placed node `p` downward, of one of two
93* **live** — `p`'s remaining children may still fork from it, and one of them
94 will inherit the lane.
95* **reserved for `d`** — only the node `d` may land in this lane.
97Lane 0 belongs to T. Nodes are placed in row order; the lane of a node `n`
98with parent `p` is decided as follows, first rule that applies:
1001. `n` is the root → lane 0.
1012. `n ∈ T` → lane 0 (it inherits `p`'s rail, which is live in lane 0).
1023. Some lane holds a rail *reserved for `n`* → that lane.
1034. `p ∉ T`, `p`'s rail is live in lane `ℓ`, and `n` is `p`'s **last** child
104 in row order → lane `ℓ` (inherit).
1055. Otherwise → **fork**: the leftmost empty lane strictly to the right of `p`'s
106 lane. If there is none, see §Overflow.
108After `n` is placed in lane `ℓ`:
110* If `n` has children in the display tree, lane `ℓ` now holds a live rail
111 owned by `n`. Otherwise (leaf, collapsed node) lane `ℓ` is empty below this
113* If `n` was the last child of `p` and did not inherit `p`'s lane (rule 5 with
114 `p` the trunk head, or `p` the root of an empty trunk), `p`'s lane becomes
115 empty below this row: the fork on this row is drawn with `╰` instead of `├`.
116 When `n` finds no lane (§Overflow) no fork is drawn, but `p`'s lane empties
117 all the same; the rail that reaches `n`'s row is drawn there as `│`.
119**Reservations.** When a node `p ∈ T` is placed and has a trunk child `t`,
120every side child of `p` that comes *after* `t` in row order needs its own
121lane already, because lane 0 will belong to `t` by then. On `p`'s own row, for
122each such child in row order, the leftmost empty lane to the right of 0 is
123taken and holds a rail reserved for that child. Side children of `p` that
124come *before* `t` fork from lane 0 just in time (rule 5) and need no
127No other node reserves lanes: a node outside T passes its lane to its last
128child (rule 4), so every earlier child can fork from the still-live rail. The
129trunk head has no trunk child, so all its children fork, the last with `╰`.
131Invariants: the trunk occupies lane 0 and nothing else ever does; a lane is
132reused as soon as the branch it carried has ended; a fork's target lane is
133always to the right of its source.
137If rule 5 or a reservation finds no empty lane in `0 < ℓ < lanes`, the
138subtree rooted at `n` is **flattened**: `n` and every descendant of it still
139get their rows, in row order, with their id, message, labels and margin, but
140they take no lane. Their rails area preserves every rail that passes through.
141If any rendered node is flattened, a separate four-column status area
142is reserved between the rails and the id separator on every row. Flattened
143rows show `»`; on a commit row this is followed by a space and its normal
144node glyph (§Glyphs), padded to four display columns. Non-flattened rows
145leave the area blank. Neither the overflow marker nor a node glyph replaces
146a live rail. Nothing is hidden —
147the focus, conflicts, and empty commits in particular are always identifiable
148— only the shape beyond `lanes` is lost. Widen `lanes` (or enable `elide`) to
151#### Step 4 — drawing a row
153Columns, left to right, at fixed offsets:
1551. **Gutter**, 2 characters: `▶ ` on the focus row, blank otherwise.
1562. **Rails**, `2 · lanes` characters: lane `i` owns characters `2i` and
1583. **Overflow status**, 4 display columns when any rendered node is flattened;
159 absent otherwise (§Overflow).
1615. **Id**: `@` followed by the shortest prefix unique among the commits of the
162 display tree, minimum 4 — so the rendered id is itself a valid id literal
163 (§3.1). Blank on a run row.
1646. **Size bar** (`detail ≥ 1`, focus and its parent and children only; all
165 commits at `detail = 2`): `▁ ▂ ▃ ▅ ▇`, as before. A row without a bar
166 keeps the column blank; it is left out only when no row has a bar.
1677. **Message**: first line; nothing if empty; followed by `⋯ n` on a
1698. **Labels**: left-aligned column, present only if any rendered commit has
170 a label; names separated by two spaces.
1719. **Margin** (`margin = true`): age and author initials, as before, each
172 right-aligned in its own column (a `9m` ends under a `13m`), so the margin
173 ends at one column on every row.
17410. **Data columns** (off unless enabled): `date` shows the commit's absolute
175 date `YYYY-MM-DD`; `files` shows the number of files changed as `n files`;
176 `author` shows the full author name. They appear in that order, grey,
177 immediately before the margin in the same right-aligned block.
179Columns 5–9 start at the same offset on every row. Labels start two columns
180past the widest message, and the data columns and the margin start two
181columns past the widest label (past the widest message when there is no
182label column). Each data column, the age and the initials are as wide as
183their widest entry and two columns apart. When stdout is a terminal and a row
184would exceed its width, the message is cut and ends with `…` so that labels
185and the margin keep their columns; otherwise nothing is cut.
187**Rails characters.** For lane `i` on the row of node `n` placed in lane
188`ℓ`, character `2i` is, first rule that applies:
190| condition | character |
192| `i = ℓ` | the node glyph (§Glyphs), or `╎` for a run |
193| `i` holds a reservation made on this row (`n ∈ T`) | `╮`, or `┬` if a further reservation lies to its right |
194| `i` is the source lane of a fork drawn on this row (`n` forked from `p` in lane `i`) | `├`, or `╰` if `p`'s lane empties below |
195| `i` lies between the two ends of a horizontal segment on this row and holds a rail | `┼` |
196| `i` lies between the two ends of a horizontal segment on this row | `─` |
197| `i` holds a rail passing through this row | `│` |
200Character `2i+1` is `─` if it lies inside a horizontal segment (from a fork
201source to its target, or from a node to its rightmost reservation), else a
202space. On a run row the count follows the `╎`, written into the rails area
203and, if needed, into the empty id column; when lanes to the right of the run
204hold rails, it follows the rightmost of them instead, so that it covers no
205rail. The count is never cut: what does not fit the rails area runs on,
206unbroken, through the space after it into the id column, which is widened if
207it cannot hold the rest.
209**Detail lines** (`detail = 2`, focus only): changed paths appear directly
210under the focus, with a mark each: `+` added, `~` modified, `−` deleted,
211`✖` unresolved. Every detail row shows `│` in every lane that holds a rail
212below the focus row (including the focus's own lane if it has children), and
213leaves any overflow status column blank. The text starts two columns into the
214id column, under the focus's id, at the same offset on every continuation row.
215When stdout is a terminal, paths wrap onto continuation rows within the width
216available after this prefix. A path longer than that width may split across
217rows; wrapping does not discard any path content. Redirected output keeps
218the changed paths on a single detail row.
222Unchanged from before:
224| glyph | meaning | `icons = true` |
227| `●` | ancestor of the focus | `🌿` |
228| `○` | other commit | `🍃` |
229| `◆` | in the immutable set (§7.5) | `🪨` |
230| `◌` | empty: no change against its parent | `🫙` |
231| `⊗` | has unresolved files | `🔥` |
234Precedence: `⌂`, then `⊗`, then `◌`, then `◆`, then the position glyphs.
235`⊗` and `◌` are properties of the history being drawn — a commit's files, and
236its parent's in that history — not of the stored commit with the same id, so
237a dry run (§1.2) draws them as the edit would leave them.
238An `icons = true` glyph is two columns wide, so it fills both characters of
239its lane: character `2ℓ+1` of its row is not drawn.
240Rails use `│ ├ ╰ ─ ┼ ╮ ┬ ╎`; `»` belongs to the separate overflow status
243#### Elision (`elide = true`)
245A commit is *interesting* if it is the focus, an ancestor of the focus off the
246trunk, a child of an ancestor of the focus off the trunk, labelled, conflicted,
247a leaf, or has more than one child. Runs of consecutive uninteresting commits
248on a single line of descent become a run node. Subtrees whose top is not the
249focus, an ancestor of it, or a child of an ancestor become a collapsed node,
250`⋯ n` counting the hidden descendants (omitted when `n = 0`).
254The synthetic root (id `zzz…`, no message or author) is dropped from the
255rendering when it is a pure anchor — that is, when it has exactly one child —
256so the first real commit starts the tree at the top instead of a `⌂` row
257floating above it. This holds whether or not elision is on: with `elide =
258false` the root's row is simply omitted, and with `elide = true` it is dropped
259rather than folded into a run. A root with more than one child is a genuine
260branch point — it may be where the current branch forks off — so it is kept,
265Glyphs are coloured by meaning (the same precedence as their shape): root bold
266magenta, conflict bright red, empty dim, immutable bright blue, focus bold
267cyan, ancestor of focus bright green, other bright white. Ids are bold magenta,
268with conflict red and immutable blue taking precedence; the focus id and `▶`
269gutter are bold cyan. Messages are bold on the focus row, red on a conflict,
270and dim-italic when empty. Labels are bold green; the margin and data columns
271are grey with bright author-hued initials. Size bars progress from cyan
272through blue and magenta to yellow as their height increases. Rail connectors
273in lane 0 take the immutable colour; other lanes cycle through magenta, cyan,
274yellow, and green by lane position. Glyphs always keep their semantic colour.
276The whole focus row is highlighted with bright text on a dark-blue background
277band that spans the full terminal width. This is colour only: glyphs, rails,
278and the `▶` gutter still carry the meaning, so a colourless rendering loses
279nothing — the band is a no-op when colour is off.
281Structured value display shares this palette: id prefixes are bold magenta,
282field names and table headers dim cyan, paths cyan, integers and file sizes
283yellow, and booleans green for true and red for false. Raw text and blob
284contents are never styled. `color = "auto"` requires a terminal whose `TERM`
285is not `dumb`; `color = "always"` explicitly enables colour and `"never"`
286disables it. A non-empty `NO_COLOR` disables colour in every mode. Redirected
287output remains plain in auto mode.
291After the tree, `treeWith` appends a *legend* explaining the symbols that
292actually appear, so a reader can always decode the current rendering. Only
293symbols that are used in this tree are explained; a symbol that never appears
294is omitted from the legend.
296The legend covers the node glyphs present (§Glyphs), the focus gutter `▶`,
297run `╎ n` and collapsed `⋯ n` rows when shown, and the detail-line marks
298(`+` added, `~` modified, `−` deleted, `✖` unresolved) when a detail line is
299present. A collapsed node shows `⋯ n` only when `n > 0`, so the `⋯` legend
300entry appears only then. When any rendered node is flattened, it also includes
301`» branch exceeds lane limit; increase lanes`. Node glyphs in the overflow
302status column contribute their normal legend entries. The rails characters
303are not itemised; they are structural (§Step 4).
305The legend text is faint (dim). Placement: when stdout is a terminal and the
306tree's widest line plus the legend fits the terminal width, the legend is set
307on the right, one entry per tree row starting from the top, separated from the
308tree by a gap; otherwise it is set at the bottom, after a blank line.
312History (times are ages): root `zzzz` → 14 uninteresting commits → `aaaa`
313(3w, "add parser") → `kpqx` (2w, "release 1.2", `main`, trunk head).
314`aaaa` also has the leaf `mnrv` (1w, "fix lexer"). `kpqx` has the focus
315`wqzt` (2d, "wip", `feature`, conflicted) and the empty `ptlm` (1d, "docs",
3163 hidden descendants). `wqzt` has the leaves `qrst` (5h, "spike") and `yxsk`
317(1h). Options: `detail = 2, margin = true, elide = true, lanes = 3`.
319Row order, and the lane state after each row (`·` empty, `L` live, `R` reserved):
321| # | node | why now | rule | lane | lanes 0 1 2 after |
322|---|---|---|---|---|---|
323| 1 | `╎ 14` | the single-child root is dropped; the 14-commit run heads the tree | 2 | 0 | L · · |
324| 2 | `aaaa` | only ready node | 2 | 0 | L R(mnrv) · — `mnrv` comes after the trunk child `kpqx`, so it is reserved here |
325| 3 | `kpqx` | 2w < 1w? no — ready = {kpqx 2w, mnrv 1w}, oldest is kpqx | 2 | 0 | L R · |
326| 4 | `mnrv` | ready = {mnrv 1w, wqzt 2d, ptlm 1d} | 3 | 1 | L · · — leaf, lane 1 empties |
327| 5 | `wqzt` | ready = {wqzt 2d, ptlm 1d} | 5 | 1 | L L · — fork from lane 0 |
328| 6 | `ptlm` | ready = {ptlm 1d, qrst 5h, yxsk 1h} | 5 | 2 | · L · — last child of the trunk head: `╰`; collapsed, lane 2 empties |
329| 7 | `qrst` | ready = {qrst 5h, yxsk 1h} | 5 | 2 | · L · — leaf |
330| 8 | `yxsk` | last | 4 | 1 | · · · — inherits, leaf |
336 ◆─╮ @aaaa ▅ add parser 3w mo
337 ◆ │ @kpqx ▅ release 1.2 main 2w mo
338 │ ○ @mnrv ▂ fix lexer 1w mo
339▶ ├─⊗ @wqzt ▃ wip feature 2d mo
340 │ │ ✖ src/lexer.rs ~ src/parser.rs + tests/lexer.rs
341 ╰─┼─◌ @ptlm docs ⋯ 3 1d ak
342 ├─○ @qrst ▁ spike 5h mo
346Column offsets in this example: gutter 0–1, rails 2–7, id from 9, size bar
34715, message from 18, labels from 31, margin from 40, and the detail line's
348marks from 11. Lane 0 is `◆ ◆ │ ├ ╰` from `aaaa` to `ptlm` and then empty;
349the reservation for `mnrv` is drawn on `aaaa`'s row as `─╮` and consumed on
350`mnrv`'s row; `ptlm` forks to lane 2 across the live lane 1, hence `┼`.
352The reference `config.j` defines `tree` with `detail = 1, margin = false,
353elide = false` (every commit, time-sorted), `treeCompact` with `elide = true`
354(the folded view above), and `treeFull` with `detail = 2, margin = true,
357#### Decisions taken in this spec (change any that are wrong)
3591. **Trunk source** — the `trunk` revset from `config.j`, read by the binary;
360 empty trunk → root only in lane 0.
3612. **Row order** — oldest first, root at the top, matching the language's
362 `up`/`top` vocabulary. Newest-first (jj style) is the same algorithm with
363 the rows reversed and `╭`/`╯` for `╰`/`╮`.
3643. **Lane inheritance** — outside the trunk, a node's lane goes to its
365 *newest* child, so older children fork just in time and no lane is ever
366 reserved except by trunk commits. The alternative (oldest inherits) needs
367 a reservation on every fork.
3684. **Reservations drawn on the parent's row** (`◆─╮`), not on a connector row
369 of their own, so a fork never costs a row.
3705. **Fork targets** — leftmost empty lane to the right of the source. Allowing
371 targets to the left would narrow some graphs but needs `╭─┤` shapes.
3726. **`lanes` is an option**, fixed across renders. Computing it per render
373 would keep columns fixed within one output but shift them between
3757. **Overflow flattens** rather than hides, marked `»`.
3768. **Focus marker** stays in the leftmost gutter, as today.
3779. **Message truncation** to the terminal width, so the label and margin
keysesc to close
- j k
- move down, up
- enter
- open
- esc
- back, or up a level
- 1 2 3 4
- readme, files, commits, refs
- /
- filter
- y
- copy the clone command
- c
- compare the chosen commit with another (or shift-click both)
- ?
- show or hide this list