fasttui

package module
v0.15.0 Latest Latest
Warning

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

Go to latest
Published: Jul 27, 2026 License: Apache-2.0 Imports: 17 Imported by: 0

README

fasttui

fasttui is a Go port of @mariozechner/pi-tui: the same differential renderer, bracketed paste handling, autocomplete, and component value semantics

Features

  • Differential Rendering: Three-strategy rendering system that only updates what changed
  • Synchronized Output: Uses CSI 2026 for atomic screen updates (no flicker)
  • Bracketed Paste Mode: Handles large pastes correctly with markers for >10 line pastes
  • Component-based: Simple Component interface with render() method
  • Theme Support: Components accept theme interfaces for customizable styling
  • Built-in Components: Text, TruncatedText, Input, Editor, Markdown, Loader, SelectList, SettingsList, Spacer, Image, Box, Container
  • Inline Images: Renders images in terminals that support Kitty or iTerm2 graphics protocols
  • Autocomplete Support: File paths and slash commands

quickstart

Installation
go get github.com/yourusername/fasttui
components

all components must implement this interface.

type Component interface {
    // render the compoent to lines for the given viewport width
    Render(width int) []string
	// handler for keyboard input when component has focus
    HandleInput(data string)
    // receives key release events
	WantsKeyRelease() bool
    // Invalidate any cached rendering state.
	// Called when theme changes or when component needs to re-render from scratch.
	Invalidate()
}

TUI

Create a terminal implementation (stdin/stdout TTY), then a TUI that owns rendering and input. Call Start() after adding children and optional SetFocus.

import (
	"github.com/yeeaiclub/fasttui"
	"github.com/yeeaiclub/fasttui/terminal"
)

term := terminal.NewProcessTerminal()

// Second arg: show hardware cursor (true) vs hide it and rely on drawn UI (false).
tui := fasttui.NewTUI(term, false)

Add Child

TUI embeds Container, so you use AddChild to stack components; the TUI owns the render loop and paints the screen.

tui.AddChild(component) // Append to the vertical layout (order = top to bottom).

// Optional: give keyboard focus to a Focusable component (Input, Editor, lists, …).
tui.SetFocus(component)

tui.Start() // Enter raw mode, start input + differential rendering.

defer tui.Stop() // Restore the terminal when the program exits.

render

Manually trigger a render using tui.TriggerRender(). This is useful when you update component state outside the normal input flow.

tui.TriggerRender()

Theme

Colors and terminal glyphs are provided by the style subpackage (github.com/yeeaiclub/fasttui/style). A theme is a JSON file that lists named color tokens, optional vars for indirection, and optional symbol / export settings.

Loading a theme
import "github.com/yeeaiclub/fasttui/style"

// Built-in names are embedded in the library (e.g. "dark", "light", and many *defaults*).
th, err := style.LoadTheme("dark")
if err != nil {
	// handle error
}
// Optional configuration uses the options pattern, e.g.:
//   style.LoadTheme("dark", style.WithColorMode(style.ColorModeTruecolor))
//   style.LoadTheme("dark", style.WithSymbolPreset(style.SymbolPresetNerd), style.WithColorBlindMode(true))
// Available: [style.WithColorMode], [style.WithSymbolPreset], [style.WithColorBlindMode]
  • LoadThemeFile(name) – returns *ThemeFile (parsed JSON) without ANSI resolution.
  • NewTheme(tf, opts ...ThemeOption) – builds a *Theme from *ThemeFile (same options as LoadTheme).
  • ParseThemeJSON(data) – parse raw JSON (same validation as file load).
Where themes are loaded from
  1. Built-in – themes shipped under style/theme (e.g. dark.json, light.json, theme/defaults/*.json) are embedded and resolved first by name (filename without .json).
  2. User themes~/.config/fasttui/themes/<name>.json (or $XDG_CONFIG_HOME/fasttui/themes/ when set). Override the directory with FASTTUI_THEMES_DIR.

ListThemeNames() returns all built-in names plus any *.json in the user themes directory (sorted, de-duplicated).

Theme JSON
  • name (string) and colors (object) are required. Every key listed in the schema must be present in colors (e.g. accent, userMessageBg, syntaxComment, statusLineBg, …).
  • vars (optional) – map of name → hex string, "", or 0–255 index. Other fields can reference a var by the same string as the key (e.g. "accent": "teal" with "vars": { "teal": "#5a8080" }).
  • export (optional) – pageBg / cardBg / infoBg for HTML/CSS export helpers; var refs may use a $ prefix (e.g. "$teal") when matching vars.
  • symbols (optional) – preset (unicode | nerd | ascii) and overrides for individual logical keys (e.g. status.success).

The JSON schema is embedded as style/theme/theme-schema.json in the module.

Using *style.Theme

Theme exposes foreground and background tokens as ANSI, plus symbols:

  • Fg(color ThemeColor, text string), Bg(bg ThemeBg, text string)
  • FgANSI / BgANSI for raw sequences; Bold, Italic, Underline, etc.
  • Symbol(key string), LangIcon(lang string), SpinnerFrames(kind), InputCursor(), …

For non-TUI use (e.g. HTML), ResolvedThemeColors, ExportColors, IsLightTheme, and DefaultThemeName (uses COLORFGBG when set) are available in the same package.

License

Apache License 2.0

Documentation

Index

Constants

View Source
const (
	CursorMarker = "\x1b_pi:c\x07"
	SegmentReset = "\x1b[0m\x1b]8;;\x07"
)

Variables

View Source
var (
	SyncOutputBegin = "\x1b[?2026h"
	SyncOutputEnd   = "\x1b[?2026l"
)
View Source
var SEGMENT_RESET = "\x1b[0m\x1b]8;;\x07"

Functions

func AcquireBuilder added in v0.10.0

func AcquireBuilder() *strings.Builder

AcquireBuilder returns a pooled strings.Builder. Call ReleaseBuilder when done.

func ApplyBackgroundToLine

func ApplyBackgroundToLine(line string, width int, bgFn func(string) string) string

func BuildWidthExceedErrorMsg

func BuildWidthExceedErrorMsg(lineIndex int, lineWidth int, termWidth int, crashLogPath string) string

BuildWidthExceedErrorMsg builds an error message for when a rendered line exceeds the terminal width.

func ExtractAnsiCode

func ExtractAnsiCode(s string, pos int) (code string, length int, ok bool)

ExtractAnsiCode extracts an ANSI escape sequence starting at the given position. It supports three types of sequences:

  • CSI (Control Sequence Introducer): ESC [ ... terminator (e.g., ESC[31m for red text)
  • OSC (Operating System Command): ESC ] ... BEL or ESC \ (e.g., ESC]8;;url for hyperlinks)
  • APC (Application Program Command): ESC _ ... BEL or ESC \ (e.g., ESC_pi:c for cursor marker)

Returns the complete escape sequence, its length, and whether extraction succeeded.

func ExtractSegments

func ExtractSegments(line string, beforeEnd int, afterStart int, afterLen int, strictAfter bool) (string, int, string, int)

func GetCrashLogPath

func GetCrashLogPath() string

GetCrashLogPath returns the path to the crash log file.

func GetCursorMarker

func GetCursorMarker() string

func GetSegmentReset

func GetSegmentReset() string

func GetSegmenter

func GetSegmenter() any

func GraphemeWidth

func GraphemeWidth(s string) int

GraphemeWidth calculates the display width of a grapheme cluster It handles: - Zero-width characters (zero-width joiners, zero-width spaces) - Combining marks (counted with their base character) - Emoji (typically width 2) - East Asian characters (width 2 for fullwidth, 1 for halfwidth) - Regular ASCII (width 1)

func IsASCII added in v0.9.0

func IsASCII(s string) bool

func IsPrintableASCII added in v0.10.0

func IsPrintableASCII(s string) bool

IsPrintableASCII reports whether s contains only printable ASCII (0x20-0x7e).

func IsPunctuationChar

func IsPunctuationChar(char string) bool

func IsWhitespaceChar

func IsWhitespaceChar(char string) bool

func LogCrashInfo

func LogCrashInfo(width int, lineIndex int, line string, newLines []string)

LogCrashInfo logs detailed crash information including terminal width, line index, and all rendered lines.

func ReleaseBuilder added in v0.10.0

func ReleaseBuilder(b *strings.Builder)

ReleaseBuilder returns a builder from AcquireBuilder to the pool.

func SliceByColumn

func SliceByColumn(line string, startCol int, length int, strict bool) string

func StripAnsi added in v0.3.2

func StripAnsi(s string) string

StripAnsi removes every escape sequence recognized by ExtractAnsiCode from s. Lone ESC bytes and incomplete or unsupported sequences are left unchanged.

func TruncateToWidth

func TruncateToWidth(text string, maxWidth int, ellipsis string, pad bool) string

TruncateToWidth truncates text to fit within a maximum visible width, adding ellipsis if needed. Optionally pad with spaces to reach exactly maxWidth. Properly handles ANSI escape codes (they don't count toward width).

Parameters:

  • text: Text to truncate (may contain ANSI codes)
  • maxWidth: Maximum visible width
  • ellipsis: Ellipsis string to append when truncating (default: "...")
  • pad: If true, pad result with spaces to exactly maxWidth

Returns: Truncated text, optionally padded to exactly maxWidth

func VisibleWidth

func VisibleWidth(s string) int

func WrapAnsiText added in v0.2.1

func WrapAnsiText(text string, width int) []string

WrapAnsiText wraps text to the given width,

preserving ANSI escape codes (colors, bold, etc.).

func WriteCrashLog

func WriteCrashLog(path string, data string)

WriteCrashLog writes crash data to the specified path.

func WriteDebugLog

func WriteDebugLog(firstChanged, viewportTop, finalCursorRow, hardwareCursorRow,
	renderEnd, cursorRow, cursorCol, height int, tCursorRow int, newLines, previousLines []string)

WriteDebugLog writes detailed debug information about the rendering process. With FASTTUI_DEBUG=1 the render loop dumps automatically; this helper is for explicit dumps.

Types

type AnsiCodeTracker

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

AnsiCodeTracker tracks the current state of ANSI escape codes in a text stream. It maintains the active text formatting attributes (bold, italic, colors, etc.) and can reconstruct the ANSI codes needed to continue formatting on a new line.

Example usage:

tracker := NewAnsiCodeTracker()
tracker.Process("\x1b[1;31m")  // bold + red foreground
codes := tracker.GetActiveCodes()  // returns "\x1b[1;31m"

func NewAnsiCodeTracker

func NewAnsiCodeTracker() *AnsiCodeTracker

NewAnsiCodeTracker creates a new ANSI code tracker with all formatting disabled.

Example:

tracker := NewAnsiCodeTracker()

func (*AnsiCodeTracker) Clear

func (t *AnsiCodeTracker) Clear()

Clear is an alias for Reset. It clears all active formatting attributes.

Example:

tracker.Clear()  // same as tracker.Reset()

func (*AnsiCodeTracker) GetActiveCodes

func (t *AnsiCodeTracker) GetActiveCodes() string

GetActiveCodes returns an ANSI escape sequence that represents all currently active formatting attributes. This is useful for continuing formatting on a new line. Returns an empty string if no formatting is active.

Example:

tracker.Process("\x1b[1;31m")     // bold + red
codes := tracker.GetActiveCodes()  // returns "\x1b[1;31m"

// Use case: wrapping text while preserving formatting
line1 := "\x1b[1;31mHello"
tracker.Process("\x1b[1;31m")
line2 := tracker.GetActiveCodes() + "World\x1b[0m"

func (*AnsiCodeTracker) GetLineEndReset

func (t *AnsiCodeTracker) GetLineEndReset() string

GetLineEndReset returns an ANSI code to reset underline formatting at the end of a line. This is useful because underline formatting can extend beyond the text content. Returns "\x1b[24m" (turn off underline) if underline is active, empty string otherwise.

Example:

tracker.Process("\x1b[4m")         // underline
reset := tracker.GetLineEndReset()  // returns "\x1b[24m"
line := "text" + reset              // prevents underline from extending

func (*AnsiCodeTracker) HasActiveCodes

func (t *AnsiCodeTracker) HasActiveCodes() bool

HasActiveCodes returns true if any formatting attributes are currently active.

Example:

tracker := NewAnsiCodeTracker()
tracker.HasActiveCodes()        // returns false
tracker.Process("\x1b[1m")      // bold
tracker.HasActiveCodes()        // returns true

func (*AnsiCodeTracker) Process

func (t *AnsiCodeTracker) Process(ansiCode string)

Process parses an ANSI escape code and updates the tracker's internal state. It handles SGR (Select Graphic Rendition) codes for text formatting and colors.

Supported codes:

  • 0: Reset all attributes
  • 1: Bold, 2: Dim, 3: Italic, 4: Underline, 5: Blink
  • 7: Inverse, 8: Hidden, 9: Strikethrough
  • 21-29: Turn off corresponding attributes
  • 30-37, 90-97: Foreground colors
  • 40-47, 100-107: Background colors
  • 38;5;n: 256-color foreground
  • 48;5;n: 256-color background
  • 38;2;r;g;b: RGB foreground
  • 48;2;r;g;b: RGB background

Example:

tracker.Process("\x1b[1;31m")     // bold + red
tracker.Process("\x1b[38;5;208m") // 256-color orange
tracker.Process("\x1b[0m")        // reset all

func (*AnsiCodeTracker) Reset

func (t *AnsiCodeTracker) Reset()

Reset clears all active formatting attributes, returning the tracker to its initial state.

Example:

tracker.Process("\x1b[1;31m")  // bold + red
tracker.Reset()                 // clear all formatting
tracker.HasActiveCodes()        // returns false

type Component

type Component interface {
	// Render returns terminal lines for the given width.
	Render(width int) []string
	// HandleInput handles raw input when focused.
	HandleInput(data string)
	// WantsKeyRelease: receive key-up events.
	WantsKeyRelease() bool
	// Invalidate clears cached render state.
	Invalidate()
}

Component: render + keyboard input.

type Container

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

func NewContainer

func NewContainer() *Container

func (*Container) AddChild

func (c *Container) AddChild(component Component)

func (*Container) Clear

func (c *Container) Clear()

func (*Container) GetChildren

func (c *Container) GetChildren() []Component

func (*Container) HandleInput

func (c *Container) HandleInput(data string)

func (*Container) InsertChildAt

func (c *Container) InsertChildAt(index int, component Component)

func (*Container) Invalidate

func (c *Container) Invalidate()

func (*Container) RemoveChild

func (c *Container) RemoveChild(component Component)

func (*Container) RemoveChildAt

func (c *Container) RemoveChildAt(index int)

func (*Container) Render

func (c *Container) Render(width int) []string

func (*Container) WantsKeyRelease

func (c *Container) WantsKeyRelease() bool

type Focusable

type Focusable interface {
	Component
	SetFocused(bool)
	IsFocused() bool
}

type FullRenderer added in v0.2.0

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

func (FullRenderer) Render added in v0.2.0

func (f FullRenderer) Render(clear bool)

type SliceResult

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

func SliceWithWidth

func SliceWithWidth(line string, startCol int, length int, strict bool) SliceResult

type TUI

type TUI struct {
	Container
	// contains filtered or unexported fields
}

func NewTUI

func NewTUI(terminal Terminal, showHardwareCursor bool) *TUI

func (*TUI) ForceRender

func (t *TUI) ForceRender()

func (*TUI) GetFullRedraws

func (t *TUI) GetFullRedraws() int

func (*TUI) GetShowHardwareCursor

func (t *TUI) GetShowHardwareCursor() bool

func (*TUI) HandleInput

func (t *TUI) HandleInput(data string)

func (*TUI) QueryCellSize

func (t *TUI) QueryCellSize()

func (*TUI) SetClearOnShrink

func (t *TUI) SetClearOnShrink(enabled bool)

func (*TUI) SetFocus

func (t *TUI) SetFocus(component Component)

SetFocus sets the component that currently receives keyboard input. In a TUI, only one interactive component (editor, selector, list, etc.) can receive input at a time. This method switches the "input focus": first unfocus the old component, then focus the new one.

func (*TUI) SetLogDir added in v0.14.0

func (t *TUI) SetLogDir(dir string)

SetLogDir sets the directory for debug and crash logs (fasttui-debug.log, fasttui-crash.log). Call before Start. Empty restores the default (FASTTUI_LOG_DIR or ~/.fasttui).

func (*TUI) SetShowHardwareCursor

func (t *TUI) SetShowHardwareCursor(enabled bool)

func (*TUI) Start

func (t *TUI) Start()

func (*TUI) Stop

func (t *TUI) Stop()

func (*TUI) TriggerRender

func (t *TUI) TriggerRender()

type Terminal

type Terminal interface {
	Start(onInput func(data string), onResize func()) error
	Stop()
	Write(data string)
	GetSize() (int, int)
	IsKittyProtocolActive() bool
	MoveBy(lines int)
	HideCursor()
	ShowCursor()
	ClearLine()
	ClearFromCursor()
	ClearScreen()
	SetTitle(title string)
}

Directories

Path Synopsis
_examples
chat command
input command
key command
select command
internal
debug
Package debug provides file-based diagnostic logging for fasttui.
Package debug provides file-based diagnostic logging for fasttui.

Jump to

Keyboard shortcuts

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