- Go 95%
- Just 5%
| Filename | Latest commit message | Latest commit date |
|---|---|---|
|
All checks were successful
ci/woodpecker/push/build Pipeline was successful
.woodpecker/release.yml |
||
| .just | ||
| .woodpecker | ||
| internal | ||
| .gitignore | ||
| CHANGELOG.md | ||
| config.example.toml | ||
| go.mod | ||
| go.sum | ||
| justfile | ||
| main.go | ||
| main_test.go | ||
| README.md | ||
cairn
A small, fast zsh prompt. Like starship, but doing far less: a handful of segments, no per-language modules, and no process on any prompt.
[default] ⚓ ~/dev/vlv/moor% ✘1 4s main
Install
just install # to $XDG_BIN_HOME, or ~/.local/bin
Then, in ~/.zshrc, after moor's hook:
PS1='%~%# '
(( $+commands[cairn] )) && eval "$(cairn hook zsh)"
The plain PS1 is the prompt for a machine without cairn: the hook only ever
replaces it, so a missing binary or a broken config leaves exactly that, and
nothing on stderr. cairn status says which config it read and why it would
not load.
cairn sets PS1 and RPS1 itself, before every prompt, so it replaces a
promptinit theme rather than adding to one.
Segments
| Segment | Draws | When |
|---|---|---|
sandbox |
[profile], yellow |
inside a claudia sandbox ($CLAUDIA_PID) |
anchor |
⚓, red when outside the domain's root |
a moor domain is active |
host |
%m |
over ssh ($SSH_TTY) |
dir |
%~ |
always |
status |
✘N, red |
the last command failed |
duration |
4s, 1m5s, 1h2m, yellow |
the last command ran min_duration or longer |
jobs |
&N, cyan |
background jobs exist |
branch |
the branch, or the short hash detached, magenta | inside a git checkout or worktree |
A segment with nothing to say draws nothing, its separator included. The left
side ends in %#. Placement is the whole config — see config.example.toml:
left = ["sandbox", "anchor", "host", "dir"]
right = ["status", "duration", "jobs", "branch"]
min_duration = 3
Those are the defaults, and the left side of them is the prompt cairn
replaced. The anchor used to be drawn by moor itself; moor now only exports the
state (MOOR_DOMAIN, MOOR_ROOT), and drawing it is cairn's.
Why it is fast
The binary runs once per shell, when ~/.zshrc evals cairn hook zsh. That
is also the only time the config is read: the hook it prints is plain zsh, one
function per segment the config places and none for the rest, and a precmd
that assembles them.
From then on a prompt is zsh alone. Everything a segment needs is already in
the shell — $PWD, $?, the job table, moor's exported variables — except the
branch, which is a walk up to the nearest .git (a stat per directory) and one
read of HEAD. git itself is never run.
What that buys, measured on the default prompt in a repo inside a domain:
| Per prompt | Cost |
|---|---|
| cairn, every default segment | ~37 µs |
| starting any Go binary, doing nothing | ~570 µs |
git symbolic-ref for the branch |
~1.6 ms |
just bench
The benchmark drives one zsh through many prompts and times it from inside, so
start-up is not counted. TestAPromptSpawnsNothing holds the other half: the
whole default prompt draws with PATH empty.
The branch is drawn, not run
A branch name comes from the repo, and git allows one called $(anything).
With promptsubst set, zsh runs whatever substitution it finds in PS1 at draw
time — so a branch placed in PS1 as text would run when you cd into a clone.
cairn never places it there: under promptsubst, PS1 is ${_cairn_ps1}, and
zsh does not re-expand what an expansion produced; without it, the value goes in
directly and nothing is expanded. Either way a % from outside (a branch, a
profile name) is doubled, so it is drawn rather than read as an escape.
Not yet
A dirty-tree marker. It is the one segment that needs a process — git status
grows with the repo — so it would run in the background and redraw the prompt
when done, rather than hold it up. The design leaves room for it; nothing here
does it yet.