render

package module
v0.0.3 Latest Latest
Warning

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

Go to latest
Published: Jan 17, 2026 License: MIT Imports: 4 Imported by: 3

README

./render

Efficient terminal rendering with double-buffering and diff optimization.

Install

go get github.com/dottermi/x/render

Usage

package main

import (
    "fmt"
    "github.com/dottermi/x/render"
)

func main() {
    // Create terminal renderer
    term := render.NewTerminal(80, 24)

    // Create buffer and draw
    buf := render.NewBuffer(80, 24)
    buf.Set(0, 0, render.Cell{
        Char: 'H',
        FG:   render.RGB(255, 0, 0),
        Bold: true,
    })
    buf.Set(1, 0, render.Cell{Char: 'i', FG: render.RGB(255, 0, 0)})

    // Render (only changed cells)
    fmt.Print(term.Render(buf))
}

How it works

Frame 1:          Frame 2:
┌──────────┐      ┌──────────┐
│ Hello    │      │ Hello    │
│ World    │  →   │ Gopher!  │  → Only "Gopher!" is sent
└──────────┘      └──────────┘

Instead of redrawing everything, Terminal.Render() compares with the previous frame and outputs only the ANSI codes needed to update changed cells.

API

Color
render.RGB(r, g, b uint8) Color  // Create RGB color
render.Default() Color           // Terminal default color
color.IsSet() bool               // Check if explicitly set
color.FGCode() string            // ANSI foreground code
color.BGCode() string            // ANSI background code
Cell
render.EmptyCell() Cell          // Space with default colors
cell.Equal(other Cell) bool      // Compare cells
Buffer
render.NewBuffer(w, h int) *Buffer
buf.Set(x, y int, c Cell)        // Write cell (clips out-of-bounds)
buf.Get(x, y int) Cell           // Read cell
buf.Fill(c Cell)                 // Fill entire buffer
buf.Clone() *Buffer              // Deep copy
buf.Diff(new *Buffer) []CellChange  // Get changed cells
Terminal
render.NewTerminal(w, h int) *Terminal
term.Render(buf *Buffer) string      // Diff-optimized output
term.RenderTo(buf *Buffer, w io.Writer)
term.RenderFull(buf *Buffer) string  // Full render (no diff)
term.Clear() string                  // Clear screen
term.Resize(w, h int)                // Update dimensions
ANSI Helpers
render.MoveCursor(x, y int) string
render.HideCursor() string
render.ShowCursor() string

Benchmark

Environment
Apple M4 Pro (14 cores: 10P + 4E)
48 GB RAM
macOS 26.0.1
Go 1.25.5 darwin/arm64
| Operation                        | Time    | Allocs |
| -------------------------------- | ------- | ------ |
| `Buffer.Set`                     | 1.9 ns  | 0      |
| `Buffer.Get`                     | 2.3 ns  | 0      |
| `NewBuffer(80x24)`               | 3.2 µs  | 2      |
| `Buffer.Fill(80x24)`             | 747 ns  | 0      |
| `Buffer.Clone(80x24)`            | 2.9 µs  | 2      |
| `Buffer.Diff (no changes)`       | 9.9 µs  | 0      |
| `Buffer.Diff (1% sparse)`        | 9.9 µs  | 6      |
| `Buffer.Diff (50%)`              | 14.7 µs | 11     |
| `Terminal.RenderTo (no changes)` | 12.4 µs | 2      |
| `Terminal.RenderTo (1% sparse)`  | 17.1 µs | 31     |
| `Terminal.RenderTo (50%)`        | 41.2 µs | 977    |
| `Terminal.RenderFullTo(80x24)`   | 40.0 µs | 1924   |
| `Color.RGB`                      | 1.6 ns  | 0      |
| `Color.FGCode (default)`         | 1.7 ns  | 0      |
| `Color.FGCode (set)`             | 22.9 ns | 1      |
| `EmptyCell`                      | 1.7 ns  | 0      |
| `Cell.Equal`                     | 4.2 ns  | 0      |

go test -bench=. -benchmem ./...

Documentation

Overview

Package render provides efficient terminal rendering with double-buffering and diff optimization. Inspired by the Ratatui immediate-mode rendering pattern, it minimizes ANSI escape code output by only updating cells that have changed between frames.

The package centers around four types:

  • Color: RGB colors with optional terminal default fallback
  • Cell: A single terminal position with character and styling
  • Buffer: A 2D grid of cells representing the screen
  • Terminal: Double-buffered renderer that computes minimal diffs

Basic usage:

term := render.NewTerminal(80, 24)
buf := render.NewBuffer(80, 24)

// Draw to buffer
cell := render.Cell{Char: 'X', FG: render.RGB(255, 0, 0)}
buf.Set(10, 5, cell)

// Render only changed cells
output := term.Render(buf)
fmt.Print(output)

Index

Constants

View Source
const (
	// Reset clears all text attributes and colors.
	Reset = "\x1b[0m"

	// ClearScreen clears the entire screen.
	ClearScreen = "\x1b[2J"
	ClearLine   = "\x1b[2K" // Clear current line
	CursorHome  = "\x1b[H"  // Move cursor to top-left (0, 0)
	CursorHide  = "\x1b[?25l"
	CursorShow  = "\x1b[?25h"

	// AltScreenEnter switches to alternate screen buffer for fullscreen apps (like vim, htop).
	AltScreenEnter = "\x1b[?1049h"
	AltScreenLeave = "\x1b[?1049l"

	// BoldOn enables bold text attribute.
	BoldOn      = "\x1b[1m"
	DimOn       = "\x1b[2m"
	ItalicOn    = "\x1b[3m"
	UnderlineOn = "\x1b[4m"
	ReverseOn   = "\x1b[7m" // Swap foreground and background
	StrikeOn    = "\x1b[9m"

	// BoldOff disables bold text attribute (also disables Dim).
	BoldOff      = "\x1b[22m"
	DimOff       = "\x1b[22m" // Also disables Bold
	ItalicOff    = "\x1b[23m"
	UnderlineOff = "\x1b[24m"
	ReverseOff   = "\x1b[27m"
	StrikeOff    = "\x1b[29m"
)

ANSI escape sequences for terminal control and text styling. These constants can be written directly to the terminal output.

Example:

fmt.Print(render.ClearScreen + render.CursorHome)
fmt.Print(render.BoldOn + "Bold text" + render.Reset)

Variables

This section is empty.

Functions

func HideCursor

func HideCursor() string

HideCursor returns the ANSI escape sequence to hide the cursor. Pair with ShowCursor when rendering is complete.

func MoveCursor

func MoveCursor(x, y int) string

MoveCursor returns the ANSI escape sequence to position the cursor. Coordinates are 0-indexed; the function handles conversion to 1-indexed ANSI positions.

Parameters:

  • x: column (0-indexed)
  • y: row (0-indexed)

Example:

fmt.Print(render.MoveCursor(10, 5)) // Move to column 10, row 5

func ShowCursor

func ShowCursor() string

ShowCursor returns the ANSI escape sequence to show the cursor. Call this to restore cursor visibility after HideCursor.

Types

type Buffer

type Buffer struct {
	Width  int
	Height int
	Cells  []Cell // row-major: index = y*Width + x
}

Buffer represents a 2D grid of Cell values for terminal rendering. Cells are stored in row-major order and accessed via (x, y) coordinates. Use NewBuffer to create a buffer pre-filled with empty cells.

Example:

buf := render.NewBuffer(80, 24)
buf.Set(0, 0, render.Cell{Char: 'H', FG: render.RGB(0, 255, 0)})
buf.Set(1, 0, render.Cell{Char: 'i', FG: render.RGB(0, 255, 0)})

func NewBuffer

func NewBuffer(width, height int) *Buffer

NewBuffer creates a Buffer of the specified dimensions filled with EmptyCell values.

Parameters:

  • width: number of columns
  • height: number of rows

Example:

buf := render.NewBuffer(80, 24)

func (*Buffer) Clone

func (b *Buffer) Clone() *Buffer

Clone creates a deep copy of the buffer. The returned buffer shares no memory with the original.

func (*Buffer) Diff

func (b *Buffer) Diff(next *Buffer) []CellChange

Diff compares this buffer with new and returns cells that differ. Only the intersection of both buffer dimensions is compared. Returns nil if buffers are identical.

This is the core optimization: instead of redrawing the entire screen, only changed cells need ANSI output.

Example:

changes := oldBuf.Diff(newBuf)
for _, c := range changes {
	fmt.Printf("Cell at (%d,%d) changed to %q\n", c.X, c.Y, c.Cell.Char)
}

func (*Buffer) Fill

func (b *Buffer) Fill(c Cell)

Fill sets every cell in the buffer to the specified value. Useful for clearing the buffer or setting a uniform background.

Example:

bg := render.Cell{Char: ' ', BG: render.RGB(0, 0, 64)}
buf.Fill(bg)

func (*Buffer) FillHorizontal added in v0.0.2

func (b *Buffer) FillHorizontal(x, y, length int, c Cell)

FillHorizontal draws a horizontal line of cells. Starts at (x, y) and extends rightward for length cells.

func (*Buffer) FillRect added in v0.0.2

func (b *Buffer) FillRect(x, y, width, height int, c Cell)

FillRect fills a rectangular region with the given cell. Cells outside buffer bounds are silently skipped.

func (*Buffer) FillVertical added in v0.0.2

func (b *Buffer) FillVertical(x, y, length int, c Cell)

FillVertical draws a vertical line of cells. Starts at (x, y) and extends downward for length cells.

func (*Buffer) Get

func (b *Buffer) Get(x, y int) Cell

Get returns the cell at the specified position. Returns EmptyCell for out-of-bounds coordinates.

Parameters:

  • x: column index (0-based)
  • y: row index (0-based)

func (*Buffer) Set

func (b *Buffer) Set(x, y int, c Cell)

Set writes a cell at the specified position. Out-of-bounds coordinates are silently ignored, enabling safe clipping.

Parameters:

  • x: column index (0-based)
  • y: row index (0-based)
  • c: cell to write

func (*Buffer) SetClipped added in v0.0.2

func (b *Buffer) SetClipped(x, y int, c Cell, clip ClipRect)

SetClipped writes a cell at the given position only if within clip bounds. Both buffer bounds and clip bounds are checked.

type Cell

type Cell struct {
	// Char is the Unicode character to display. Zero value renders as space.
	Char rune

	// FG is the foreground (text) color.
	FG Color

	// BG is the background color.
	BG Color

	// Text decoration attributes
	Bold      bool
	Dim       bool
	Italic    bool
	Underline bool
	Strike    bool
	Reverse   bool
}

Cell represents a single character position in the terminal with styling attributes. Each cell contains a character, foreground and background colors, and text decorations. The zero value displays as a space with terminal default colors.

Example:

cell := render.Cell{
	Char:   'A',
	FG:     render.RGB(255, 255, 255),
	BG:     render.RGB(0, 0, 128),
	Bold:   true,
}

func EmptyCell

func EmptyCell() Cell

EmptyCell returns a Cell containing a space character with default colors. Use this as the baseline for cleared or empty screen regions.

func (Cell) Equal

func (c Cell) Equal(other Cell) bool

Equal reports whether c and other have identical character, colors, and attributes. Used internally by Buffer.Diff to detect changes between frames.

type CellChange

type CellChange struct {
	X, Y int
	Cell Cell
}

CellChange represents a modified cell at a specific screen position. Used by Buffer.Diff to report which cells need updating.

type ClipRect added in v0.0.2

type ClipRect struct {
	X, Y, W, H int
}

ClipRect defines a rectangular clipping region. Coordinates use (0,0) as top-left corner.

func (ClipRect) Contains added in v0.0.2

func (c ClipRect) Contains(x, y int) bool

Contains returns true if the point (x, y) is within the clip rect bounds.

type Color

type Color struct {
	R, G, B uint8
	Set     bool
}

Color represents an RGB color for terminal rendering with optional default fallback. Use RGB to create explicit colors or Default for terminal defaults. The zero value is equivalent to Default().

Example:

red := render.RGB(255, 0, 0)
transparent := render.Default()

func Default

func Default() Color

Default returns a Color that uses the terminal's default foreground or background. Equivalent to the zero value.

func RGB

func RGB(r, g, b uint8) Color

RGB creates a Color with the specified red, green, and blue components. Values range from 0 to 255.

Example:

white := render.RGB(255, 255, 255)
coral := render.RGB(255, 127, 80)

func (Color) BGCode

func (c Color) BGCode() string

BGCode returns the ANSI escape sequence for setting this color as background. Returns the default background reset code if the color is not set.

func (Color) FGCode

func (c Color) FGCode() string

FGCode returns the ANSI escape sequence for setting this color as foreground. Returns the default foreground reset code if the color is not set.

func (Color) IsSet

func (c Color) IsSet() bool

IsSet reports whether the color was explicitly set via RGB. Returns false for colors created with Default or zero values.

func (Color) WriteBGTo

func (c Color) WriteBGTo(w io.Writer)

WriteBGTo writes the background ANSI escape sequence directly to w. This avoids string allocation compared to BGCode().

func (Color) WriteFGTo

func (c Color) WriteFGTo(w io.Writer)

WriteFGTo writes the foreground ANSI escape sequence directly to w. This avoids string allocation compared to FGCode().

type Terminal

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

Terminal manages double-buffered rendering with diff-based optimization. It tracks the previously rendered state and outputs only the ANSI sequences needed to update changed cells, minimizing terminal I/O.

Typical usage follows an immediate-mode pattern:

term := render.NewTerminal(80, 24)
for {
	buf := render.NewBuffer(80, 24)
	// Draw your UI into buf...
	output := term.Render(buf)
	os.Stdout.WriteString(output)
}

func NewTerminal

func NewTerminal(width, height int) *Terminal

NewTerminal creates a Terminal renderer for the specified dimensions. The internal buffer is initialized to empty cells.

Parameters:

  • width: terminal width in columns
  • height: terminal height in rows

Example:

term := render.NewTerminal(80, 24)

func (*Terminal) Clear

func (t *Terminal) Clear() string

Clear resets the internal buffer and returns ANSI codes to clear the screen. The cursor is moved to the home position (0, 0).

Example:

os.Stdout.WriteString(term.Clear())

func (*Terminal) Render

func (t *Terminal) Render(next *Buffer) string

Render computes the diff between the current state and new buffer, returning the minimal ANSI escape sequences needed to update the terminal. Updates the internal state to match new after rendering.

Returns an empty string if no cells have changed.

Example:

buf := render.NewBuffer(80, 24)
buf.Set(0, 0, render.Cell{Char: 'X'})
output := term.Render(buf)
os.Stdout.WriteString(output)

func (*Terminal) RenderFull

func (t *Terminal) RenderFull(buf *Buffer) string

RenderFull renders the entire buffer without diff computation. Use this for the initial frame or after clearing the screen. Subsequent frames should use Terminal.Render for efficiency.

Example:

fmt.Print(render.ClearScreen)
fmt.Print(term.RenderFull(buf))

func (*Terminal) RenderFullTo

func (t *Terminal) RenderFullTo(buf *Buffer, w io.Writer)

RenderFullTo writes the entire buffer to w without diff computation. Prefer this over Terminal.RenderFull to avoid intermediate string allocation.

Parameters:

  • buf: the buffer to render
  • w: destination for ANSI output

func (*Terminal) RenderInline added in v0.0.3

func (t *Terminal) RenderInline(buf *Buffer) string

RenderInline renders the entire buffer without cursor positioning. Use this for inline output (e.g., printing styled text to stdout) where cursor positioning is not desired.

Example:

buf := render.NewBuffer(20, 1)
buf.Set(0, 0, render.Cell{Char: 'H', Bold: true})
fmt.Print(term.RenderInline(buf))

func (*Terminal) RenderInlineTo added in v0.0.3

func (t *Terminal) RenderInlineTo(buf *Buffer, w io.Writer)

RenderInlineTo writes the buffer to w without cursor positioning. Prefer this over Terminal.RenderInline to avoid intermediate string allocation.

Parameters:

  • buf: the buffer to render
  • w: destination for ANSI output

func (*Terminal) RenderTo

func (t *Terminal) RenderTo(next *Buffer, w io.Writer)

RenderTo writes the diff-optimized ANSI output directly to w. Prefer this over Terminal.Render when writing to files or network connections to avoid intermediate string allocation.

Parameters:

  • next: the buffer representing the desired screen state
  • w: destination for ANSI output

func (*Terminal) Resize

func (t *Terminal) Resize(width, height int)

Resize updates the terminal dimensions and clears the internal buffer. Call this when the terminal window size changes.

Parameters:

  • width: new terminal width in columns
  • height: new terminal height in rows

Directories

Path Synopsis
examples
doom-fire command

Jump to

Keyboard shortcuts

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