Show progress and spinners¶
output has three progress-feedback helpers, all obtained from a Renderer so
they share its writer, theme and interactivity. Each animates in a terminal and
prints plain lines under CI — the same code, no branching.
Point the renderer at os.Stderr for progress feedback so it does not pollute a
piped stdout:
Spinner — indeterminate work¶
Interactive: an animated spinner next to the message that clears on completion.
Non-interactive: Fetching release... then Fetching release... done (or
... failed). A Ctrl-C while the spinner is active cancels fn's context and
returns context.Canceled — never a false success.
Need a return value? Use the free generic function:
release, err := output.SpinWithResult(r, ctx, "Fetching", func(ctx context.Context) (Release, error) {
return fetch(ctx)
})
Progress bar — known total¶
p := r.Progress(len(files), "Uploading")
for range files {
upload()
p.Increment() // or p.IncrementBy(n)
}
p.Done()
Interactive: a live bar Uploading [=====> ] 12/40 (30%). Non-interactive: a
line at each 10% boundary, so logs are informative without flooding.
Status — multi-step operations¶
st := r.Status()
st.Update("Resolving dependencies")
st.Success("Dependencies resolved")
st.Update("Building")
st.Fail("Build failed")
st.Done()
Update replaces the current line in place (interactive) with a spinner-framed
message; Success/Warn/Fail finalise the current step onto its own line with
the themed icon; Done clears any pending in-place line.
Change the icons, colours, spinner frames and bar runes via theming.
Run one at a time¶
Status and Progress each hold their own mutex, so a single instance is safe
to drive from several goroutines. Nothing coordinates between helpers: two
Status values, or a Status and a Progress, writing to the same destination
will overwrite each other's in-place redraws. Spin is stricter still — it
takes over the terminal with a Bubble Tea program and must not overlap with
another spinner.
Show one piece of live feedback at a time, and finish it (Done, or the return
of Spin) before starting the next.
What is not configurable¶
The bar is a fixed 40 characters wide and does not adapt to terminal width; on a narrow terminal the line wraps. The non-interactive form logs at fixed 10% boundaries. Only the glyphs are themeable. Full list in what this does not do.
Why the spinner stops animating when you pipe the command¶
Interactivity is detected from stdout, even for a renderer writing to
stderr — so mytool | jq prints plain lines rather than animating, on both
streams. That is deliberate;
interactive vs plain rendering
explains why, and how to override it.