Help us shape the future of Go! Take the 2026 Go Developer Survey

ui

package
v0.7.2 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jun 20, 2026 License: MIT Imports: 9 Imported by: 0

Documentation

Overview

Package ui centralizes agentsync's terminal presentation: semantic color, a curated glyph vocabulary, and small layout primitives (sections, status lines, aligned labels). It is the single place that decides whether to emit ANSI, so every command renders through a *Printer and the color/glyph/spacing language stays consistent across `status`, `diff`, `doctor`, and `apply`.

Two independent axes:

  • Color is TTY-gated. `--color=always|never` forces it; `auto` (the default) enables color only when the output is a terminal and NO_COLOR (https://no-color.org) is unset. Non-TTY output (pipes, files, tests) is therefore byte-for-byte plain — color never leaks into a redirect.
  • Glyphs are always Unicode. The ✓ / ◐ / ✗ vocabulary already appears in the translation report and the capability matrix; keeping it unconditional means piped output reads the same as the screen and existing fixtures hold. Color, not glyph choice, is what degrades.

Color is reserved for state: a green ✓ means synced, a red ✗ means drift. It is never decoration. Everything still parses with color stripped.

Index

Constants

View Source
const (
	GlyphOK      = "✓" // success / synced / clean
	GlyphPartial = "◐" // partial coverage (mirrors the capability matrix)
	GlyphErr     = "✗" // failure / drift / missing
	GlyphWarn    = "⚠" // warning / needs attention
	GlyphInfo    = "•" // neutral bullet
	GlyphArrow   = "→" // transition / "see"
)

Curated glyph vocabulary. Always Unicode; each is one display column wide, so callers can align around them with plain rune/space counting (no runewidth).

View Source
const GlyphWarnEmoji = "⚠️"

GlyphWarnEmoji is the colourful warning sign (with VS16) used as the warning label prefix. Wider than one column in some terminals, which is fine — the warning lines are not part of any padded layout.

Variables

This section is empty.

Functions

func Pad

func Pad(s string, width int) string

Pad left-justifies s to a fixed visible width, counting runes (the glyph set is single-width) rather than bytes, then returns the padded plain string. Callers color the RESULT so that ANSI bytes never throw off the column — padding is applied before any escape codes exist.

Alignment is best-effort for non-ASCII: counting is per-rune, so wide/ ambiguous-width runes (CJK, emoji) and combining marks make the rune count diverge from the actual display-cell width and skew the column. This is a deliberate, purely cosmetic limitation — bringing in a grapheme-aware width table (golang.org/x/text/width) was judged not worth the dependency for the curated, mostly-ASCII output here. Sanitize removes the security-relevant deceptive runes (bidi/zero-width) but does not normalize width.

func Sanitize added in v0.5.0

func Sanitize(s string) string

Sanitize strips control characters and deceptive format runes from a string so untrusted text can be rendered to a terminal without smuggling escape sequences or spoofed/hidden text. It is a thin re-export of untrusted.Sanitize (which owns the implementation and its full doc) for the display sites that hold a plain composite/built string rather than an untrusted.Text — a Text sanitizes itself via its String() method, so prefer printing a Text directly. Apply at the display boundary, before width/Pad calculation, so a stripped rune never throws off column alignment.

Types

type ColorMode

type ColorMode int

ColorMode is the resolved value of the global --color flag.

const (
	ColorAuto ColorMode = iota
	ColorAlways
	ColorNever
)

func ParseColorMode

func ParseColorMode(s string) (ColorMode, error)

ParseColorMode maps the --color flag string to a ColorMode. An empty string defaults to auto so callers can pass the raw flag value.

type Printer

type Printer struct {
	Out io.Writer
	Err io.Writer
	// contains filtered or unexported fields
}

Printer renders styled output to a pair of writers. Construct one per command invocation via New; the color decision is frozen at construction.

func New

func New(out, err io.Writer, mode ColorMode) *Printer

New builds a Printer bound to out/err, resolving whether to emit color from mode, the NO_COLOR environment variable, and whether out is a terminal.

func (*Printer) Blue

func (p *Printer) Blue(s string) string

func (*Printer) Bold

func (p *Printer) Bold(s string) string

Semantic style helpers. Each returns s unchanged when color is disabled, so callers can compose them freely without branching.

func (*Printer) Color

func (p *Printer) Color() bool

Color reports whether this Printer emits ANSI. Commands that hand a writer to a third-party renderer (e.g. the diff library's own colorizer) consult this to gate that output through the same decision.

func (*Printer) Cyan

func (p *Printer) Cyan(s string) string

func (*Printer) Faint

func (p *Printer) Faint(s string) string

func (*Printer) Green

func (p *Printer) Green(s string) string

func (*Printer) Red

func (p *Printer) Red(s string) string

func (*Printer) Section

func (p *Printer) Section(title string)

Section prints a heading (bold when colored, plain text otherwise) to Out.

func (*Printer) Spin

func (p *Printer) Spin(label string) func()

Spin is the one-call helper: it starts a Spinner and returns the stop function. Typical use:

stop := p.Spin("fetching marketplace github")
result, err := fetcher.Fetch(src, cacheDir)
stop()
if err != nil { ... }

func (*Printer) Spinner

func (p *Printer) Spinner(label string) *Spinner

Spinner builds a Spinner bound to p.Err and inheriting p's color decision. Call Start to begin and Stop to end; both are idempotent and safe to call in either order (a Stop without Start is a no-op).

func (*Printer) Yellow

func (p *Printer) Yellow(s string) string

type Spinner

type Spinner struct {
	// contains filtered or unexported fields
}

Spinner is a lightweight in-place progress indicator for slow network ops (marketplace fetch, plugin pull). It animates only when its writer is a terminal; off a terminal (CI logs, piped stderr, captured-output tests) it is a complete no-op — no animation, no static fallback line — so byte-stable fixtures stay byte-stable and grep'd output stays clean. The success line a caller already prints carries the result.

func (*Spinner) Start

func (s *Spinner) Start()

Start begins the animation. Idempotent; no-op on a non-terminal writer.

func (*Spinner) Stop

func (s *Spinner) Stop()

Stop ends the animation, clears the spinner line, and blocks until the animation goroutine has flushed. Idempotent; safe to call before Start (in which case it is a no-op).

type WarnWriter

type WarnWriter struct {
	// contains filtered or unexported fields
}

WarnWriter wraps a destination writer and styles "warning: " line prefixes as a bold-yellow "⚠️ warning:" so every warning — whether emitted by the CLI itself, by an adapter's Ingest, or by capture's re-reference path — reads consistently. Lines that do not start with the literal "warning: " prefix (e.g. pre-styled ANSI lines, indented continuation lines, or "agentsync:" notes) pass through verbatim. The writer is line-buffered so a callers' partial Write is held until a newline arrives — fmt.Fprintf in practice always finishes a line per call, but buffering keeps a chunked writer correct.

Not safe for concurrent use: the line-assembly buffer is unsynchronized. One *WarnWriter per command invocation is the intended pattern.

func NewWarnWriter

func NewWarnWriter(w io.Writer, p *Printer) *WarnWriter

NewWarnWriter returns a *WarnWriter that flushes styled lines to w using p. p's color decision is honored: with color off, the prefix becomes a plain "⚠️ warning:" (the glyph is content, not decoration — same rule as the curated glyph vocabulary above).

func (*WarnWriter) Flush

func (s *WarnWriter) Flush()

Flush emits any buffered partial line (no trailing \n) as-is. Call at end of command if you've routed a writer that may not always end in \n; the import path does always terminate, so this is defensive.

func (*WarnWriter) RouteTo

func (s *WarnWriter) RouteTo(a any) func()

RouteTo wires this writer into anything that exposes a SetStderr(io.Writer) setter (matching adapter.WarnEmitter) and returns a restore function that detaches the writer when invoked. Idiomatic use pairs with defer:

defer warnW.RouteTo(a)()

The inner RouteTo(a) call evaluates immediately (wires the writer); the outer () is the deferred restore. The returned function is always safe to call — it's a no-op when the target doesn't implement the setter, when the target is a typed-nil pointer, or when the target was an untyped nil — so callers never need to type-assert or nil-check.

Non-implementor cases that resolve to a silent no-op:

  • untyped nil (`any(nil)`): the type-assert misses because the interface value carries no concrete type.
  • typed nil (`var a *T = nil; RouteTo(a)`): the type-assert SUCCEEDS because the interface value holds the method set of *T, but calling SetStderr would dereference the nil pointer. RouteTo guards against this via reflect.
  • any value whose dynamic type doesn't implement SetStderr.

func (*WarnWriter) Write

func (s *WarnWriter) Write(p []byte) (int, error)

Write line-buffers data, emitting completed lines through emit. Partial trailing bytes are retained for the next Write. Always returns len(p), nil (the contract callers like fmt.Fprintf expect).

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL