tuist

package module
v0.0.0-...-571b2a5 Latest Latest
Warning

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

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

README

tuist

A high-performance TUI framework for Go.

Demos

go run codeberg.org/vito/tuist/demos@latest

The Idea

  • Everything is a component, embedding tui.Compo
  • Components have lifecycle hooks (OnMount, OnDismount)
  • Components can be hovered and focused (SetHovered, SetFocused)
  • Components are fully interactive (HandleKeyPress, HandlePaste, HandleMouse)
  • Components render to text, with an optional cursor position
  • Components renders are cached, and only re-render when Compo.Update is called
  • Output is diffed against previous frame and only changed lines are repainted
  • If content changes off-screen, a full (synchronized) repaint is required (trade-off)

Inspiration

  • Go-app - component system, lifecycle hooks, UI goroutine model
  • pi-tui - the approach for repaintable scrollback; this project started as a straight-up conversion.
  • BubbleZone - for the mouse region markers trick
  • Bubbletea - a great TUI framework, I just needed a different model. This project leverages various components from its ecosystem (Lipgloss, Ultraviolet).

LLM usage

Used it lots. It wrote the commits, but not this README.

Documentation

Overview

Package tuist implements a differential terminal renderer that uses the normal scrollback buffer (no alternate screen). It can surgically update any line via cursor movement, and falls back to a full clear+repaint when off-screen content changes. Synchronized output prevents flickering.

This is a Go port of the pi TUI renderer.

Example
package main

import (
	"fmt"

	"charm.land/lipgloss/v2"
	uv "github.com/charmbracelet/ultraviolet"

	"codeberg.org/vito/tuist"
)

var (
	titleStyle = lipgloss.NewStyle().Bold(true).Foreground(lipgloss.Color("212"))
	countStyle = lipgloss.NewStyle().Bold(true).Foreground(lipgloss.Color("86"))
	hintStyle  = lipgloss.NewStyle().Foreground(lipgloss.Color("241"))
	keyStyle   = lipgloss.NewStyle().Bold(true).Foreground(lipgloss.Color("252"))
)

// Label is a static single-line component.
type Label struct {
	tuist.Compo
	Text string
}

func (l *Label) Render(ctx tuist.RenderContext) tuist.RenderResult {
	line := l.Text
	if tuist.VisibleWidth(line) > ctx.Width {
		line = tuist.Truncate(line, ctx.Width, "…")
	}
	return tuist.RenderResult{Lines: []string{line}}
}

// Counter increments on each key press, and 'q' quits.
type Counter struct {
	tuist.Compo
	Count   int
	quit    func()
	focused bool
}

func (c *Counter) Render(ctx tuist.RenderContext) tuist.RenderResult {
	line := countStyle.Render(fmt.Sprintf("%d", c.Count))
	if tuist.VisibleWidth(line) > ctx.Width {
		line = tuist.Truncate(line, ctx.Width, "…")
	}
	return tuist.RenderResult{Lines: []string{line}}
}

var _ tuist.Interactive = (*Counter)(nil)

func (c *Counter) HandleKeyPress(_ tuist.EventContext, ev uv.KeyPressEvent) bool {
	if ev.Text == "q" {
		c.quit()
		return true
	}
	c.Count++
	c.Update()
	return true
}

var _ tuist.Focusable = (*Counter)(nil)

func (c *Counter) SetFocused(_ tuist.EventContext, focused bool) { c.focused = focused }

func main() {
	term := tuist.NewProcessTerminal()
	tui := tuist.New(term)

	if err := tui.Start(); err != nil {
		panic(err)
	}
	defer tui.Stop()

	done := make(chan struct{})
	counter := &Counter{quit: func() { close(done) }}

	// All component mutations must happen on the UI goroutine.
	tui.Dispatch(func() {
		tui.AddChild(&Label{Text: titleStyle.Render("● Counter")})
		tui.AddChild(counter)
		tui.AddChild(&Label{
			Text: keyStyle.Render("any key") + hintStyle.Render(" increment  ") +
				keyStyle.Render("q") + hintStyle.Render(" quit"),
		})
		tui.SetFocus(counter)
	})

	<-done
}
Example (CompletionMenu)

ExampleCompletionMenu demonstrates how to wire up a CompletionMenu with a TextInput and a custom provider.

package main

import (
	"strings"

	"codeberg.org/vito/tuist"
)

func main() {
	ti := tuist.NewTextInput("sql> ")

	// Define available completions — in a real app these might come from
	// a schema, type environment, or static keyword list.
	keywords := []string{"SELECT", "FROM", "WHERE", "INSERT", "UPDATE", "DELETE", "JOIN", "LEFT", "RIGHT", "INNER", "GROUP", "ORDER", "BY", "LIMIT"}

	provider := func(input string, cursor int) tuist.CompletionResult {
		// Find the word being typed at the cursor.
		text := input[:cursor]
		wordStart := len(text)
		for wordStart > 0 && text[wordStart-1] != ' ' {
			wordStart--
		}
		partial := text[wordStart:]
		if partial == "" {
			return tuist.CompletionResult{}
		}

		partialUpper := strings.ToUpper(partial)
		var items []tuist.Completion
		for _, kw := range keywords {
			if strings.HasPrefix(kw, partialUpper) {
				items = append(items, tuist.Completion{
					Label:         kw,
					Detail:        "keyword",
					Documentation: "SQL keyword: " + kw,
					Kind:          "keyword",
				})
			}
		}
		return tuist.CompletionResult{
			Items:       items,
			ReplaceFrom: wordStart,
		}
	}

	_ = tuist.NewCompletionMenu(ti, provider)

	// In a real app:
	//   container.AddChild(ti)
	//   // CompletionMenu manages overlays via the TextInput's OnChange.
	//   // Parent HandleKeyPress should delegate to menu.HandleKeyPress first.
}

Index

Examples

Constants

This section is empty.

Variables

This section is empty.

Functions

func CompositeLineAt

func CompositeLineAt(baseLine, overlayLine string, startCol, overlayWidth, totalWidth int) string

CompositeLineAt splices overlay content into a base line at a specific column offset. The base line's content is preserved on both sides of the overlay, and the result is exactly totalWidth visible columns.

func ExpandTabs

func ExpandTabs(s string, tabWidth int) string

ExpandTabs replaces each tab character with spaces to reach the next tab stop (every tabWidth columns), accounting for ANSI escape sequences and wide characters.

func Mark

func Mark(comp Component, content string) string

Mark wraps content with invisible zone markers for a component. The framework scans rendered output for these markers to build a mouse hit map, enabling positional dispatch to the marked component.

Use Mark to make inline content (e.g. a clickable value within a status bar) receive mouse events directly. For full-line components in the render tree, marking is automatic — you don't need to call Mark yourself.

// In parent's Render:
re := tuist.Mark(c.reInput, c.reInput.RenderInline())
im := tuist.Mark(c.imInput, c.imInput.RenderInline())
top := title + " re " + re + "  im " + im

func SliceByColumn

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

SliceByColumn extracts a range of visible columns from a line. Returns the substring from startCol to startCol+length (in visible columns), preserving ANSI escape codes that are active at that point.

func Truncate

func Truncate(s string, maxWidth int, tail string) string

Truncate truncates s to at most maxWidth visible columns, appending tail (e.g. "...") if truncation occurred.

func VisibleWidth

func VisibleWidth(s string) int

VisibleWidth returns the terminal display width of a string, ignoring ANSI escape sequences and accounting for wide characters.

Types

type Completion

type Completion struct {
	// Label is the text inserted on accept.
	Label string

	// DisplayLabel overrides Label for rendering in the menu. If empty,
	// Label is shown. May contain ANSI escape codes for styling.
	DisplayLabel string

	// Detail is a short type signature or description shown alongside.
	// May contain ANSI escape codes for styling.
	Detail string

	// Documentation is a longer doc string for the detail panel.
	Documentation string

	// Kind is a free-form string describing the completion type (e.g.
	// "function", "keyword", "field"). May contain ANSI escape codes
	// for styling. The framework does not interpret this value — it is
	// purely for the consumer's use in DisplayLabel or DetailRenderer.
	Kind string

	// InsertText overrides Label for the actual text inserted into the
	// input. For example, an argument completion might insert "name: "
	// while showing "name" as the label. If empty, Label is used.
	InsertText string
}

Completion represents a single completion suggestion.

type CompletionMenu

type CompletionMenu struct {
	Compo

	// Provider generates completions. Must be set before use.
	Provider CompletionProvider

	// DetailRenderer renders the detail panel. If nil, a default
	// renderer is used that shows Detail and Documentation.
	DetailRenderer DetailRenderer

	// MaxVisible is the maximum number of items shown in the dropdown
	// before scrolling. Defaults to 8.
	MaxVisible int

	// Styles (all have sensible defaults).
	MenuStyle         lipgloss.Style
	MenuSelectedStyle lipgloss.Style
	MenuBorderStyle   lipgloss.Style
	DetailBorderStyle lipgloss.Style
	DetailTitleStyle  lipgloss.Style
	DimStyle          lipgloss.Style
	// contains filtered or unexported fields
}

CompletionMenu manages an autocomplete dropdown and detail panel for a TextInput. It handles:

  • Querying a CompletionProvider on input changes
  • Showing/hiding the dropdown overlay
  • Keyboard navigation (Up/Down/Escape)
  • Setting the ghost suggestion on the TextInput
  • Showing a detail panel for the highlighted item

Usage:

ti := tuist.NewTextInput("prompt> ")
menu := tuist.NewCompletionMenu(ti, provider)
// Add menu as a child so it receives bubbled keys.
container.AddChild(ti)
container.AddChild(menu) // invisible; just handles events

The CompletionMenu does not render any content itself — it manages overlays attached to the TextInput's cursor position.

func NewCompletionMenu

func NewCompletionMenu(input *TextInput, provider CompletionProvider) *CompletionMenu

NewCompletionMenu creates a CompletionMenu attached to the given TextInput.

func (*CompletionMenu) HandleKeyPress

