inline
Styled text and in-place progress in the normal terminal scrollback. No raw mode, no alternate screen, no input handling, no cursor hiding — the shell keeps owning the terminal. Intended for CLI tools (package managers, service tooling, build scripts) that want color and live status without a full-screen TUI.
Unix only (//go:build unix). Depends on the parent terminal package for
color types, capability detection, and RGB → 256 mapping.
Model
Output is split into two zones:
installed openssl ← permanent lines (Log) — scroll normally
installed zlib
⠹ installing curl ← live block (Update) — rewritten in place
[██████░░░░░░░░] 2/5
Log prints permanent lines above the live block; Update replaces the live
block by cursor-up + clear + rewrite; Done erases the block and optionally
prints final lines. Interleaving is handled internally — Log during an
active live block erases, prints, and redraws in one flush.
API
Printer
| Method | Description |
|---|---|
New(w io.Writer) *Printer |
Creates a printer. Terminal detection via size probe; color defaults on for terminals with NO_COLOR unset. Safe for concurrent use. |
Log(format string, a ...any) |
Prints one permanent line above the live block (Printf semantics, newline appended). |
Update(lines ...string) |
Replaces the live block, rewriting in place. No-op on non-terminal output. |
Done(final ...string) |
Erases the live block and prints final permanent lines. Call before exit. |
Paint(s string, st Style) string |
Returns s wrapped in SGR codes for the detected color mode, or unchanged when color is off. |
SetColor(on bool) |
Overrides color detection (e.g. force styling into a pipe for less -R). Affects Paint only; Update remains terminal-gated. |
Size() (w, h int) |
Current terminal dimensions, 80×24 when unknown. |
Style
Value type, zero value is unstyled, builder-composable:
| Function | Description |
|---|---|
Fg(c terminal.RGB) Style |
Starts a style with foreground color. |
(s Style) Bg(c terminal.RGB) Style |
Adds background color. |
(s Style) Attr(a terminal.Attr) Style |
Adds attribute bits (AttrBold, AttrDim, ...). |
warn := inline.Fg(terminal.Amber).Attr(terminal.AttrBold)
p.Log("%s low disk space", p.Paint("warning:", warn))
True color terminals get 38;2;R;G;B; 256-color terminals get 38;5;N via
Redmean mapping — same degradation path as the parent package.
Progress helpers
Pure string builders, no Printer required:
| Function | Description |
|---|---|
Bar(width int, pct float64, chars [3]rune) string |
Progress bar of width cells, pct clamped to [0,1], half-cell resolution via the partial rune. |
BarBlock |
Default character set [3]rune{'█', '▌', '░'}. |
Spinner(frame int) string |
Braille spinner frame for a monotonic counter. |
Compose with Paint for colored bars:
line := "[" + p.Paint(inline.Bar(30, pct, inline.BarBlock), barStyle) + "]"
Non-terminal output
When output is a pipe or file (CI, redirection): Update is a no-op, Paint
returns input unchanged, Log and Done print plain sequential text. A tool
using inline degrades to ordinary log output with no code changes.
Example
Simulated package installation — spinner, overall progress bar, permanent completion lines:
package main
import (
"fmt"
"os"
"time"
"github.com/lixenwraith/terminal"
"github.com/lixenwraith/terminal/inline"
)
func main() {
p := inline.New(os.Stdout)
name := inline.Fg(terminal.LightSkyBlue).Attr(terminal.AttrBold)
okSt := inline.Fg(terminal.LimeGreen).Attr(terminal.AttrBold)
dim := inline.Fg(terminal.IronGray)
pkgs := []string{"openssl", "zlib", "curl", "git", "go"}
frame := 0
for i, pkg := range pkgs {
const steps = 25
for s := range steps {
pct := (float64(i) + float64(s)/steps) / float64(len(pkgs))
p.Update(
inline.Spinner(frame)+" installing "+p.Paint(pkg, name),
"["+inline.Bar(32, pct, inline.BarBlock)+"] "+
p.Paint(fmt.Sprintf("%d/%d", i+1, len(pkgs)), dim),
)
frame++
time.Sleep(40 * time.Millisecond)
}
p.Log("%s %s", p.Paint("✓", okSt), pkg)
}
p.Done(p.Paint("✓ 5 packages installed", okSt))
}
Run in a terminal: the two-line status block animates in place while
completion lines accumulate above it. Piped (go run . | cat): only the
completion lines and the final summary appear, unstyled.
Notes
- Width is measured in runes (
unicode/utf8); East Asian wide characters and combining marks are not width-aware — same limitation astui. - Live block lines must occupy one visual row each: no
\n, tabs, or control characters. Lines are truncated to terminal width automatically; embedded SGR fromPaintis preserved through truncation. - The live block is clamped to terminal height − 1 rows (newest lines kept).
- Pass external strings (package names, paths) as
Logarguments, never as the format string. - Ctrl-C mid-update leaves the live block on screen but the terminal in a normal state — no raw mode or screen buffer to restore.