# 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. Cross-platform (Unix, Windows). Depends only on the `color` package for 24-bit RGB inputs and 256-color automatic degradation. ## 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 color.RGB) Style` | Starts a style with foreground color. | | `(s Style) Bg(c color.RGB) Style` | Adds background color. | | `(s Style) Bold() Style` | Adds bold attribute (also: `Dim`, `Italic`, `Underline`, `Blink`, `Reverse`). | ```go warn := inline.Fg(color.Amber).Bold() 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 dynamically handled by the `color` 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: ```go 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: ```go package main import ( "fmt" "os" "time" "github.com/lixenwraith/color" "github.com/lixenwraith/terminal/inline" ) func main() { p := inline.New(os.Stdout) name := inline.Fg(color.LightSkyBlue).Bold() okSt := inline.Fg(color.LimeGreen).Bold() dim := inline.Fg(color.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)) } ``` ## Notes - Width is measured in runes (`unicode/utf8`); East Asian wide characters and combining marks are not width-aware. - Live block lines must occupy one visual row each: no `\n`, tabs, or control characters. Lines are truncated to terminal width automatically; embedded SGR from `Paint` is preserved through truncation. - The live block is clamped to terminal height − 1 rows (newest lines kept). - Pass external strings (package names, paths) as `Log` arguments, 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.