Skip to content

Add colour support for chain labels in terminal output - #5

Merged
inxilpro merged 2 commits into
mainfrom
claude/chain-label-colors-ufy1gk
Aug 20, 2026
Merged

inxilpro merged 2 commits into
mainfrom
claude/chain-label-colors-ufy1gk

Conversation

@inxilpro

Copy link
Copy Markdown
Contributor

Summary

This change adds optional colour support to parallel.sh, allowing each chain's label to be displayed in a distinct colour when output is sent to a terminal. The colour is applied only to the label and separator bar, leaving command output untouched.

Key Changes

  • Colour detection logic (_probe_color): Determines whether to use colours based on a priority system:

    • Terminal capability (checks for 8+ colours via tput or TERM)
    • NO_COLOR environment variable (disables colour if set)
    • FORCE_COLOR environment variable (enables/disables colour, with 0 meaning disable)
    • COLOR variable (explicit control: 1/yes/on/true/always or 0/no/off/false/never)
  • Colour assignment (_colors): Distributes colours from COLOR_PALETTE to chains in declaration order, cycling through the palette when more chains exist than colours

  • Output formatting: Modified _emit and _outcome functions to wrap labels and separators with colour codes while preserving command output exactly as written

  • Configuration variables:

    • COLOR: Controls colour mode (defaults to auto)
    • COLOR_PALETTE: SGR parameters for colours (defaults to 36 35 32 33 34 — cyan, magenta, green, yellow, blue)
  • Test coverage: Comprehensive test suite covering colour detection, palette cycling, empty palettes, outcome formatting, grouped output, and all four decision factors

Implementation Details

  • Colour codes are applied only to labels and bars; command output is never modified
  • Outcome markers ([!], [.]) remain plain; only the label is coloured
  • Uses POSIX-compatible SGR escape sequences written as literal bytes
  • Gracefully degrades to plain text when output is not a terminal or when colour is disabled
  • Follows established conventions (NO_COLOR, FORCE_COLOR) used by other build tools

https://claude.ai/code/session_017gYMCt5QBRcZ5J2RgUQoD4

claude added 2 commits August 20, 2026 11:50
A build with four chains streams four labels down the left of the terminal,
and telling them apart meant reading them. Each chain now gets a colour from
a palette, in declaration order and round again from the top when a build has
more chains than there are colours, and the colour covers the label and the
bar in both layouts: the bars make a column in the chain's colour, and what a
command wrote goes out exactly as it wrote it. The `[!]` and `[.]` in front of
an outcome line stay plain, so the colour says which chain and nothing else.

Colour is on where `run` prints to a terminal that says it has colours, and
off everywhere else, so a build that pipes its output to a file or greps it
for `[!]` gets the same bytes it got before. Four things decide, each one
overruling the one before it:

- the terminal: stdout is one, and terminfo reports eight colours or more.
  `TERM` unset or `dumb` counts as none, and where there is no `tput` to ask,
  a `TERM` that is set and is not `dumb` is taken at its word.
- `NO_COLOR`, set to anything at all: no colour.
- `FORCE_COLOR`, set to anything but `0`: colour. `FORCE_COLOR=0` is how a
  good many tools are told to stop, so it is read as a refusal rather than as
  the name being set.
- `COLOR`: `1`, `0`, or `auto` to leave it to the three above. Last, because
  it is the only one of the four aimed at this library.

The question is asked at `run` and not when the library is sourced, since the
answer is about the stream `run` prints to and a build script is free to
redirect its own output before it gets there.

`COLOR_PALETTE` is the colours themselves, as SGR parameters, so `1;36` is
bold cyan and `38;5;213` is one of the 256. It defaults to cyan, magenta,
green, yellow and blue: the colours a terminal has had since it had eight,
less red, which belongs to what a build says about its own failures, and less
black and white, which the rest of the line already is. Emptying it is another
way of asking for no colour, so the default fills in for a palette that was
never set rather than for one that was deliberately emptied.

The escape character is written as its bytes, the way the box drawing bar
already was: the sources stay ASCII, and `make lint` still says so.

Eleven examples cover it, including the precedence between the four and the
palette starting again once it runs out.
`set -- $COLOR_PALETTE` split the palette into colours everywhere the suite
runs but zsh, which does not split an unquoted expansion at all: there the
whole palette arrived as one colour and every label opened with
`ESC[36 35 32 33 34m`. Six examples said so on the zsh and macOS zsh jobs, and
nothing said so under the eight other shells.

The palette is walked a word at a time now, with the expansions that trim a
string, which every shell here does the same way. Leading spaces are trimmed
between words, so the spacing in a hand written palette does not matter and a
palette of nothing but them still reads as the empty one it is.

Verified under all nine Linux shells in the matrix rather than the three that
were to hand: dash, bash, bash --posix, zsh, zsh --emulate sh, mksh, ksh93,
posh and busybox ash, 53 examples and no failures under each.
@inxilpro
inxilpro merged commit 3405df7 into main Aug 20, 2026
30 checks passed
@inxilpro
inxilpro deleted the claude/chain-label-colors-ufy1gk branch August 20, 2026 13:58
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants