gitstack

grindlemire/go-tui code browser

12.8 KB markdown 456 lines 2026-03-23 · a47141f raw

Terminal Reference

Overview

The Terminal interface abstracts all communication with the user's terminal. It handles screen rendering, cursor management, raw mode, alternate screen buffers, and mouse event reporting. go-tui ships with two implementations: ANSITerminal for real terminals and MockTerminal for testing.

Most applications never interact with Terminal directly; the App type handles terminal setup and teardown. You'll use these types when you need custom terminal behavior, direct rendering, or test verification.

Terminal Interface

type Terminal interface {
    Size() (width, height int)
    Flush(changes []CellChange)
    Clear()
    ClearToEnd()
    SetCursor(x, y int)
    HideCursor()
    ShowCursor()
    EnterRawMode() error
    ExitRawMode() error
    EnterAltScreen()
    ExitAltScreen()
    EnableMouse()
    DisableMouse()
    Caps() Capabilities
    WriteDirect([]byte) (int, error)
}

Size

Size() (width, height int)

Returns the terminal dimensions in cells (columns and rows).

Flush

Flush(changes []CellChange)

Writes a batch of cell changes to the terminal. Changes should be in row-major order for best performance. The implementation optimizes cursor movement and only emits style escape codes when the style differs from the previous cell.

Clear

Clear()

Clears the entire terminal screen, including scrollback on real terminals.

ClearToEnd

ClearToEnd()

Clears from the current cursor position to the end of the screen.

SetCursor

SetCursor(x, y int)

Moves the cursor to the given position. Coordinates are 0-indexed, with (0, 0) at the top-left corner.

HideCursor / ShowCursor

HideCursor()
ShowCursor()

Controls cursor visibility. HideCursor is typically called during rendering to prevent flicker, and ShowCursor restores visibility afterward.

EnterRawMode / ExitRawMode

EnterRawMode() error
ExitRawMode() error

Raw mode disables terminal line buffering so the application can read individual keystrokes. EnterRawMode saves the current terminal state; ExitRawMode restores it. Both return an error if the terminal state cannot be changed.

EnterAltScreen / ExitAltScreen

EnterAltScreen()
ExitAltScreen()

The alternate screen buffer is a separate display area that preserves the user's original terminal content. Full-screen applications use it so that quitting restores the previous scrollback. Inline-mode applications skip it.

EnableMouse / DisableMouse

EnableMouse()
DisableMouse()

Enables or disables SGR mouse event reporting. When enabled, mouse clicks, drags, and wheel scrolls generate MouseEvent values through the EventReader. Always call DisableMouse before exiting to restore normal terminal behavior.

Caps

Caps() Capabilities

Returns the terminal's detected capabilities: color support level, Unicode, true color, and alternate screen availability. See Capabilities below.

WriteDirect

WriteDirect([]byte) (int, error)

Writes raw bytes to the terminal output, bypassing the cell-based rendering pipeline. Use this for escape sequences or raw content that the higher-level API does not cover.

ANSITerminal

ANSITerminal is the production implementation of Terminal. It communicates with real terminal emulators through ANSI escape sequences.

Creating an ANSITerminal

NewANSITerminal

func NewANSITerminal(out io.Writer, in io.Reader) (*ANSITerminal, error)

Creates a terminal with auto-detected capabilities. The out parameter is typically os.Stdout and in is os.Stdin. File descriptors are extracted from the writers if they implement *os.File, which enables raw mode and terminal size queries.

import (
    "os"
    tui "github.com/grindlemire/go-tui"
)

term, err := tui.NewANSITerminal(os.Stdout, os.Stdin)
if err != nil {
    // handle error
}

NewANSITerminalWithCaps

func NewANSITerminalWithCaps(out io.Writer, in io.Reader, caps Capabilities) *ANSITerminal

Creates a terminal with explicit capabilities instead of auto-detecting them. Use this when you know the target environment or want to force specific behavior.

caps := tui.Capabilities{
    Colors:    tui.ColorTrue,
    Unicode:   true,
    TrueColor: true,
    AltScreen: true,
}
term := tui.NewANSITerminalWithCaps(os.Stdout, os.Stdin, caps)

Additional Methods

ANSITerminal provides methods beyond the Terminal interface:

SetCaps

func (t *ANSITerminal) SetCaps(caps Capabilities)

Updates the terminal's capabilities after creation. You might call this if you detect capabilities at runtime through a terminal query/response sequence.

ResetStyle

func (t *ANSITerminal) ResetStyle()

Resets internal style tracking, forcing the next Flush call to emit style escape codes regardless of whether the style changed. Call this after writing directly to the terminal output to re-sync the style state.

Writer

func (t *ANSITerminal) Writer() io.Writer

Returns the underlying output writer. Be careful: writes through this bypass the terminal's style optimization and cursor tracking.

BeginSyncUpdate / EndSyncUpdate

func (t *ANSITerminal) BeginSyncUpdate()
func (t *ANSITerminal) EndSyncUpdate()

Brackets a synchronized update. Terminal emulators that support this protocol buffer output between Begin and End, then display everything at once. This prevents tearing during complex frame updates. Terminals that don't recognize the sequence just ignore it.

Size Defaults

ANSITerminal.Size() returns 80x24 if the terminal dimensions cannot be determined (e.g., when stdout is not a TTY).

BufferedWriter

A write buffer that batches output before sending it to an underlying writer.

type BufferedWriter struct {
    // contains unexported fields
}

NewBufferedWriter

func NewBufferedWriter(out io.Writer) *BufferedWriter

Creates a BufferedWriter wrapping the given writer.

Write

func (w *BufferedWriter) Write(p []byte) (int, error)

Appends bytes to the internal buffer. Does not write to the underlying writer.

Flush

func (w *BufferedWriter) Flush() error

Writes the buffered content to the underlying writer and clears the buffer.

bw := tui.NewBufferedWriter(os.Stdout)
bw.Write([]byte("hello "))
bw.Write([]byte("world"))
bw.Flush() // writes "hello world" to stdout in one call

EventReader

The EventReader interface reads terminal input events for the application's event loop.

type EventReader interface {
    PollEvent(timeout time.Duration) (Event, bool)
    Close() error
}

PollEvent

PollEvent(timeout time.Duration) (Event, bool)

Reads the next input event within the given timeout. Returns (event, true) when an event is available, or (nil, false) on timeout. Timeout behavior:

  • Positive duration: waits up to that long for an event
  • Zero: non-blocking check, returns immediately
  • Negative: blocks indefinitely until an event arrives

Resize events are handled at the App level, not by the reader. The App catches SIGWINCH signals and dispatches ResizeEvent through the event queue. The reader only handles keyboard and mouse input.

Close

Close() error

Releases resources held by the reader (signal handlers, file descriptors). Must be called when the reader is no longer needed.

NewEventReader

func NewEventReader(in *os.File) (EventReader, error)

Creates an EventReader for the given input file (typically os.Stdin). The terminal should already be in raw mode before creating the reader. The reader handles keyboard and mouse input only; resize events (SIGWINCH) are handled at the App level.

import (
    "os"
    tui "github.com/grindlemire/go-tui"
)

reader, err := tui.NewEventReader(os.Stdin)
if err != nil {
    // handle error
}
defer reader.Close()

event, ok := reader.PollEvent(100 * time.Millisecond)
if ok {
    // process event
}

InterruptibleReader

Extends EventReader with the ability to wake up a blocking PollEvent call from another goroutine.

type InterruptibleReader interface {
    EventReader

    EnableInterrupt() error
    Interrupt() error
}

EnableInterrupt

EnableInterrupt() error

Sets up the interrupt mechanism (a self-pipe on Unix). The framework calls this automatically during App initialization. Calling it multiple times is safe; subsequent calls are no-ops.

Interrupt

Interrupt() error

Wakes up a blocking PollEvent call. The blocked call returns (nil, false). Safe to call even when PollEvent is not currently blocking.

The App uses InterruptibleReader internally to wake the event loop when state changes are queued via QueueUpdate.

Capabilities

Capabilities describes what features the terminal supports.

type Capabilities struct {
    Colors    ColorCapability
    Unicode   bool
    TrueColor bool
    AltScreen bool
}
Field Type Description
Colors ColorCapability Level of color support (none, 16, 256, or true color)
Unicode bool Whether the terminal renders Unicode characters
TrueColor bool Whether 24-bit RGB colors are supported
AltScreen bool Whether the alternate screen buffer is available

DetectCapabilities

func DetectCapabilities() Capabilities

Determines terminal capabilities from environment variables. Detection checks, in order:

  1. COLORTERMtruecolor or 24bit indicates true color support
  2. Terminal-specific environment variables — WT_SESSION (Windows Terminal), ITERM_SESSION_ID (iTerm2), KITTY_WINDOW_ID (Kitty), KONSOLE_VERSION (Konsole), VTE_VERSION (GNOME Terminal, Tilix)
  3. TERMdumb disables all features; 256color suffix enables 256-color; truecolor enables true color

Default capabilities when detection finds nothing specific: 16-color, Unicode enabled, no true color, alternate screen available.

caps := tui.DetectCapabilities()
fmt.Println(caps) // e.g., "true-color, unicode, altscreen"

ColorCapability Constants

const (
    ColorNone ColorCapability = iota  // Monochrome, no color
    Color16                           // Standard 16 ANSI colors
    Color256                          // 256-color palette
    ColorTrue                         // 24-bit RGB true color
)

SupportsColor

func (c Capabilities) SupportsColor(color Color) bool

Reports whether the terminal can display the given color. ANSI colors require at least Color16 support; RGB colors require TrueColor.

caps := tui.DetectCapabilities()
color := tui.RGBColor(255, 128, 0) // orange

if caps.SupportsColor(color) {
    // use the color directly
} else {
    // fall back to an ANSI approximation
}

EffectiveColor

func (c Capabilities) EffectiveColor(color Color) Color

Returns the best color the terminal can actually display. If the terminal supports the color type, returns the original. Otherwise, RGB colors are approximated to the nearest ANSI color, and ANSI colors on colorless terminals fall back to DefaultColor().

caps := tui.DetectCapabilities()
requested := tui.RGBColor(0, 200, 100)
actual := caps.EffectiveColor(requested) // RGB on true-color, ANSI approximation otherwise

String

func (c Capabilities) String() string

Returns a human-readable summary like "true-color, unicode, altscreen" or "16-color, ascii".

Render Functions

Package-level functions that App uses internally for rendering. They're also available if you're building a custom render loop.

Render

func Render(term Terminal, buf *Buffer)

Diff-based render. Computes the cells that changed since the last Swap() and calls term.Flush() with only those changes. This is the standard render path and minimizes bytes sent to the terminal.

RenderFull

func RenderFull(term Terminal, buf *Buffer)

Full redraw. Sends every non-empty cell to the terminal regardless of whether it changed. Use this after operations that may have corrupted the terminal state (e.g., a child process writing to stdout).

Cross-References