gorich

package module
v1.1.2 Latest Latest
Warning

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

Go to latest
Published: Jul 20, 2026 License: MIT Imports: 5 Imported by: 0

README

gorich

Styled text with markup syntax, multi-task progress bars, tables, spinners, and live displays. All flicker-free.

CI Release License Last Commit Stars Go Reference

Features

  • Rich Print - Styled text with [bold red]markup[/] syntax
  • Progress Bars - Multiple concurrent tasks with customizable columns and per-group sections
  • Live Blocks - Growing per-task output blocks with animated spinners, styled line prefixes, and optional space reservation
  • Tables - Bordered tables with column styling, markup cells, row styles, footers, sections, and 19 box styles
  • Flicker-free - Single-write buffered output with synchronized-output (DEC 2026) frames for smooth updates
  • Speed estimation - ETA calculation with rolling average
  • File progress - io.Reader/io.Writer wrappers for IO tracking
  • 50+ spinners - Animated spinners from cli-spinners
  • Color support - Truecolor, 256-color, and 16-color with auto-downgrading
  • Thread-safe - Safe for concurrent updates from multiple goroutines

Installation

go get github.com/depado/gorich

Quick Start - Rich Print

package main

import (
    "github.com/depado/gorich"
    "github.com/depado/gorich/console"
)

func main() {
    // Styled text with markup
    gorich.Print("[bold]Hello[/] [red]World[/]")
    gorich.Print("[italic green]Success![/]")
    gorich.Print("[bold white on blue]Highlighted[/]")

    // Hex and RGB colors
    gorich.Print("[#ff6600]Orange[/]")
    gorich.Print("[rgb(100,150,200)]Custom color[/]")

    // Printf style (no trailing newline, like standard printf)
    gorich.Printf("[bold]Count:[/] %d", 42)
    gorich.Printf(" - [green]done[/]\n")

    // Sprint/Sprintf return the rendered ANSI string instead of printing
    s := gorich.Sprintf("[bold]Count:[/] %d", 42)
    _ = s

    // Horizontal rules with optional styling
    gorich.Rule("Section Title")
    gorich.Rule("Styled Rule", console.WithRuleStyle("blue"))
    gorich.Rule("Styled Title", console.WithTitleStyle("bold red"))
}

Markup Syntax

