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"
"github.com/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.Context) tuist.RenderResult {
return tuist.RenderResult{Lines: []string{l.Text}}
}
// 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.Context) tuist.RenderResult {
return tuist.RenderResult{Lines: []string{countStyle.Render(fmt.Sprintf("%d", c.Count))}}
}
var _ tuist.Interactive = (*Counter)(nil)
func (c *Counter) HandleKeyPress(_ tuist.Context, 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.Context, focused bool) { c.focused = focused }
func main() {
term := tuist.NewStdTerminal()
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"
"github.com/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 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 Context
- func (ctx Context) AddInputListener(l InputListener) func()
- func (ctx Context) Dispatch(fn func())
- func (ctx Context) HasKittyKeyboard() bool
- func (ctx Context) Recycle() []string
- func (ctx Context) RequestRender(repaint bool)
- func (ctx Context) Resize(w, h int) Context
- func (ctx Context) ScreenHeight() int
- func (ctx Context) SetFocus(comp Component)
- func (ctx Context) ShowOverlay(comp Component, opts *OverlayOptions) *OverlayHandle
- type CursorGroup
- type CursorPos
- type DetailRenderer
- type Dismounter
- 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 RenderResult
- type RenderStats
- type SizeValue
- type Slot
- type Spinner
- type StdTerminal
- func (t *StdTerminal) Columns() int
- func (t *StdTerminal) HideCursor()
- func (t *StdTerminal) Rows() int
- func (t *StdTerminal) SetInputPassthrough(w io.Writer)
- func (t *StdTerminal) ShowCursor()
- func (t *StdTerminal) Start(onInput func([]byte), onResize func()) error
- func (t *StdTerminal) Stop()
- func (t *StdTerminal) Write(p []byte)
- func (t *StdTerminal) WriteString(s string)
- type TUI
- func (t *TUI) AddInputListener(l InputListener) func()
- func (t *TUI) Dispatch(fn func())
- func (t *TUI) Exec(fn func(in io.Reader, out io.Writer, errOut io.Writer) error) error
- func (t *TUI) FullRedraws() int
- func (t *TUI) HasKittyKeyboard() bool
- func (t *TUI) RenderOnce()
- func (t *TUI) RequestRender(repaint 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()
- type Terminal
- type TextInput
- func (t *TextInput) CursorEnd()
- func (t *TextInput) CursorScreenCol() int
- func (t *TextInput) HandleKeyPress(ctx Context, ev uv.KeyPressEvent) bool
- func (t *TextInput) HandlePaste(ctx Context, ev uv.PasteEvent) bool
- func (t *TextInput) InsertRune(r rune)
- func (t *TextInput) Render(ctx Context) RenderResult
- func (t *TextInput) SetFocused(_ Context, 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 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 Context, 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 Context)
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(ctx Context, child Component) RenderResult
RenderChild renders a child component through this Compo, using the framework's render cache. It is the single mechanism for child lifecycle management — Container and Slot use it internally.
RenderChild wires the child's parent pointer so that Compo.Update propagates upward. Children are automatically mounted (firing [Mounter.OnMount]) on their first render and dismounted when the parent re-renders without calling RenderChild for them (orphan cleanup). MouseEnabled children have their output wrapped with zone markers for positional mouse dispatch.
The ctx argument carries layout constraints (Width, Height). Pass-through rendering inherits the parent's constraints; use Context.Resize for adjusted dimensions:
func (w *MyWrapper) Render(ctx tuist.Context) tuist.RenderResult {
return w.RenderChild(ctx, w.inner)
}
func (b *Border) Render(ctx tuist.Context) tuist.RenderResult {
return b.RenderChild(ctx.Resize(w, h), b.child)
}
func (*Compo) RenderChildInline ¶
RenderChildInline renders a child component and returns the result as a single string suitable for inline embedding within a parent's line. For MouseEnabled children, the string is automatically wrapped with zone markers for positional mouse dispatch.
This is a convenience wrapper around [RenderChild] for components that produce content meant to be composed horizontally within a parent's rendered line:
func (c *Chrome) Render(ctx tuist.Context) tuist.RenderResult {
re := c.RenderChildInline(ctx, c.reInput)
im := c.RenderChildInline(ctx, c.imInput)
top := title + " re " + re + " im " + im
return tuist.RenderResult{Lines: []string{top}}
}
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 Context.Dispatch to schedule state changes and Update calls.
type Component ¶
type Component interface {
// Render produces the visual output within the given constraints.
Render(ctx Context) 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.
Children are mounted lazily via [RenderChild] on the first render after being added. Removed children are dismounted when the parent re-renders and they are no longer in the child list (orphan cleanup).
func (*Container) AddChild ¶
AddChild appends a component to the container. The child will be mounted on the next render via [RenderChild].
func (*Container) Clear ¶
func (c *Container) Clear()
Clear removes all children. They will be dismounted when the container re-renders (orphan cleanup).
func (*Container) RemoveChild ¶
RemoveChild removes a component from the container. The child will be dismounted when the container re-renders (orphan cleanup).
func (*Container) Render ¶
func (c *Container) Render(ctx Context) RenderResult
type Context ¶ added in v0.0.2
type Context struct {
context.Context
// Width is the available width in terminal columns. During Render
// this carries the layout constraint; outside Render it is zero.
Width int
// Height is the allocated height in lines. 0 means unconstrained
// (the component may return as many lines as it wants).
Height int
// contains filtered or unexported fields
}
Context is a component's handle to the framework. Every framework callback — Render, OnMount, HandleKeyPress, SetFocused, etc. — receives a Context.
Context 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.
During Render, Width and Height carry layout constraints. Outside Render (event handlers, lifecycle hooks) they are zero.
func (Context) AddInputListener ¶ added in v0.0.2
func (ctx Context) AddInputListener(l InputListener) func()
AddInputListener registers a listener that intercepts input before it reaches the focused component. Returns a removal function.
func (Context) Dispatch ¶ added in v0.0.2
func (ctx Context) 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 Context in closure scope, so the callback doesn't receive one.
func (Context) HasKittyKeyboard ¶ added in v0.0.2
HasKittyKeyboard reports terminal keyboard protocol support.
func (Context) Recycle ¶ added in v0.0.2
Recycle returns 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.
func (Context) RequestRender ¶ added in v0.0.2
RequestRender schedules a render. If repaint is true, forces full redraw.
func (Context) Resize ¶ added in v0.0.2
Resize returns a copy of ctx with the given Width and Height.
func (Context) ScreenHeight ¶ added in v0.0.2
ScreenHeight returns the actual terminal height in rows. It is always available regardless of whether Height constrains the component. Components that render inline but want to fill the viewport can use this. Returns 0 if the component is not mounted in a TUI.
func (Context) SetFocus ¶ added in v0.0.2
SetFocus gives keyboard focus to the given component (or nil to blur).
func (Context) ShowOverlay ¶ added in v0.0.2
func (ctx Context) ShowOverlay(comp Component, opts *OverlayOptions) *OverlayHandle
ShowOverlay displays a component as an overlay and returns a handle.
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.
OnDismount is called lazily during the render pass when a parent re-renders without calling [RenderChild] for a previously rendered child (orphan cleanup). Dismount fires children-first (leaves before parents).
type Focusable ¶
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 ¶
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 ¶
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 Context, 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 Context)
}
Mounter is an optional interface for components that need to perform setup when they enter a TUI-rooted tree. The Context embeds context.Context whose Done() channel is closed when the component is dismounted — use it to bound background goroutine lifetimes.
OnMount is called lazily during the first render after a component is added to a Container, Slot, or rendered via [RenderChild]. This means OnMount fires on the UI goroutine during the render pass, not immediately when AddChild or Set is called.
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 Context, 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) 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) Remove ¶ added in v0.0.3
func (h *OverlayHandle) Remove()
Remove permanently removes the overlay from the overlay stack. 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 Context, 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 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.
The child is mounted lazily via [RenderChild] on the first render after being set. The previous child is dismounted when the Slot re-renders and it is no longer the current child (orphan cleanup).
func (*Slot) Render ¶
func (s *Slot) Render(ctx Context) 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 ¶
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 Context) RenderResult
type StdTerminal ¶ added in v0.0.2
type StdTerminal struct {
// contains filtered or unexported fields
}
StdTerminal is a Terminal backed by the standard file descriptors Terminal dimensions are cached and refreshed on SIGWINCH to avoid repeated ioctl syscalls during rendering.
func NewStdTerminal ¶ added in v0.0.2
func NewStdTerminal() *StdTerminal
func (*StdTerminal) Columns ¶ added in v0.0.2
func (t *StdTerminal) Columns() int
func (*StdTerminal) HideCursor ¶ added in v0.0.2
func (t *StdTerminal) HideCursor()
func (*StdTerminal) Rows ¶ added in v0.0.2
func (t *StdTerminal) Rows() int
func (*StdTerminal) SetInputPassthrough ¶ added in v0.0.2
func (t *StdTerminal) SetInputPassthrough(w io.Writer)
SetInputPassthrough redirects stdin to the given writer instead of the normal input handler. Pass nil to discard input (e.g. when the terminal is stopped). Call with the onInput wrapper to resume normal input handling (done automatically by Start).
func (*StdTerminal) ShowCursor ¶ added in v0.0.2
func (t *StdTerminal) ShowCursor()
func (*StdTerminal) Start ¶ added in v0.0.2
func (t *StdTerminal) Start(onInput func([]byte), onResize func()) error
func (*StdTerminal) Stop ¶ added in v0.0.2
func (t *StdTerminal) Stop()
func (*StdTerminal) Write ¶ added in v0.0.2
func (t *StdTerminal) Write(p []byte)
func (*StdTerminal) WriteString ¶ added in v0.0.2
func (t *StdTerminal) WriteString(s string)
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 ¶
New creates a TUI backed by the given terminal. No goroutines are started; call TUI.Start to begin the interactive event loop, or use TUI.RenderOnce for synchronous single-frame rendering.
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) 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) Exec ¶
Exec suspends the TUI and runs fn with exclusive access to the terminal. Input from stdin is piped to in; out and errOut are connected to stdout and stderr respectively. When fn returns, the TUI is automatically restarted.
This is the equivalent of bubbletea's ExecCommand. The terminal's single reader goroutine remains the sole consumer of os.Stdin; fn reads from a pipe that receives the forwarded bytes.
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) RenderOnce ¶
func (t *TUI) RenderOnce()
RenderOnce performs a single synchronous render cycle, writing the output directly to the underlying Terminal. This is useful for headless rendering, testing, or any scenario where you want to produce exactly one frame without running the event loop.
Must not be called concurrently with the event loop (i.e., don't call this while Start() is active).
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) 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. When enabled, the terminal cursor is positioned at the component's reported cursor location and made visible; this is used for IME input and cursor-position tests.
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 ¶
Start begins the TUI event loop and puts the terminal into raw mode. The event loop runs on a background goroutine; all component state mutations (Render, HandleKeyPress, Dispatch callbacks) are serialized on that goroutine. Call TUI.Stop to end the loop and restore the terminal.
For synchronous/headless rendering without an event loop, use TUI.RenderOnce instead.
If the TUIST_LOG environment variable is set, Start automatically opens the specified file path for render debug logging (JSONL format). This is equivalent to calling SetDebugWriter with the opened file. The file is created/truncated on each Start call and closed on Stop.
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 and discards
// subsequent input until the next Start call.
Stop()
// SetInputPassthrough redirects stdin bytes to w instead of the
// normal input handler. This is used when a background command
// needs exclusive stdin access: the command reads from a pipe
// whose write end is w, while the terminal's single reader
// goroutine continues to be the sole consumer of os.Stdin.
// Pass nil to discard input (done automatically by Stop).
// Start restores normal input handling.
SetInputPassthrough(w io.Writer)
// 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 Context, 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 Context)
// KeyInterceptor, if set, is called before TextInput processes a key.
// Return true to consume the event (TextInput won't handle it).
// Return false to let TextInput handle it normally.
KeyInterceptor func(ctx Context, ev uv.KeyPressEvent) bool
// 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 Context, ev uv.KeyPressEvent) bool
HandleKeyPress implements Interactive.
func (*TextInput) HandlePaste ¶
func (t *TextInput) HandlePaste(ctx Context, ev uv.PasteEvent) bool
HandlePaste implements Pasteable.
func (*TextInput) InsertRune ¶
InsertRune inserts a rune at the current cursor position.
func (*TextInput) Render ¶
func (t *TextInput) Render(ctx Context) RenderResult
Render returns one or more lines: prompt + input, with cursor position.
func (*TextInput) SetFocused ¶
Source Files
¶
Directories
¶
| Path | Synopsis |
|---|---|
|
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. |
|
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. |
|
teav1
module
|
|
|
Package vt provides a virtual terminal that implements tuist.Terminal backed by midterm.Terminal.
|
Package vt provides a virtual terminal that implements tuist.Terminal backed by midterm.Terminal. |