func (m *CompletionMenu) HandleKeyPress(ctx EventContext, ev uv.KeyPressEvent) bool

HandleKeyPress intercepts Up/Down/Escape for menu navigation. Returns true if the key was consumed. Call this from a parent component's HandleKeyPress, or embed CompletionMenu as a child.

func (*CompletionMenu) Hide

func (m *CompletionMenu) Hide()

Hide dismisses the completion menu and detail panel.

func (*CompletionMenu) Refresh

func (m *CompletionMenu) Refresh(ctx EventContext)

Refresh re-queries the provider and updates the menu. Call this when external state changes (e.g. new bindings become available) but the input text hasn't changed.

func (*CompletionMenu) Visible

func (m *CompletionMenu) Visible() bool

Visible reports whether the dropdown is currently showing.

type CompletionProvider

type CompletionProvider func(input string, cursorPos int) CompletionResult

CompletionProvider is called by CompletionMenu when the input changes. It receives the full input text and cursor byte position, and returns completion candidates. Return an empty/nil Items slice for no completions.

type CompletionResult

type CompletionResult struct {
	// Items is the list of completion candidates.
	Items []Completion

	// ReplaceFrom is the byte offset in the input where the completed
	// token starts. The text from ReplaceFrom to the cursor is replaced
	// by the chosen completion's InsertText/Label.
	ReplaceFrom int
}

CompletionResult is returned by a CompletionProvider.

type Compo

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

Compo provides automatic render caching and dirty propagation for components. Embed it in your component struct:

type MyWidget struct {
    tuist.Compo
    // ... your fields ...
}

Call Update() when your component's state changes. The framework will re-render the component on the next frame. Between Update() calls, Render() is skipped entirely and the cached result is reused.

Update() propagates upward through the component tree, so parent Containers automatically know a child changed. If the tree is rooted in a TUI, Update() also schedules a render automatically.

Dirty tracking uses a monotonic generation counter rather than a boolean flag. Update() increments the counter; renderComponent snapshots it before calling Render and records the snapshot afterwards. Any concurrent Update() during Render increments the counter past the snapshot, guaranteeing a re-render on the next frame — no store-ordering subtleties required.

func (*Compo) RenderChild

func (c *Compo) RenderChild(child Component, ctx RenderContext) RenderResult

RenderChild renders a child component through this Compo, using the framework's render cache. It also wires the child's parent pointer so that Update() on the child propagates upward through this component.

MouseEnabled children are automatically mounted into the TUI (if the parent is mounted) so they participate in zone-based positional mouse dispatch. Their output is wrapped with zone markers. When the parent is re-rendered without calling RenderChild for a previously rendered child, that child is dismounted automatically.

Use this instead of calling child.Render(ctx) directly when your component wraps another component without using Container or Slot:

func (w *MyWrapper) Render(ctx tuist.RenderContext) tuist.RenderResult {
    return w.RenderChild(w.inner, ctx)
}

func (*Compo) Update

func (c *Compo) Update()

Update marks the component as needing re-render on the next frame. Propagates upward so parent containers are also marked dirty. If the component tree is rooted in a TUI, a render is scheduled automatically.

Must be called from the UI goroutine (event handlers, lifecycle hooks, or Dispatch callbacks). Background goroutines should use EventContext.Dispatch to schedule state changes and Update calls.

type Component

type Component interface {

	// Render produces the visual output within the given constraints.
	Render(ctx RenderContext) RenderResult
	// contains filtered or unexported methods
}

Component is the interface all UI components must implement. All components must embed Compo to get automatic render caching and dirty propagation.

type ComponentStat

type ComponentStat struct {
	Name     string `json:"name"`
	RenderUs int64  `json:"render_us"`
	Lines    int    `json:"lines"`
	Cached   bool   `json:"cached"`
}

ComponentStat captures render metrics for a single component within a frame.

type Container

type Container struct {
	Compo
	Children []Component
}

Container is a Component that holds child components and renders them sequentially (vertical stack). It embeds Compo, so parent containers can cache entire subtrees when nothing changes.

Container uses renderComponent for each child, so children with Compo that haven't called Update() are skipped entirely.

func (*Container) AddChild

func (c *Container) AddChild(comp Component)

func (*Container) Clear

func (c *Container) Clear()

func (*Container) RemoveChild

func (c *Container) RemoveChild(comp Component)

func (*Container) Render

func (c *Container) Render(ctx RenderContext) RenderResult

type CursorGroup

type CursorGroup struct{}

CursorGroup links cursor-relative overlays so they share a single above/below decision. Create one with NewCursorGroup and assign it to the CursorGroup field of each linked overlay's OverlayOptions.

func NewCursorGroup

func NewCursorGroup() *CursorGroup

NewCursorGroup creates a new cursor group. Pointer identity is used to determine group membership.

type CursorPos

type CursorPos struct {
	Row, Col int
}

CursorPos represents a cursor position within a component's rendered output.

type DetailRenderer

type DetailRenderer func(c Completion, width int) []string

DetailRenderer renders the detail panel for a highlighted completion. It receives the completion and available width, and returns lines to display in the detail bubble. If nil, a default renderer shows Detail and Documentation.

type Dismounter

type Dismounter interface {
	OnDismount()
}

Dismounter is an optional interface for components that need to perform cleanup when they leave a TUI-rooted tree. The mount context's Done() channel is already closed when OnDismount is called.

Dismount fires children-first (leaves before parents).

type EventContext

type EventContext struct {
	context.Context
	// contains filtered or unexported fields
}

EventContext provides access to framework operations. It is passed to event handlers, lifecycle hooks, and dispatch callbacks — the places where components perform side effects. It is NOT available during Render, which should be a pure function of component state.

EventContext embeds context.Context. The Done() channel is closed when the source component is dismounted, so background goroutines spawned from OnMount can use it as a cancellation signal.

func (EventContext) AddInputListener

func (ctx EventContext) AddInputListener(l InputListener) func()

AddInputListener registers a listener that intercepts input before it reaches the focused component. Returns a removal function.

func (EventContext) Attach

func (ctx EventContext) Attach(comp Component)

Attach connects a component to the TUI for event dispatch and Compo.Update without adding it to the render tree. This is used for inline components whose mouse regions are declared via [InlineRegion] in a parent's RenderResult.

The attached component can receive focus (via [SetFocus]), fire Compo.Update, and participate in hover tracking — just like a tree-mounted component. The parent pointer is set to the source component's Compo so that event bubbling works.

Call [Detach] when the component is no longer needed (typically in the parent's OnDismount).

func (EventContext) Detach

func (ctx EventContext) Detach(comp Component)

Detach disconnects a previously [Attach]ed component from the TUI.

func (EventContext) DisableMouse

func (ctx EventContext) DisableMouse()

DisableMouse decrements the mouse reference count, disabling terminal mouse reporting when no components need it.

func (EventContext) Dispatch

func (ctx EventContext) Dispatch(fn func())

Dispatch schedules a function to run on the UI goroutine.

Safe to call from any goroutine. This is the primary way for background goroutines (spawned from OnMount, commands, etc.) to mutate component state and call Compo.Update. The caller already has the EventContext in closure scope, so the callback doesn't receive one.

func (EventContext) EnableMouse

func (ctx EventContext) EnableMouse()

EnableMouse increments the mouse reference count, enabling terminal mouse reporting if it wasn't already enabled. Call DisableMouse to decrement.

func (EventContext) HasKittyKeyboard

func (ctx EventContext) HasKittyKeyboard() bool

HasKittyKeyboard reports terminal keyboard protocol support.

func (EventContext) HasOverlay

func (ctx EventContext) HasOverlay() bool

HasOverlay reports whether any overlay is currently visible.

func (EventContext) RequestRender

func (ctx EventContext) RequestRender(repaint bool)

RequestRender schedules a render. If repaint is true, forces full redraw.

func (EventContext) SetDebugWriter

func (ctx EventContext) SetDebugWriter(w io.Writer)

SetDebugWriter enables render performance logging. Must be called on the UI goroutine (from an event handler or Dispatch callback).

func (EventContext) SetFocus

func (ctx EventContext) SetFocus(comp Component)

SetFocus gives keyboard focus to the given component (or nil to blur).

func (EventContext) ShowOverlay

func (ctx EventContext) ShowOverlay(comp Component, opts *OverlayOptions) *OverlayHandle

ShowOverlay displays a component as an overlay and returns a handle.

type Focusable

type Focusable interface {
	SetFocused(ctx EventContext, focused bool)
}

Focusable is an optional interface for components that want to know when they gain or lose focus (e.g. to show/hide a cursor).

type Hoverable

type Hoverable interface {
	SetHovered(ctx EventContext, hovered bool)
}

Hoverable is an optional interface for MouseEnabled components that want to know when the mouse enters or leaves their rendered region. This is useful for clearing hover highlights when the cursor moves away.

SetHovered(true) is called when the mouse first enters the component's region. SetHovered(false) is called when the mouse leaves (moves to a different component or to a non-interactive area).

type InputListener

type InputListener func(ctx EventContext, ev uv.Event) bool

InputListener is called with each decoded event before it reaches the focused component. Return true to consume the event and stop propagation.

type Interactive

type Interactive interface {
	Component

	// HandleKeyPress is called with a decoded key press event.
	// Return true if the event was consumed; return false to let it
	// bubble to the parent component.
	HandleKeyPress(ctx EventContext, ev uv.KeyPressEvent) bool
}

Interactive is an optional interface for components that accept keyboard input when focused. The TUI decodes raw terminal bytes and dispatches typed events; components never see raw bytes.

Key events are delivered to the focused component first. If HandleKeyPress returns false, the event bubbles up through parent components in the tree (any parent implementing Interactive gets a chance to handle it). If the focused component does not implement Interactive at all, the event bubbles immediately.

type Mounter

type Mounter interface {
	OnMount(ctx EventContext)
}

Mounter is an optional interface for components that need to perform setup when they enter a TUI-rooted tree. The EventContext embeds context.Context whose Done() channel is closed when the component is dismounted — use it to bound background goroutine lifetimes.

OnMount is called:

  • When a component is added to a Container/Slot that is already mounted (i.e., connected to a TUI).
  • When an ancestor is mounted, propagating down to all descendants.

type MouseEnabled

type MouseEnabled interface {
	Component

	// HandleMouse is called with a decoded mouse event. Use ev.Row and
	// ev.Col for component-relative hit testing. Switch on
	// ev.MouseEvent.(type) to distinguish clicks, motion, and wheel
	// events. Return true if the event was consumed; return false to
	// let it bubble to the parent component.
	HandleMouse(ctx EventContext, ev MouseEvent) bool
}

MouseEnabled is an optional interface for components that need mouse event capture. When a component implementing MouseEnabled is mounted into a TUI-rooted tree, the TUI enables terminal mouse reporting (SGR extended mode with all-motion tracking). When the last such component is dismounted, mouse reporting is disabled and normal terminal scrollback behavior is restored.

Mouse events are dispatched positionally: the framework finds the deepest MouseEnabled component whose rendered region contains the mouse cursor and delivers the event there. If HandleMouse returns false, the event bubbles up through parent components in the tree (like key events). When MouseEnabled overlays are active, dispatch falls back to focus-based delivery.

type MouseEvent

type MouseEvent struct {
	uv.MouseEvent

	// Row is the mouse Y position relative to this component's first
	// rendered line (0-indexed).
	Row int

	// Col is the mouse X position (terminal column, 0-indexed).
	Col int
}

MouseEvent wraps an ultraviolet mouse event with component-relative coordinates for hit-testing within the component's rendered output.

Use Row and Col for position checks. Use the embedded uv.MouseEvent for button/modifier info and to distinguish event subtypes:

switch ev.MouseEvent.(type) {
case uv.MouseClickEvent:
case uv.MouseMotionEvent:
case uv.MouseWheelEvent:
}

type OverlayAnchor

type OverlayAnchor int

OverlayAnchor specifies where an overlay is positioned relative to the terminal viewport.

const (
	AnchorCenter OverlayAnchor = iota
	AnchorTopLeft
	AnchorTopRight
	AnchorBottomLeft
	AnchorBottomRight
	AnchorTopCenter
	AnchorBottomCenter
	AnchorLeftCenter
	AnchorRightCenter
)

type OverlayHandle

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

OverlayHandle controls a displayed overlay.

func (*OverlayHandle) Hide

func (h *OverlayHandle) Hide()

Hide permanently removes the overlay. Must be called on the UI goroutine (from an event handler or Dispatch).

func (*OverlayHandle) IsHidden

func (h *OverlayHandle) IsHidden() bool

IsHidden reports whether the overlay is temporarily hidden. Must be called on the UI goroutine (from an event handler or Dispatch).

func (*OverlayHandle) SetHidden

func (h *OverlayHandle) SetHidden(hidden bool)

SetHidden temporarily hides or shows the overlay. Focus is not changed; the caller should manage focus explicitly via TUI.SetFocus. Must be called on the UI goroutine (from an event handler or Dispatch).

func (*OverlayHandle) SetOptions

func (h *OverlayHandle) SetOptions(opts *OverlayOptions)

SetOptions replaces the overlay's positioning/sizing options without destroying and recreating the overlay. This avoids allocation churn for things like repositioning a completion menu on each keystroke. Must be called on the UI goroutine (from an event handler or Dispatch).

type OverlayMargin

type OverlayMargin struct {
	Top, Right, Bottom, Left int
}

OverlayMargin specifies pixel-free spacing from terminal edges.

type OverlayOptions

type OverlayOptions struct {
	Width     SizeValue
	MinWidth  int
	MaxHeight SizeValue

	Anchor  OverlayAnchor
	OffsetX int
	OffsetY int

	Row SizeValue
	Col SizeValue

	Margin OverlayMargin

	// ContentRelative positions the overlay relative to the rendered content
	// bounds rather than the terminal viewport. For example, AnchorBottomLeft
	// with ContentRelative places the overlay at the bottom of the content
	// (not the bottom of the screen), which is useful for menus that should
	// float just above the last content line.
	ContentRelative bool

	// CursorRelative positions the overlay relative to the base content's
	// cursor position. The cursor position is resolved during compositing
	// on the render goroutine, so the caller doesn't need to track it.
	//
	// When set, Col is placed at cursor column + OffsetX. Row placement
	// depends on PreferAbove: if true, the overlay's bottom edge is placed
	// at the row above the cursor; if there isn't enough room above, it
	// flips to below. If false (or unset), the overlay starts below the
	// cursor and flips to above when needed.
	//
	// Width, MaxHeight, MinWidth, and Margin are resolved normally.
	// Anchor is ignored for row/col positioning (PreferAbove and OffsetX
	// determine placement). If the base content has no cursor, the overlay
	// is hidden for that frame.
	CursorRelative bool

	// PreferAbove is used with CursorRelative to place the overlay above
	// the cursor row when there is enough room, flipping to below otherwise.
	PreferAbove bool

	// CursorGroup links cursor-relative overlays so they share the same
	// above/below direction. If any overlay in the group doesn't fit
	// above the cursor, all overlays in the group are placed below.
	// This ensures companion overlays (e.g. a completion menu and its
	// detail panel) stay on the same side of the cursor regardless of
	// their individual heights.
	CursorGroup *CursorGroup
}

OverlayOptions configures overlay positioning and sizing.

Overlays are pure rendering constructs — they composite a component on top of the base content. Focus management is the caller's responsibility; use TUI.SetFocus to direct input to the overlay's component when needed.

type Pasteable

type Pasteable interface {
	HandlePaste(ctx EventContext, ev uv.PasteEvent) bool
}

Pasteable is an optional interface for components that accept pasted text (via bracketed paste). Paste events bubble like key events: if HandlePaste returns false, the event propagates to the parent.

type ProcessTerminal

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

ProcessTerminal is a Terminal backed by os.Stdin / os.Stdout. Terminal dimensions are cached and refreshed on SIGWINCH to avoid repeated ioctl syscalls during rendering.

func NewProcessTerminal

func NewProcessTerminal() *ProcessTerminal

func (*ProcessTerminal) Columns

func (t *ProcessTerminal) Columns() int

func (*ProcessTerminal) HideCursor

func (t *ProcessTerminal) HideCursor()

func (*ProcessTerminal) Rows

func (t *ProcessTerminal) Rows() int

func (*ProcessTerminal) ShowCursor

func (t *ProcessTerminal) ShowCursor()

func (*ProcessTerminal) Start

func (t *ProcessTerminal) Start(onInput func([]byte), onResize func()) error

func (*ProcessTerminal) Stop

func (t *ProcessTerminal) Stop()

func (*ProcessTerminal) Write

func (t *ProcessTerminal) Write(p []byte)

func (*ProcessTerminal) WriteString

func (t *ProcessTerminal) WriteString(s string)

type RenderContext

type RenderContext struct {
	// Width is the available width in terminal columns.
	Width int
	// Height is the allocated height in lines. 0 means unconstrained
	// (the component may return as many lines as it wants).
	Height int
	// ScreenHeight is the actual terminal height in rows. It is always
	// set regardless of whether Height constrains the component. Components
	// that render inline but want to fill the viewport can use this.
	ScreenHeight int

	// Recycle is a pre-allocated []string from the previous render,
	// resliced to zero length. Components may append into it to avoid
	// allocating a new lines slice each frame. It is nil on the first
	// render. Components that ignore it get no behavior change.
	//
	// The slice is safe to reuse because parent containers copy child
	// lines into their own buffer via append.
	Recycle []string
	// contains filtered or unexported fields
}

RenderContext carries everything a component needs to render.

type RenderResult

type RenderResult struct {
	// Lines is the rendered content.
	Lines []string

	// Cursor, if non-nil, is where the hardware cursor should be placed,
	// relative to this component's output (Row 0 = first line of Lines).
	Cursor *CursorPos
}

RenderResult is the output of a Component.Render call.

type RenderStats

type RenderStats struct {
	// RenderTime is how long it took to generate the rendered content
	// (calling Component.Render on the tree, excluding overlay compositing).
	RenderTime time.Duration

	// CompositeTime is how long overlay compositing took (column-level
	// string surgery). Zero when there are no overlays.
	CompositeTime time.Duration

	// DiffTime is how long the differential update computation took.
	DiffTime time.Duration

	// WriteTime is how long it took to write the escape sequences to
	// the terminal.
	WriteTime time.Duration

	// TotalTime is the wall-clock duration of the entire doRender call.
	TotalTime time.Duration

	// TotalLines is the total number of lines in the rendered output.
	TotalLines int

	// LinesRepainted is the number of lines that were actually written
	// to the terminal (changed lines).
	LinesRepainted int

	// CacheHits is the number of lines that matched the previous frame
	// and were skipped.
	CacheHits int

	// FullRedraw is true when the entire screen was repainted (no diff).
	FullRedraw bool

	// FullRedrawReason describes why a full redraw was triggered.
	FullRedrawReason string

	// OverlayCount is the number of active overlays composited.
	OverlayCount int

	// BytesWritten is the number of bytes sent to the terminal (escape
	// sequences + content). Large values indicate potential slowness over
	// SSH or on slow terminals.
	BytesWritten int

	// FirstChangedLine is the first line index that differed from the
	// previous frame, or -1 if nothing changed.
	FirstChangedLine int

	// LastChangedLine is the last line index that differed from the
	// previous frame, or -1 if nothing changed.
	LastChangedLine int

	// ScrollLines is how many lines the viewport scrolled this frame.
	ScrollLines int

	// HeapAlloc is the current heap allocation in bytes (live objects).
	HeapAlloc uint64

	// HeapObjects is the number of allocated heap objects.
	HeapObjects uint64

	// TotalAlloc is the cumulative bytes allocated (monotonically increasing).
	TotalAlloc uint64

	// Sys is the total memory obtained from the OS.
	Sys uint64

	// Mallocs is the number of heap allocations during this render frame.
	Mallocs uint64

	// Frees is the number of heap frees during this render frame.
	Frees uint64

	// HeapAllocDelta is the net bytes allocated during this render frame
	// (TotalAlloc delta).
	HeapAllocDelta uint64

	// NumGC is the total number of completed GC cycles.
	NumGC uint32

	// GCPauseNs is the most recent GC pause duration in nanoseconds.
	GCPauseNs uint64

	// GCCPUFraction is the fraction of CPU time used by the GC.
	GCCPUFraction float64

	// Goroutines is the number of goroutines at the time of the render.
	Goroutines int

	// StackInuse is bytes used by goroutine stacks.
	StackInuse uint64

	// HeapInuse is bytes in in-use heap spans.
	HeapInuse uint64

	// HeapIdle is bytes in idle (unused) heap spans.
	HeapIdle uint64
}

RenderStats captures performance metrics for a single render cycle.

type SizeValue

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

SizeValue represents either an absolute column/row count or a percentage of the terminal dimension ("50%"). Use SizeAbs and SizePct helpers.

func SizeAbs

func SizeAbs(n int) SizeValue

SizeAbs returns an absolute SizeValue.

func SizePct

func SizePct(p float64) SizeValue

SizePct returns a percentage SizeValue (0-100).

type Slot

type Slot struct {
	Compo
	// contains filtered or unexported fields
}

Slot is a component that delegates to a single replaceable child. Use it to swap between components (e.g. text input vs spinner) without modifying the parent container's child list.

func NewSlot

func NewSlot(child Component) *Slot

NewSlot creates a Slot with the given initial child.

func (*Slot) Get

func (s *Slot) Get() Component

Get returns the current child.

func (*Slot) Render

func (s *Slot) Render(ctx RenderContext) RenderResult

func (*Slot) Set

func (s *Slot) Set(c Component)

Set replaces the current child.

type Spinner

type Spinner struct {
	Compo

	// Style wraps each frame (e.g. to apply color). May be nil.
	Style func(string) string
	// Label is displayed after the spinner frame.
	Label string
	// contains filtered or unexported fields
}

Spinner is a component that shows an animated spinner. It starts spinning automatically when mounted (added to a TUI-rooted tree) and stops when dismounted.

func NewSpinner

func NewSpinner() *Spinner

NewSpinner creates a dot-style spinner.

func (*Spinner) OnMount

func (s *Spinner) OnMount(ctx EventContext)

OnMount starts the spinner animation. The goroutine is bounded by ctx.Done(), which fires when the component is dismounted.

func (*Spinner) Render

func (s *Spinner) Render(ctx RenderContext) RenderResult

type TUI

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

TUI is the main renderer. It extends Container with differential rendering on the normal scrollback buffer.

All component state — including Render(), HandleKeyPress(), and any Dispatch callbacks — runs on a single UI goroutine. Components never need locks for their own fields.

func New

func New(term Terminal) *TUI

New creates a TUI backed by the given terminal.

func (*TUI) AddInputListener

func (t *TUI) AddInputListener(l InputListener) func()

AddInputListener registers a listener that intercepts input before it reaches the focused component. Returns a function that removes it. Must be called on the UI goroutine (from an event handler or Dispatch).

func (*TUI) DisableMouse

func (t *TUI) DisableMouse()

DisableMouse decrements the mouse reference count and, if transitioning to 0, disables terminal mouse reporting. Must be called on the UI goroutine.

func (*TUI) Dispatch

func (t *TUI) Dispatch(fn func())

Dispatch schedules a function to run on the UI goroutine. Use this to mutate component state from background goroutines (e.g. after async I/O). The function runs before the next render, serialized with all input handling and other dispatched functions.

Safe to call from any goroutine.

func (*TUI) EnableMouse

func (t *TUI) EnableMouse()

EnableMouse increments the mouse reference count and, if transitioning from 0 to 1, enables terminal mouse reporting. Must be called on the UI goroutine. Components normally don't call this directly — the framework calls it automatically when a MouseEnabled component is mounted.

func (*TUI) FullRedraws

func (t *TUI) FullRedraws() int

FullRedraws returns the number of full (non-differential) redraws performed.

Safe to call from any goroutine.

func (*TUI) HasKittyKeyboard

func (t *TUI) HasKittyKeyboard() bool

HasKittyKeyboard reports whether the terminal confirmed support for the Kitty keyboard protocol (disambiguate escape codes). This is determined by the response to the RequestKittyKeyboard query sent during Start(). Returns false until the response is received.

Safe to call from any goroutine.

func (*TUI) HasOverlay

func (t *TUI) HasOverlay() bool

HasOverlay reports whether any overlay is currently visible. Must be called on the UI goroutine (from an event handler or Dispatch).

func (*TUI) RequestRender

func (t *TUI) RequestRender(repaint bool)

RequestRender schedules a render on the next iteration. If repaint is true, all cached state is discarded and a full repaint occurs.

Safe to call from any goroutine.

func (*TUI) SetClearOnShrink

func (t *TUI) SetClearOnShrink(enabled bool)

SetClearOnShrink controls whether empty rows are cleared when content shrinks. When false (the default), stale rows remain until overwritten, which reduces full redraws on slower terminals.

func (*TUI) SetDebugWriter

func (t *TUI) SetDebugWriter(w io.Writer)

SetDebugWriter enables render performance logging. Each render cycle writes a single stats line to w. Pass nil to disable.

Safe to call from any goroutine; takes effect on next render.

func (*TUI) SetFocus

func (t *TUI) SetFocus(comp Component)

SetFocus gives keyboard focus to the given component (or nil). Must be called on the UI goroutine (from an event handler or Dispatch).

func (*TUI) SetShowHardwareCursor

func (t *TUI) SetShowHardwareCursor(enabled bool)

SetShowHardwareCursor enables or disables the hardware cursor (for IME).

func (*TUI) ShowOverlay

func (t *TUI) ShowOverlay(comp Component, opts *OverlayOptions) *OverlayHandle

ShowOverlay displays a component as an overlay on top of the base content. Focus is not changed; use TUI.SetFocus to direct input to the overlay's component when needed. Must be called on the UI goroutine (from an event handler or Dispatch).

func (*TUI) Start

func (t *TUI) Start() error

Start begins the TUI event loop.

func (*TUI) Stop

func (t *TUI) Stop()

Stop ends the TUI event loop and restores the terminal.

func (*TUI) Terminal

func (t *TUI) Terminal() Terminal

Terminal returns the underlying terminal.

type Terminal

type Terminal interface {
	// Start puts the terminal into raw mode and begins listening for input
	// and resize events. onInput receives raw bytes from stdin. onResize is
	// called when the terminal dimensions change.
	Start(onInput func([]byte), onResize func()) error

	// Stop restores the terminal to its original state.
	Stop()

	// Write sends raw bytes to the terminal.
	Write(p []byte)

	// WriteString sends a string to the terminal.
	WriteString(s string)

	// Columns returns the current terminal width.
	Columns() int

	// Rows returns the current terminal height.
	Rows() int

	// HideCursor hides the hardware cursor.
	HideCursor()

	// ShowCursor shows the hardware cursor.
	ShowCursor()
}

Terminal abstracts terminal I/O so the renderer can be tested with a fake terminal.

type TextInput

type TextInput struct {
	Compo

	// Prompt is rendered before the first line. May contain ANSI codes.
	Prompt string

	// ContinuationPrompt is rendered before continuation lines (lines
	// after the first in multiline input). Defaults to aligned spacing
	// if empty.
	ContinuationPrompt string

	// OnSubmit is called when Enter is pressed. The string is the trimmed
	// input value. Return true to clear the input after submission.
	OnSubmit func(ctx EventContext, value string) bool

	// Suggestion is a ghost completion hint shown after the cursor. It is
	// cleared on every keystroke and must be re-set by the caller (e.g. in
	// OnSubmit or OnChange). Cleared automatically on each keystroke.
	Suggestion string

	// SuggestionStyle wraps the suggestion text (e.g. dim style).
	// If nil, the suggestion is rendered as-is.
	SuggestionStyle func(string) string

	// OnChange is called after the input value has been modified (character
	// inserted, deleted, etc.). It is NOT called for cursor-only movements.
	OnChange func(ctx EventContext)
	// contains filtered or unexported fields
}

TextInput is a text editor component with cursor, history, and kill-line support. It supports multiline editing via Shift+Enter.

func NewTextInput

func NewTextInput(prompt string) *TextInput

NewTextInput creates a TextInput with the given prompt.

func (*TextInput) CursorEnd

func (t *TextInput) CursorEnd()

CursorEnd moves the cursor to the end of the input.

func (*TextInput) CursorScreenCol

func (t *TextInput) CursorScreenCol() int

CursorScreenCol returns the screen column of the cursor, including the prompt width. This is useful for callers that need to position overlays (e.g. completion menus) relative to the cursor.

func (*TextInput) HandleKeyPress

func (t *TextInput) HandleKeyPress(ctx EventContext, ev uv.KeyPressEvent) bool

HandleKeyPress implements Interactive.

func (*TextInput) HandlePaste

func (t *TextInput) HandlePaste(ctx EventContext, ev uv.PasteEvent) bool

HandlePaste implements Pasteable.

func (*TextInput) Render

func (t *TextInput) Render(ctx RenderContext) RenderResult

Render returns one or more lines: prompt + input, with cursor position.

func (*TextInput) SetFocused

func (t *TextInput) SetFocused(_ EventContext, focused bool)

func (*TextInput) SetValue

func (t *TextInput) SetValue(s string)

SetValue replaces the input and moves the cursor to the end.

func (*TextInput) Value

func (t *TextInput) Value() string

Value returns the current input string.

Directories

Path Synopsis
grid renders an interactive grid of colored rectangles that respond to mouse hover and click-to-focus, demonstrating tuist's marker-based zone system with side-by-side layout.
grid renders an interactive grid of colored rectangles that respond to mouse hover and click-to-focus, demonstrating tuist's marker-based zone system with side-by-side layout.
internal
debugui command
Command render-debug launches a live web dashboard for tuist render performance metrics.
Command render-debug launches a live web dashboard for tuist render performance metrics.
teav1 module

Jump to

Keyboard shortcuts

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