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 ¶
- func CompositeLineAt(baseLine, overlayLine string, startCol, overlayWidth, totalWidth int) string
- func ExpandTabs(s string, tabWidth int) string
- func Mark(comp Component, content string) string
- func SliceByColumn(line string, startCol, length int) string
- func Truncate(s string, maxWidth int, tail string) string
- func VisibleWidth(s string) int
- type Completion
- type CompletionMenu
- type CompletionProvider
- type CompletionResult
- type Compo
- type Component
- type ComponentStat
- type Container
- type CursorGroup
- type CursorPos
- type DetailRenderer
- type Dismounter
- type EventContext
- func (ctx EventContext) AddInputListener(l InputListener) func()
- func (ctx EventContext) Attach(comp Component)
- func (ctx EventContext) Detach(comp Component)
- func (ctx EventContext) DisableMouse()
- func (ctx EventContext) Dispatch(fn func())
- func (ctx EventContext) EnableMouse()
- func (ctx EventContext) HasKittyKeyboard() bool
- func (ctx EventContext) HasOverlay() bool
- func (ctx EventContext) RequestRender(repaint bool)
- func (ctx EventContext) SetDebugWriter(w io.Writer)
- func (ctx EventContext) SetFocus(comp Component)
- func (ctx EventContext) ShowOverlay(comp Component, opts *OverlayOptions) *OverlayHandle
- type Focusable
- type Hoverable
- type InputListener
- type Interactive
- type Mounter
- type MouseEnabled
- type MouseEvent
- type OverlayAnchor
- type OverlayHandle
- type OverlayMargin
- type OverlayOptions
- type Pasteable
- type ProcessTerminal
- func (t *ProcessTerminal) Columns() int
- func (t *ProcessTerminal) HideCursor()
- func (t *ProcessTerminal) Rows() int
- func (t *ProcessTerminal) ShowCursor()
- func (t *ProcessTerminal) Start(onInput func([]byte), onResize func()) error
- func (t *ProcessTerminal) Stop()
- func (t *ProcessTerminal) Write(p []byte)
- func (t *ProcessTerminal) WriteString(s string)
- type RenderContext
- type RenderResult
- type RenderStats
- type SizeValue
- type Slot
- type Spinner
- type TUI
- func (t *TUI) AddInputListener(l InputListener) func()
- func (t *TUI) DisableMouse()
- func (t *TUI) Dispatch(fn func())
- func (t *TUI) EnableMouse()
- func (t *TUI) FullRedraws() int
- func (t *TUI) HasKittyKeyboard() bool
- func (t *TUI) HasOverlay() bool
- func (t *TUI) RequestRender(repaint bool)
- func (t *TUI) SetClearOnShrink(enabled bool)
- func (t *TUI) SetDebugWriter(w io.Writer)
- func (t *TUI) SetFocus(comp Component)
- func (t *TUI) SetShowHardwareCursor(enabled bool)
- func (t *TUI) ShowOverlay(comp Component, opts *OverlayOptions) *OverlayHandle
- func (t *TUI) Start() error
- func (t *TUI) Stop()
- func (t *TUI) Terminal() Terminal
- type Terminal
- type TextInput
- func (t *TextInput) CursorEnd()
- func (t *TextInput) CursorScreenCol() int
- func (t *TextInput) HandleKeyPress(ctx EventContext, ev uv.KeyPressEvent) bool
- func (t *TextInput) HandlePaste(ctx EventContext, ev uv.PasteEvent) bool
- func (t *TextInput) Render(ctx RenderContext) RenderResult
- func (t *TextInput) SetFocused(_ EventContext, focused bool)
- func (t *TextInput) SetValue(s string)
- func (t *TextInput) Value() string
Examples ¶
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
func CompositeLineAt ¶
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 ¶
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 ¶
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 ¶
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 ¶
Truncate truncates s to at most maxWidth visible columns, appending tail (e.g. "...") if truncation occurred.
func VisibleWidth ¶
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 ¶
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) RemoveChild ¶
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 ¶
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.
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 (*Slot) Render ¶
func (s *Slot) Render(ctx RenderContext) RenderResult
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 (*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 (*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 ¶
FullRedraws returns the number of full (non-differential) redraws performed.
Safe to call from any goroutine.
func (*TUI) HasKittyKeyboard ¶
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 ¶
HasOverlay reports whether any overlay is currently visible. Must be called on the UI goroutine (from an event handler or Dispatch).
func (*TUI) RequestRender ¶
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 ¶
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 ¶
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 ¶
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 ¶
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).
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 ¶
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 ¶
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)
Source Files
¶
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
|