gopher

Progress System

The progress system provides user feedback for long-running operations through progress bars and spinners.

Overview

The internal/progress package provides:

Architecture

Modular Design

The progress system uses a modular architecture with clear separation:

ProgressBar / Spinner
  ├── Configuration (ProgressConfig with functional options)
  ├── Calculation (percentage, speed, etc.)
  ├── Rendering (buildLine - format to string)
  └── Output (terminalWriter - cross-platform I/O)

Benefits:

Components

1. ProgressBar

Displays a visual progress bar with percentage, bytes, and speed information.

Basic Usage:

// Create a progress bar
pb := progress.NewProgressBar(totalBytes, "Downloading file")

// Update progress
pb.Update(currentBytes)

// Finish (shows 100% and adds newline)
pb.Finish()

With Options:

// Minimal mode (percentage only)
pb := progress.NewProgressBar(total, "Downloading", progress.WithMinimal())

// Custom width
pb := progress.NewProgressBar(total, "Downloading", progress.WithWidth(80))

// Silent mode (no output)
pb := progress.NewProgressBar(total, "Downloading", progress.WithSilent())

// Multiple options
pb := progress.NewProgressBar(total, "Downloading",
    progress.WithWidth(60),
    progress.WithChars("=", "-"),
    progress.WithSpeed(false),
)

Features:

2. Spinner

Animated spinner for operations without known progress.

Usage:

// Create and start spinner
spinner := progress.NewSpinner("Loading data")
spinner.Start()

// Do work...
time.Sleep(5 * time.Second)

// Stop (shows checkmark)
spinner.Stop()

With defer pattern:

spinner := progress.NewSpinner("Processing")
spinner.Start()
defer spinner.Stop()

// Do work... spinner automatically stops at end

Features:

3. ProgressWriter

Wraps an io.Writer to automatically update progress during write operations.

Usage:

file, _ := os.Create("output.dat")
defer file.Close()

pb := progress.NewProgressBar(totalBytes, "Writing")
pw := progress.NewProgressWriter(file, pb)

// Writes automatically update progress
io.Copy(pw, source)
pb.Finish()

Perfect for:

4. Formatters Package

Reusable formatting utilities in internal/formatters:

// Format bytes
formatters.FormatBytes(1048576)  // "1.0 MB"

// Format speed
formatters.FormatSpeed(10485760)  // "10.0 MB/s"

// Format percentage
formatters.FormatPercentage(0.753)  // "75.3%"

Configuration Options

All options use the functional options pattern for flexibility:

Option Description Example
WithWidth(int) Set bar width in characters WithWidth(80)
WithUpdateThrottle(duration) Minimum time between updates WithUpdateThrottle(50*time.Millisecond)
WithSpeed(bool) Show/hide speed display WithSpeed(false)
WithBytes(bool) Show/hide byte counts WithBytes(false)
WithChars(filled, empty) Custom bar characters WithChars("=", "-")
WithSilent() Disable all output WithSilent()
WithMinimal() Show only percentage WithMinimal()
WithCustom(func) Custom configuration WithCustom(func(c *ProgressConfig) {...})

Cross-Platform Behavior

Terminal Width Detection

Platform-Specific Handling

Windows:

Unix/Linux/macOS:

TTY Detection

Implementation Details

Terminal Writer

The terminalWriter component handles all cross-platform terminal output:

type terminalWriter struct {
    output      io.Writer
    isStdout    bool
    lastLineLen int
}

Key features:

Throttling

Progress updates are throttled to prevent performance issues:

Usage in Gopher

Current usage:

  1. Download progress (internal/downloader)
    progressBar := progress.NewProgressBar(fileSize, "Downloading "+filename)
    progressWriter := progress.NewProgressWriter(file, progressBar)
    io.Copy(progressWriter, resp.Body)
    progressBar.Finish()
    
  2. Uninstall spinner (cmd/gopher)
    spinner := progress.NewSpinner("Uninstalling Go "+version)
    spinner.Start()
    manager.Uninstall(version)
    spinner.Stop()
    
  3. Extraction spinner (internal/installer)
    spinner := progress.NewSpinner("Extracting archive")
    spinner.Start()
    defer spinner.Stop()
    // extraction logic...
    
  4. Metadata spinner (internal/installer)
    spinner := progress.NewSpinner("Creating version metadata")
    spinner.Start()
    createMetadata(...)
    spinner.Stop()
    

Testing

Unit Tests

# Test all progress components
go test ./internal/progress/...

# Test formatters
go test ./internal/formatters/...

Manual Testing

# Test progress bar
./build/gopher install 1.23.0

# Test spinners
./build/gopher uninstall 1.23.0

Future Enhancements

See docs/ROADMAP.md for planned features:

Troubleshooting

Progress Bar Not Updating

Line Wrapping Issues

Windows-Specific Issues


Last Updated: October 15, 2025
Version: 1.0.0
Status: Production Ready