Expand description
One check, one block — and while it runs, one line: per-check output capture plus a live progress region for the concurrent stage.
Twenty checks used to print straight to inherited stdio from their own threads, so two failing linters shuffled their lines together and the reader un-shuffled them by hand — the dispatcher’s roll-up existed partly to apologise for it. Now every check writes into its own slot, and a completed check’s output reaches stdout as ONE locked write: contiguous, whatever the other nineteen were doing.
Three writers feed a slot:
- The check’s own thread, through
say— which is whatcommon::ok/fail/warncall. A thread with no slot installed (commit-msg,amont install, the dispatcher itself) prints directly, exactly as before; nothing outside a stage changes. - A captured child’s reader threads, through
Stage::append_raw— they are not the check’s thread, so the thread-local cannot carry the routing; theArcis captured before the spawn instead. - Nobody else. The dispatcher’s own lines (skips, pins, the roll-up) happen strictly before or after the fan-out and stay direct.
Order across checks is COMPLETION order — deterministic per block, not
per stage, which is the same nondeterminism the interleaved version had
without the shuffling. amont.progress false switches the whole
mechanism off and restores raw streaming for anyone who wants to watch a
tool write in real time.
§The region
When stderr is a real terminal (watching) the stage also paints a
live region UNDER the finished blocks: one line per running check —
braille spinner, name, elapsed — repainted every 80ms by a ticker
thread, shrinking as checks finish, gone without a trace when the stage
ends. Blocks go to stdout, the region to stderr; both feed one tty, and
every write to either happens under the same Stage::out lock, so a
block never tears a repaint in half. Piped, redirected, TERM=dumb, or
CI: watching is false, no ticker starts, and the region costs
nothing — which is also why the test suite (piped stdio throughout)
exercises capture but never the paint.
Structs§
- Finish
OnDrop - Emits slot
idx’s block when dropped — however the check’s closure exits, a panic included: the partial output of a check that died still reaches the reader, above the dead-check verdict the runner fills in. - Sink
Guard - Uninstalls the thread’s sink on drop, whatever path the check took out.
- Stage
- A running stage: the slots, and the one lock every terminal write inside the stage goes through.
Functions§
- current_
sink - The sink installed on THIS thread, if any — how a child-capture helper on the check’s own thread learns where the reader threads should append.
- enabled
- Whether the capture mechanism is on at all.
amont.progress falseis the escape hatch back to raw streaming — one knob, read once. - say
- One line of check output, wherever it should go.
- watching
- Is anyone watching? True only when stderr is a real terminal that speaks
VT: not piped, not redirected, not
TERM=dumb— and on Windows only withTERMactually set, because bare conhost may not interpret the cursor codes the region depends on. This is the paint gate; capture (enabled) does not consult it.