Syntax Description
[bold]text[/] Bold text
[italic]text[/] Italic text*
[underline]text[/] Underlined text
[strike]text[/] Strikethrough
[red]text[/] Named colors
[bright_red]text[/] Bright variants
[#ff0000]text[/] Hex colors
[rgb(255,0,0)]text[/] RGB colors
[bold red]text[/] Combined styles
[white on red]text[/] Background colors
[bold red on white]text[/] Full style
\\[text] Escaped brackets

*Italic support depends on your terminal and font. Many terminals don't support italic or require a font with italic glyphs.

Horizontal Rules

// Simple rule
gorich.Rule("Section Title")

// Style the rule line
gorich.Rule("Blue Line", console.WithRuleStyle("blue"))

// Style the title
gorich.Rule("Bold Title", console.WithTitleStyle("bold red"))

// Style both
gorich.Rule("Fancy", console.WithRuleStyle("dim"), console.WithTitleStyle("bold yellow"))

// Markup in title (combined with title style)
gorich.Rule("[green]Success[/]", console.WithRuleStyle("cyan"))

// Empty rule (just a line)
gorich.Rule("")

Progress Bars

package main

import (
    "context"
    "time"

    "github.com/depado/gorich/progress"
)

func main() {
    p := progress.New()
    p.Start(context.Background())
    defer p.Stop()

    total := 100.0
    task := p.AddTask("Processing", &total)

    for i := 0; i < 100; i++ {
        time.Sleep(50 * time.Millisecond)
        p.Advance(task, 1)
    }
}

Output:

Processing ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━  75% • 0:00:02

Multiple Tasks

Task descriptions support the same markup syntax as gorich.Print():

p := progress.New()
p.Start(context.Background())
defer p.Stop()

total1, total2, total3 := 100.0, 200.0, 150.0

// Descriptions support [markup] syntax for colors and styles
task1 := p.AddTask("[cyan]Downloading[/]", &total1)
task2 := p.AddTask("[yellow]Processing[/]", &total2)
task3 := p.AddTask("[magenta]Uploading[/]", &total3)

// Update tasks concurrently - it's thread-safe
go func() {
    for i := 0; i < 100; i++ {
        p.Advance(task1, 1)
        time.Sleep(20 * time.Millisecond)
    }
}()
// ... similar for other tasks

Output:

Downloading ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 100% • 0:00:00
Processing  ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━  65% • 0:00:03
Uploading   ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━  80% • 0:00:01

Descriptions are left-aligned and automatically sized to the widest one so the bars line up. Pass progress.WithJustify(console.JustifyRight) for the classic right-aligned look, or progress.WithWidth(n) for a fixed width:

progress.DescriptionColumn(progress.WithJustify(console.JustifyRight))
progress.DescriptionColumn(progress.WithWidth(20))

Custom Columns

Customize the progress display with different column types:

p := progress.New(
    progress.WithColumns(
        progress.NewSpinnerColumn(),
        progress.DescriptionColumn(),
        progress.NewBarColumn(progress.WithBarWidth(30)),
        progress.NewDownloadColumn(false),
        progress.NewTransferSpeedColumn(false),
        progress.NewTimeRemainingColumn(),
    ),
)

Available Columns

Column Description Example Output
DescriptionColumn() Task description, markup + auto-width (left-aligned) Downloading
NewBarColumn() Visual progress bar (turns green when done) ━━━━━━━━━━━━━━━━
NewTaskProgressColumn(showSpeed) Percentage or speed 75%
NewTimeRemainingColumn() Estimated time remaining 0:00:15
NewTimeElapsedColumn() Elapsed time 0:01:23
NewSpinnerColumn() Animated spinner
NewDownloadColumn(binary) Download progress 5.2 MB/10.0 MB
NewTransferSpeedColumn(binary) Transfer rate 1.5 MB/s
NewMofNCompleteColumn(sep) M of N items 50/100
NewSeparatorColumn(text) Static separator

File Progress

Track progress while reading files:

p := progress.New()
p.Start(context.Background())
defer p.Stop()

// Wrap a file with progress tracking
reader, taskID, err := p.WrapFile("large-file.bin", "Reading")
if err != nil {
    log.Fatal(err)
}
defer reader.Close()

// Read as normal - progress updates automatically
io.Copy(io.Discard, reader)

Or wrap any io.Reader:

resp, _ := http.Get("https://example.com/file.zip")
reader, taskID := p.WrapReader(resp.Body, resp.ContentLength, "Downloading")
io.Copy(file, reader)

Indeterminate Progress

For tasks with unknown total, pass nil - the bar will show a pulsing animation:

task := p.AddTask("Searching", nil)  // nil total = indeterminate

// Later, when you know the total:
total := 500.0
p.Update(task, progress.TaskUpdateConfig{Total: &total})

Marking Tasks as Done

Use Done() to explicitly mark a task as finished and optionally update its description:

task := p.AddTask("Waiting for API...", nil)  // indeterminate task
p.Done(task, "[green]Connected![/]")          // mark done + update description

Done() works on both indeterminate and determinate tasks. For determinate tasks that haven't reached 100%, it also sets completed to total for a proper visual finish (green bar, checkmark in spinner, etc.).

Sections

By default all tasks share one column layout. Use sections to give groups of tasks their own columns and indentation - for example a full summary bar on top with lightweight worker rows underneath:

p := progress.New(
    progress.WithColumns(                     // default section (section 0)
        progress.NewSpinnerColumn(),
        progress.DescriptionColumn(),
        progress.NewBarColumn(),
        progress.NewTaskProgressColumn(false),
        progress.NewSeparatorColumn("•"),
        progress.NewTimeRemainingColumn(),
    ),
)

// A second section with fewer columns, indented by 2 spaces
workers := p.AddSection(
    progress.WithSectionColumns(
        progress.NewSpinnerColumn(),
        progress.DescriptionColumn(),
    ),
    progress.WithSectionIndent(2),
)

p.Start(context.Background())
defer p.Stop()

total := 88.0
summary := p.AddTask("[bold]Syncing depado[/]", &total)   // goes to section 0
w1 := workers.AddTask("[cyan]articles - cloning...[/]", nil)  // goes to workers

// Every task is addressed by its TaskID regardless of section
p.Advance(summary, 1)
p.Done(w1, "[green]articles[/]")

Output:

⠸ Syncing depado ━━━━━━━━━━━━━━━━━━━━  45% • 0:01:23
  ⠸ articles - cloning...
  ⠸ buoy - pulling...
  ✓ gorich

Sections are a rendering grouping only: task updates always go through the Progress methods by TaskID. If you never call AddSection, behavior is identical to a single default section. Each section auto-sizes its own DescriptionColumn independently.

Live Blocks

BlockDisplay is a console.Renderable for showing a growing list of output blocks. Useful when running parallel commands and streaming their output in a live, in-place display. Each block has a header with an animated spinner while running (which freezes into ✓ or ✗ on completion), followed by its last N output lines. Wrap it in a live.Live for auto-refresh:

package main

import (
    "context"
    "os/exec"
    "sync"
    "time"

    "github.com/depado/gorich/console"
    "github.com/depado/gorich/live"
)

func main() {
    c := console.New()
    display := live.NewBlockDisplay(
        live.WithBlockMaxLines(3),
        live.WithBlockSpinnerName("dots"),
        live.WithBlockPrefix("[blue]│ [/]"),
        live.WithBlockReserveSpace(false),
    )

    l := live.New(c, display, live.WithAutoRefresh(true), live.WithRefreshRate(15))
    l.Start(context.Background())
    defer l.Stop()

    var wg sync.WaitGroup
    repos := []struct{ name, expr string }{
        {"alpha", "echo hi && sleep 0.5 && echo there && sleep 0.3 && echo done"},
        {"delta", "echo failing && echo badly && sleep 0.3 && exit 2"},
    }
    for _, r := range repos {
        wg.Add(1)
        go func(r struct{ name, expr string }) {
            defer wg.Done()
            idx := display.Start(r.name)
            out := display.NewWriter(idx)
            cmd := exec.Command("sh", "-c", r.expr)
            cmd.Stdout = out
            cmd.Stderr = out
            if err := cmd.Run(); err != nil {
                if e, ok := err.(*exec.ExitError); ok {
                    display.Finish(idx, e.ExitCode())
                } else {
                    display.Finish(idx, 1)
                }
            } else {
                display.Finish(idx, 0)
            }
        }(r)
    }
    wg.Wait()
    time.Sleep(300 * time.Millisecond)
}

Output (in a terminal):

⠹ alpha
│ hi
│ there
✗ delta (exit 2) (312ms)
│ failing
│ badly
✓ alpha (807ms)
│ hi
│ there
│ done

Block options

Option Default Description
WithBlockMaxLines(n) 3 Max output lines kept per block (ring buffer)
WithBlockSpinnerName(name) "dots" Spinner animation for running blocks (see Spinners)
WithBlockPrefix(prefix) " " String prepended to every output line. Supports markup; e.g. "[blue]│ [/]" renders a blue vertical bar
-- -- Output lines passed to AppendLine / BlockWriter also support markup. Use "[red]error[/]" for stderr, "[dim]output[/]" for stdout, or plain text (inherits the block's OutputStyle)
-- -- Block titles passed to Start also support markup; e.g. display.Start("[cyan]api[/]"). Markup layers over the status style, so [white]auth[/] on a finished block keeps the status bold and only overrides the color
WithBlockReserveSpace(bool) false When true, pads each block with blank lines up to maxLines so the height is stable from the start. When false (default), blocks grow organically as output arrives

Per-block style overrides

Each Block (obtained indirectly via Start) has optional style fields that override the defaults:

type Block struct {
    SpinnerStyle   *style.Style  // nil = cyan
    RunningStyle   *style.Style  // nil = unstyled; base style, markup in the title layers on top
    SucceededStyle *style.Style  // nil = green bold
    FailedStyle    *style.Style  // nil = red bold
    OutputStyle    *style.Style  // nil = dim
    // ...plus Title, Status, Lines, Elapsed, ExitCode
}

API

display := live.NewBlockDisplay(opts...)

// Lifecycle
idx := display.Start("repo-name")        // append a running block, get its index
display.AppendLine(idx, "output line")    // add a line (ring-buffered to maxLines)
display.Finish(idx, exitCode)             // mark done (0=succeeded, non-zero=failed)

// IO
writer := display.NewWriter(idx)          // io.Writer that flushes complete lines

BlockDisplay implements console.Renderable and is safe for concurrent use Start, AppendLine, Finish, and BlockWriter.Write may be called from any goroutine.

Configuration Options

p := progress.New(
    progress.WithConsole(customConsole),       // Custom console
    progress.WithRefreshRate(15),              // 15 Hz refresh (default: 10)
    progress.WithSpeedEstimatePeriod(60),      // 60s speed window (default: 30)
    progress.WithTransient(true),              // Clear display when done
    progress.WithDisable(true),                // Disable output (for CI)
)

Task Options

task := p.AddTask("Processing", &total,
    progress.TaskWithCompleted(50),           // Start at 50%
    progress.TaskWithVisible(false),          // Hidden initially
    progress.TaskWithStart(false),            // Don't start timer yet
    progress.TaskWithFields(map[string]any{   // Custom fields
        "filename": "data.csv",
    }),
)

// Control task timing
p.StartTask(task)   // Start the timer
p.StopTask(task)    // Pause the timer
p.ResetTask(task, true)  // Reset and restart

Tables

Create bordered tables with styled columns, markup cells, row formatting, footers, sections, flexible column widths, and 19 box styles.

package main

import (
    "github.com/depado/gorich/console"
    "github.com/depado/gorich/table"
)

func main() {
    c := console.New()

    tbl := table.NewTable("Name", "Age", "City")
    tbl.AddRow("Alice", "30", "New York")
    tbl.AddRow("Bob", "25", "San Francisco")
    tbl.AddRow("Charlie", "35", "London")
    c.Render(tbl)
}

Output:

┏━━━━━━━━━┳━━━━━┳━━━━━━━━━━━━━━━┓
┃ Name    ┃ Age ┃ City          ┃
┡━━━━━━━━━╇━━━━━╇━━━━━━━━━━━━━━━┩
│ Alice   │ 30  │ New York      │
│ Bob     │ 25  │ San Francisco │
│ Charlie │ 35  │ London        │
└─────────┴─────┴───────────────┘

Column Styling

tbl := table.NewTable()
tbl.AddColumn("Name", table.WithColumnStyle("bold"))
tbl.AddColumn("Age",  table.WithColumnStyle("yellow"), table.WithColumnJustify(console.JustifyCenter))
tbl.AddColumn("City", table.WithColumnStyle("italic"))

tbl.AddRow("[bold]Alice[/]", "30", "[blue]New York[/]")
tbl.AddRow("Bob", "25", "[blue]San Francisco[/]")

c.Render(tbl)
tbl := table.NewTableWithOptions(nil,
    table.WithShowFooter(true),
    table.WithBox(box.ROUNDED),
)
tbl.AddColumn("Task",   table.WithColumnStyle("bold"))
tbl.AddColumn("Status", table.WithColumnJustify(console.JustifyCenter))
tbl.AddRow("Setup project", "[green]Done[/]", "Alice")
tbl.AddSection()
tbl.AddRow("Code review",   "[dim]Pending[/]", "Bob")
c.Render(tbl)

Flexible Widths

tbl := table.NewTableWithOptions(nil, table.WithExpand())
tbl.AddColumn("Fixed",    table.WithColumnWidth(12))
tbl.AddColumn("Flex 2x",  table.WithColumnRatio(2))
tbl.AddColumn("Flex 1x",  table.WithColumnRatio(1))
c.Render(tbl)

For full table options see the API Reference section below.

Spinners

50+ built-in spinners from cli-spinners:

progress.NewSpinnerColumn(
    progress.WithSpinnerName("dots"),      // dots, line, star, moon, etc.
    progress.WithFinishedText("Done!"),    // Text when complete
)

Available spinners: dots, dots2, dots3, line, pipe, star, hamburger, growVertical, growHorizontal, balloon, noise, bounce, boxBounce, triangle, arc, circle, toggle, arrow, bouncingBar, bouncingBall, smiley, monkey, hearts, clock, earth, moon, runner, pong, shark, and more.

API Reference

Progress

// Create and start
p := progress.New(opts...)
p.Start(ctx)
defer p.Stop()

// Task management
taskID := p.AddTask(description, total, opts...)
p.Advance(taskID, amount)
p.Update(taskID, config)
p.Done(taskID, description...)  // Mark task as finished
p.RemoveTask(taskID)

// Sections (per-group column layouts)
section := p.AddSection(
    progress.WithSectionColumns(cols...),
    progress.WithSectionIndent(2),
)
taskID := section.AddTask(description, total, opts...)

// Task timing
p.StartTask(taskID)
p.StopTask(taskID)
p.ResetTask(taskID, start)

// State
p.Finished() bool  // All tasks complete?
p.Refresh()        // Force refresh

TaskUpdateConfig

p.Update(taskID, progress.TaskUpdateConfig{
    Description: &newDesc,
    Total:       &newTotal,
    Completed:   &newCompleted,
    Advance:     &advanceBy,
    Visible:     &isVisible,
    Fields:      map[string]any{"key": "value"},
})

Table

// Create a table
tbl := table.NewTable(headers ...string)
tbl := table.NewTableWithOptions(headers []string, opts ...TableOption)

// Add columns with options
col := tbl.AddColumn("Header",
    table.WithColumnStyle("bold cyan"),
    table.WithColumnJustify(console.JustifyRight),
    table.WithColumnWidth(20),
    table.WithColumnRatio(2),
)

// Add data rows
tbl.AddRow("cell1", "cell2")
tbl.AddStyledRow([]interface{}{"cell1", "cell2"}, style, endSection)
tbl.AddSection()

// Set footer (WithShowFooter must be true)
col.Footer = "Summary"

// Render
c.Render(tbl)

Table Options

table.NewTableWithOptions(headers,
    table.WithTitle("[bold]Title[/]"),
    table.WithCaption("Caption text"),
    table.WithWidth(80),
    table.WithBox(box.ROUNDED),      // Default: box.HEAVY_HEAD
    table.WithExpand(),              // Fill terminal width
    table.WithShowLines(),           // Dividers between all rows
    table.WithShowHeader(false),     // Hide header
    table.WithShowFooter(true),      // Show footer row
    table.WithShowEdge(false),       // Hide outer edges
    table.WithLeading(1),            // Blank lines between rows
    table.WithStyle(s),
    table.WithRowStyles(s1, s2),     // Alternating row styles
    table.WithHeaderStyle(s),
    table.WithFooterStyle(s),
    table.WithBorderStyle(s),
    table.WithPad(0, 1, 0, 1),      // top, right, bottom, left
    table.WithCollapsePadding(),
    table.WithPadEdge(false),
)

Styling

GoRich automatically detects terminal capabilities and uses the best available:

  • Truecolor (24-bit) when COLORTERM=truecolor
  • 256 colors when TERM contains 256color
  • Standard 16 colors otherwise
  • No color when NO_COLOR is set or output is not a terminal

Predefined Styles

Pre-defined style variables avoid the need to parse strings:

// Attribute styles (use with `&style.Bold`, etc.)
style.Bold      style.Dim       style.Italic
style.Underline style.Blink     style.Reverse
style.Strike    style.Conceal   style.Overline

// Color styles
style.Red     style.Green    style.Blue
style.Yellow  style.Cyan     style.Magenta
style.White   style.Black

Used with table/progress APIs that take *style.Style:

table.WithHeaderStyle(&style.Bold)
table.WithBorderStyle(&style.Cyan)

Progress Bar Colors

The progress bar automatically changes color based on state:

  • In progress: Magenta
  • Finished: Green (when task completes)
  • Pulse animation: Purple gradient (for indeterminate tasks)

Thread Safety

All Progress methods are safe to call from multiple goroutines. Updates are protected by mutexes, and the display refresh happens in a separate goroutine.

Acknowledgments

  • Rich by Will McGugan: inspired gorich's API design and rendering approach
  • cli-spinners by Sindre Sorhus: spinner definitions
  • go-runewidth: terminal cell width calculation

License

MIT License - see LICENSE file

Documentation

Overview

Package gorich provides Rich-style terminal formatting for Go.

Quick start:

gorich.Println("[bold red]Hello[/] [green]World[/]")

For more control, create a Console:

c := console.New()
c.Println("[bold]Hello[/]")

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func Console

func Console() *console.Console

Console returns the default console for advanced usage.

func Log

func Log(args ...any)

Log prints with a log prefix.

func Print

func Print(args ...any)

Print prints Rich-style markup to stdout without a trailing newline.

Example:

gorich.Print("Working... ")
gorich.Print("[green]done[/]\n")

func Printf

func Printf(format string, args ...any)

Printf prints formatted Rich-style markup to stdout.

Example:

gorich.Printf("[bold]Count:[/] %d", 42)

func Println added in v1.1.0

func Println(args ...any)

Println prints Rich-style markup to stdout with a trailing newline.

Example:

gorich.Println("[bold]Hello[/] [red]World[/]")
gorich.Println("[italic green]Success![/]")
gorich.Println("[#ff0000]Hex color[/]")
gorich.Println("[bold red on white]Styled text[/]")

func Rule

func Rule(title string, opts ...console.RuleOption)

Rule prints a horizontal rule with optional title.

func Sprint added in v1.0.10

func Sprint(args ...any) string

Sprint renders Rich-style markup args and returns the resulting ANSI string.

func Sprintf added in v1.0.10

func Sprintf(format string, args ...any) string

Sprintf renders formatted Rich-style markup and returns the resulting ANSI string.

Example:

s := gorich.Sprintf("[bold]Count:[/] %d", 42)

Types

type MarkupRenderable added in v1.0.2

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

MarkupRenderable wraps a string as a console.Renderable that parses Rich-style markup on render. It implements both console.Renderable and console.Measurable.

func NewMarkupRenderable added in v1.0.2

func NewMarkupRenderable(content string) *MarkupRenderable

NewMarkupRenderable creates a MarkupRenderable from a markup string.

func (*MarkupRenderable) Content added in v1.0.2

func (mr *MarkupRenderable) Content() string

Content returns the raw markup string.

func (*MarkupRenderable) Measure added in v1.0.2

Measure implements console.Measurable.

func (*MarkupRenderable) Render added in v1.0.2

Render implements console.Renderable.

type MarkupText added in v1.0.4

type MarkupText struct {
	Text markup.Text
}

MarkupText wraps a pre-parsed markup.Text as a console.Renderable. Use this to avoid re-parsing when you already have a parsed result. For raw markup strings, use NewMarkupRenderable instead.

func NewMarkupText added in v1.0.4

func NewMarkupText(t markup.Text) *MarkupText

NewMarkupText creates a MarkupText from a pre-parsed markup.Text.

func (*MarkupText) Measure added in v1.0.4

Measure implements console.Measurable.

func (*MarkupText) Render added in v1.0.4

func (mt *MarkupText) Render(c *console.Console, opts console.Options) []segment.Segment

Render implements console.Renderable.

Directories

Path Synopsis
Package console provides the terminal rendering engine.
Package console provides the terminal rendering engine.
example
blocks command
blocks-many command
print command
progress command
showcase command
Command showcase combines every GoRich feature in a single demo: rich print & markup, rules, tables, progress bars, spinners and live blocks.
Command showcase combines every GoRich feature in a single demo: rich print & markup, rules, tables, progress bars, spinners and live blocks.
spinners command
table command
internal
Package live provides auto-refreshing terminal displays.
Package live provides auto-refreshing terminal displays.
Package markup provides Rich-style markup parsing for styled terminal text.
Package markup provides Rich-style markup parsing for styled terminal text.
Package progress provides terminal progress bars with multiple tasks and customizable columns.
Package progress provides terminal progress bars with multiple tasks and customizable columns.
Package segment provides the atomic rendering unit for terminal output.
Package segment provides the atomic rendering unit for terminal output.
Package spinner provides animated spinner widgets.
Package spinner provides animated spinner widgets.
Package style provides terminal styling with ANSI colors and text attributes.
Package style provides terminal styling with ANSI colors and text attributes.
box

Jump to

Keyboard shortcuts

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