Documentation
¶
Overview ¶
Package tui provides shared utilities for terminal UI components.
notificationrow.go contains ActionRow and ActionBadge, the bottom-row UI for action buttons (refresh, send, save) and the notification reopen badge.
Index ¶
- Constants
- Variables
- func ClampCursor(cursor, maxIndex int) int
- func CopyToClipboard(text string) tea.Cmd
- func DistributeSpace(total int, entries []LayoutEntry) []int
- func FileItems() ([]string, error)
- func Hit(id string, msg tea.MouseMsg) bool
- func IsLeftClick(msg tea.MouseMsg) bool
- func MoveCursorDown(cursor, maxIndex int) int
- func MoveCursorUp(cursor int) int
- func NoFilesError() error
- func OverlayCenter(overlay string, width, height int) string
- func RenderBadge(text string, color lipgloss.TerminalColor) string
- func RenderChip(text string, variant ChipVariant, color lipgloss.TerminalColor) string
- func RenderChipBlock(text string, variant ChipVariant, color lipgloss.TerminalColor, ...) string
- func RunSelector(items []string, prompt string, emptyErr error, helpMessage ...string) (string, error)
- func RunWithSpinnerAfter(message string, task func() (any, error)) (any, error)
- func ScanMouseZones(view string) string
- func SyncViewport(vp *viewport.Model, content string, cursorLine, scrollMargin int)
- func ZoneBounds(id string) (startX, startY, endX, endY int, ok bool)
- func ZonePos(id string, msg tea.MouseMsg) (int, int)
- type ActionBadge
- type ActionItem
- type ActionRow
- func (r *ActionRow) BadgeListView() string
- func (r *ActionRow) BadgeView() string
- func (r *ActionRow) HandleKey(key string) (id string, handled bool)
- func (r *ActionRow) HandleMouse(msg tea.MouseMsg) (id string, handled bool)
- func (r *ActionRow) SetBadge(badge ActionBadge)
- func (r *ActionRow) SetItems(items []ActionItem)
- func (r *ActionRow) View() string
- func (r *ActionRow) ViewColumn(width, height int) string
- func (r *ActionRow) ViewList() string
- type ChipVariant
- type CopiedMsg
- type Dropdown
- func (d *Dropdown) Blur()
- func (d Dropdown) Cursor() int
- func (d *Dropdown) Expand()
- func (d Dropdown) Expanded() bool
- func (d *Dropdown) Focus()
- func (d Dropdown) Focused() bool
- func (d Dropdown) Init() tea.Cmd
- func (d *Dropdown) Select(index int)
- func (d Dropdown) Update(msg tea.Msg) (Dropdown, tea.Cmd)
- func (d Dropdown) Value() string
- func (d Dropdown) View() string
- type FilterableList
- func (f *FilterableList) ClearFilter()
- func (f *FilterableList) HasFilterValue() bool
- func (f *FilterableList) RenderItems() (string, int)
- func (f *FilterableList) RenderItemsWidth(maxWidth int) (string, int)
- func (f *FilterableList) SelectCurrent() (string, bool)
- func (f *FilterableList) UpdateInput(msg tea.Msg) tea.Cmd
- type FocusRing
- func (f *FocusRing) FocusByNumber(num int) bool
- func (f *FocusRing) HandleKey(key string) (consumed, quit bool)
- func (f *FocusRing) IsFocused(p *Panel) bool
- func (f *FocusRing) LeftFocused() bool
- func (f *FocusRing) Next()
- func (f *FocusRing) Prev()
- func (f *FocusRing) SetTyping(v bool)
- func (f *FocusRing) Typing() bool
- type LayoutEntry
- type MouseZone
- type Notification
- type NotificationCenter
- func (n *NotificationCenter) CopyText() string
- func (n *NotificationCenter) Enqueue(severity NotificationSeverity, message string) tea.Cmd
- func (n *NotificationCenter) HandleKey(key string) (handled bool, copyText string)
- func (n *NotificationCenter) HasLast() bool
- func (n *NotificationCenter) Render(width, height int) string
- func (n *NotificationCenter) RenderModal(width, height int) string
- func (n *NotificationCenter) Severity() NotificationSeverity
- func (n *NotificationCenter) ToggleLast() bool
- func (n *NotificationCenter) Update(msg tea.Msg) tea.Cmd
- func (n *NotificationCenter) Visible() bool
- type NotificationSeverity
- type Panel
- func (p *Panel) CanRender() bool
- func (p *Panel) EnsureVisible(line, colStart, colEnd int)
- func (p *Panel) GotoBottom()
- func (p *Panel) GotoTop()
- func (p *Panel) Resize(outerW, outerH int)
- func (p *Panel) ScrollPercent() float64
- func (p *Panel) SetContent(content, cacheKey string) bool
- func (p *Panel) SetHeader(header string)
- func (p *Panel) SyncContent(content string, cursorLine int)
- func (p *Panel) Update(msg tea.Msg) tea.Cmd
- func (p *Panel) View(focused bool) string
- func (p *Panel) Width() int
- type PanelSearch
- func (ps *PanelSearch) Active() bool
- func (ps *PanelSearch) CurrentMatch() int
- func (ps *PanelSearch) CycleNext()
- func (ps *PanelSearch) CyclePrev()
- func (ps *PanelSearch) Footer() string
- func (ps *PanelSearch) HandleKey(msg tea.KeyMsg) (stopped bool, confirmed bool, cmd tea.Cmd)
- func (ps *PanelSearch) MatchCount() int
- func (ps *PanelSearch) Query() string
- func (ps *PanelSearch) SetMatches(indices []int)
- func (ps *PanelSearch) SetQuery(value string)
- func (ps *PanelSearch) Start()
- func (ps *PanelSearch) Stop()
- type SelectorModel
- type TextInput
- type TextInputOpts
- type Toggle
Constants ¶
const ( // Navigation KeyUp = "up" KeyDown = "down" KeyCtrlP = "ctrl+p" // up KeyCtrlN = "ctrl+n" // down KeyLeft = "left" KeyRight = "right" // Vim-style navigation (use only when text input is inactive) KeyJ = "j" KeyK = "k" KeyH = "h" KeyL = "l" KeyG = "g" // first press of gg (go to top) KeyShiftG = "G" // go to bottom KeyYank = "ctrl+y" // single 'y' key suffers when the cursor is in TextInput KeyRefresh = "ctrl+r" KeySend = "ctrl+o" KeySave = "ctrl+s" KeySaveQuery = "ctrl+q" KeyCreateRequest = "ctrl+x" KeySlash = "/" // vim-style search trigger KeyAt = "@" // reopen or hide the most recent notification // Actions KeyEnter = "enter" KeyTab = "tab" KeySpace = " " KeyShiftTab = "shift+tab" // Reverse tab - navigate backward KeyQuit = "ctrl+c" // Force quit - always works KeyCancel = "esc" // Cancel/back - context aware )
Common Key binding and other constants single source of truth for all TUI components.
const ( // Column widths LeftPanelPct = 30 MinLeftPanelWidth = 26 MinRightPanelWidth = 32 // Right panel vertical split (percentage of contentHeight) DetailTopPct = 40 // Fixed-height rows (lines). Subtract these from the total // vertical budget before giving the remainder to scrollable // content areas. SearchBoxHeight = 3 // top border + input + bottom border StatusRowHeight = 1 // "N/M operations" line HelpBarHeight = 1 // full-width help bar below both columns )
const DefaultScrollMargin = 3
DefaultScrollMargin is the number of lines kept visible above/below the cursor when scrolling inside a viewport-backed list.
Variables ¶
var ( ColorPrimary = lipgloss.Color("4") // blue ColorSecondary = lipgloss.Color("5") // magenta ColorMuted = lipgloss.Color("8") // gray (bright black) ColorWarn = lipgloss.Color("3") // yellow ColorError = lipgloss.Color("1") // red ColorSuccess = lipgloss.Color("2") // green )
Semantic colors using basic ANSI (0-15). These are defined by the terminal's own color scheme, so they automatically adapt when the user switches between dark and light themes.
var ( // TitleStyle for section titles TitleStyle = lipgloss.NewStyle(). Bold(true). Foreground(ColorPrimary) // MutedTitleStyle for unfocused section titles MutedTitleStyle = lipgloss.NewStyle(). Bold(true). Foreground(ColorMuted) // SubtitleStyle for secondary titles SubtitleStyle = lipgloss.NewStyle(). Foreground(ColorSecondary) // HelpStyle for inline help text (muted gray) HelpStyle = lipgloss.NewStyle(). Foreground(ColorMuted) // HelpBarStyle for the bottom help bar — more noticeable than // HelpStyle but not as prominent as body text. HelpBarStyle = lipgloss.NewStyle(). Foreground(ColorMuted). Italic(true) // BorderStyle for boxes with borders BorderStyle = lipgloss.NewStyle(). Border(lipgloss.RoundedBorder()). BorderForeground(ColorMuted) // FocusedInputStyle for the actively focused input field FocusedInputStyle = BorderStyle.Padding(0, 1). BorderForeground(ColorPrimary) // InputStyle for unfocused/locked input fields InputStyle = BorderStyle.Padding(0, 1) // ActionChipStyle renders compact action controls. ActionChipStyle = lipgloss.NewStyle(). Bold(true). Padding(0, 1) // NotificationBadgeBaseStyle renders the @ reopen badge. NotificationBadgeBaseStyle = lipgloss.NewStyle(). Bold(true). Padding(0, 1) )
var BoxStyle = lipgloss.NewStyle(). Border(lipgloss.RoundedBorder()). BorderForeground(ColorMuted). Padding(1, 1)
BoxStyle for full-screen content containers with rounded border and padding.
var SpinnerFrames = []rune{'|', '/', '-', '\\'}
Functions ¶
func ClampCursor ¶
ClampCursor ensures cursor is within valid bounds [0, maxIndex]. Useful after filtering reduces the list size.
func CopyToClipboard ¶
CopyToClipboard returns a tea.Cmd that writes text to the system clipboard. The result is delivered as a CopiedMsg so the caller can show feedback or handle errors.
func DistributeSpace ¶
func DistributeSpace(total int, entries []LayoutEntry) []int
DistributeSpace splits total pixels among entries proportionally by weight, guaranteeing each entry at least MinSize. Rounding remainders go to the first entries. If total < sum of MinSizes, every entry still gets its MinSize (caller handles the overflow).
func IsLeftClick ¶
IsLeftClick reports whether msg is a left-button release event.
func MoveCursorDown ¶
MoveCursorDown increments cursor position, respecting upper bound. maxIndex is the maximum valid index (typically len(items)-1).
func MoveCursorUp ¶
MoveCursorUp decrements cursor position, respecting lower bound of 0.
func NoFilesError ¶
func NoFilesError() error
func OverlayCenter ¶
OverlayCenter places overlay in the center of the canvas.
func RenderBadge ¶
func RenderBadge(text string, color lipgloss.TerminalColor) string
RenderBadge creates a colored badge with the given foreground color.
func RenderChip ¶
func RenderChip(text string, variant ChipVariant, color lipgloss.TerminalColor) string
RenderChip renders compact UI labels used for passive badges, clickable buttons, and solid markers like the @ notification badge.
func RenderChipBlock ¶
func RenderChipBlock(text string, variant ChipVariant, color lipgloss.TerminalColor, width, height int) string
func RunSelector ¶
func RunSelector( items []string, prompt string, emptyErr error, helpMessage ...string, ) (string, error)
RunSelector runs a generic selector TUI with the given items and prompt. Returns the selected item, or empty string if cancelled. Returns emptyErr if no items are available.
func RunWithSpinnerAfter ¶
func ScanMouseZones ¶
Scan registers all marked regions at the root view without affecting layout.
func SyncViewport ¶
SyncViewport updates vp's content and adjusts its YOffset so that cursorLine stays visible with scrollMargin lines of padding.
func ZoneBounds ¶
ZoneBounds returns the stored bounds for the given zone ID.
Types ¶
type ActionBadge ¶
type ActionBadge struct {
Label string
Key string
Severity NotificationSeverity
Visible bool
}
type ActionRow ¶
type ActionRow struct {
// contains filtered or unexported fields
}
ActionRow renders a compact bottom-left row for future gql actions such as refresh, send, and save. It can also show an optional leading badge like the notification reopen marker.
func NewActionRow ¶
func NewActionRow(items ...ActionItem) ActionRow
func (*ActionRow) BadgeListView ¶
func (*ActionRow) HandleMouse ¶
func (*ActionRow) SetBadge ¶
func (r *ActionRow) SetBadge(badge ActionBadge)
func (*ActionRow) SetItems ¶
func (r *ActionRow) SetItems(items []ActionItem)
func (*ActionRow) ViewColumn ¶
type ChipVariant ¶
type ChipVariant string
const ( ChipVariantBadge ChipVariant = "badge" ChipVariantButton ChipVariant = "button" ChipVariantSolid ChipVariant = "solid" )
type CopiedMsg ¶
type CopiedMsg struct{ Err error }
CopiedMsg is sent after a clipboard write attempt. Err is nil on success.
type Dropdown ¶
type Dropdown struct {
Label string
Options []string
Selected int
// contains filtered or unexported fields
}
Not a full tea.Model — embed in a parent model and call Update/View explicitly, similar to Panel and Toggle.
func NewDropdown ¶
NewDropdown creates a Dropdown with the given label, options, and initially selected index. Out-of-range initial values clamp to 0.
func (*Dropdown) Blur ¶
func (d *Dropdown) Blur()
Blur removes focus and collapses the dropdown if expanded.
func (*Dropdown) Expand ¶
func (d *Dropdown) Expand()
Expand opens the dropdown and aligns the cursor with the selected option.
func (Dropdown) Update ¶
Update processes key messages based on the current expand state. Collapsed: Enter/Space expand the list. Expanded: arrow keys move the cursor, Enter/Space select, Esc collapses without changing.
type FilterableList ¶
type FilterableList struct {
Filtered []string
Cursor int
TextInput TextInput
// contains filtered or unexported fields
}
FilterableList is a headless data component that manages a string list with text-based filtering, cursor tracking, and item rendering. It does not implement tea.Model; embed it in a Bubble Tea model (like SelectorModel or apicaller.helperPane) to build interactive selectors on top.
func NewFilterableList ¶
func NewFilterableList( items []string, prompt, placeholder string, requireInput bool, ) FilterableList
func (*FilterableList) ClearFilter ¶
func (f *FilterableList) ClearFilter()
func (*FilterableList) HasFilterValue ¶
func (f *FilterableList) HasFilterValue() bool
func (*FilterableList) RenderItems ¶
func (f *FilterableList) RenderItems() (string, int)
RenderItems renders the filtered list with a cursor indicator on the selected item. It returns the rendered content and the line number of the cursor, which callers can pass to SyncViewport for scroll tracking.
func (*FilterableList) RenderItemsWidth ¶
func (f *FilterableList) RenderItemsWidth(maxWidth int) (string, int)
func (*FilterableList) SelectCurrent ¶
func (f *FilterableList) SelectCurrent() (string, bool)
func (*FilterableList) UpdateInput ¶
func (f *FilterableList) UpdateInput(msg tea.Msg) tea.Cmd
type FocusRing ¶
type FocusRing struct {
// contains filtered or unexported fields
}
FocusRing tracks which panel is focused and whether the user is in typing mode. Index -1 means the left panel (search/list) is focused; 0+ indexes into the panels slice.
func NewFocusRing ¶
NewFocusRing creates a ring starting on the left panel (index -1).
func (*FocusRing) FocusByNumber ¶
FocusByNumber jumps to the panel with the given Number field. Number 1 focuses the left panel. Returns false if no match found. Exits typing mode on switch.
func (*FocusRing) HandleKey ¶
TODO-gql: gqlexplorer.handleKey duplicates Esc/Tab/Enter/number-key logic instead of delegating to HandleKey. Consolidate in Phase 3 when multiple editable panels make the duplication painful.
HandleKey processes focus-related keys. Returns two bools:
- consumed: true if the key was handled (caller should not process it further)
- quit: true if Esc was pressed while already not typing (caller should exit)
func (*FocusRing) IsFocused ¶
IsFocused returns true if the given panel is the currently focused one.
func (*FocusRing) LeftFocused ¶
LeftFocused returns true when the left panel (search/list) has focus.
func (*FocusRing) Next ¶
func (f *FocusRing) Next()
Next advances focus to the next panel, wrapping from the last panel back to the left panel (-1). Exits typing mode on switch.
type LayoutEntry ¶
type MouseZone ¶
type MouseZone struct {
// contains filtered or unexported fields
}
MouseZone provides stable, collision-free zone IDs for one component or model. Actionable views should mark click targets with IDs derived from the same zone.
func NewMouseZone ¶
func NewMouseZone() MouseZone
NewMouseZone initializes the shared zone manager and returns a unique prefix that can be used to create stable IDs for one component instance.
type Notification ¶
type NotificationCenter ¶
type NotificationCenter struct {
// contains filtered or unexported fields
}
NotificationCenter manages one active notification plus the last dismissed message. Enqueue/Update integrate with Bubble Tea; callers can render either a compact notification or a top-overlay modal.
func NewNotificationCenter ¶
func NewNotificationCenter() NotificationCenter
func (*NotificationCenter) CopyText ¶
func (n *NotificationCenter) CopyText() string
func (*NotificationCenter) Enqueue ¶
func (n *NotificationCenter) Enqueue(severity NotificationSeverity, message string) tea.Cmd
Enqueue shows a notification and schedules expiry based on estimated reading time.
func (*NotificationCenter) HandleKey ¶
func (n *NotificationCenter) HandleKey(key string) (handled bool, copyText string)
HandleKey handles notification-local interactions. For ctrl+y it returns the displayed message so the parent can call CopyToClipboard.
func (*NotificationCenter) HasLast ¶
func (n *NotificationCenter) HasLast() bool
func (*NotificationCenter) Render ¶
func (n *NotificationCenter) Render(width, height int) string
func (*NotificationCenter) RenderModal ¶
func (n *NotificationCenter) RenderModal(width, height int) string
func (*NotificationCenter) Severity ¶
func (n *NotificationCenter) Severity() NotificationSeverity
func (*NotificationCenter) ToggleLast ¶
func (n *NotificationCenter) ToggleLast() bool
func (*NotificationCenter) Visible ¶
func (n *NotificationCenter) Visible() bool
type NotificationSeverity ¶
type NotificationSeverity string
const ( NotificationInfo NotificationSeverity = "info" NotificationWarn NotificationSeverity = "warn" NotificationError NotificationSeverity = "error" )
type Panel ¶
type Panel struct {
Number int
Header string
Label string
// contains filtered or unexported fields
}
Panel is a reusable bordered viewport box for right-side content panels. Parent owns layout sizing; Panel owns viewport state, border rendering, and content caching. Not a full tea.Model — embed in a parent model and call Update/View explicitly.
func (*Panel) CanRender ¶
CanRender returns false when outer dimensions are too small to fit the border frame, avoiding lipgloss rendering artifacts.
func (*Panel) EnsureVisible ¶
EnsureVisible scrolls both axes so the given line and column range are on screen.
func (*Panel) GotoBottom ¶
func (p *Panel) GotoBottom()
GotoBottom sets the viewport scroll position to the bottom.
func (*Panel) GotoTop ¶
func (p *Panel) GotoTop()
GotoTop resets the viewport scroll position to the top.
func (*Panel) ScrollPercent ¶
ScrollPercent returns the viewport's current scroll position as 0.0–1.0.
func (*Panel) SetContent ¶
SetContent updates viewport content. Returns true if the content was updated, false if the cacheKey matched and the update was skipped. Pass an empty cacheKey to force update every time.
func (*Panel) SyncContent ¶
SyncContent sets content and scrolls so cursorLine stays visible.
func (*Panel) Update ¶
Update forwards messages (scroll, mouse) to the inner viewport. Parent should call this only when this panel is focused.
type PanelSearch ¶
type PanelSearch struct {
// contains filtered or unexported fields
}
PanelSearch provides reusable search-input state, match navigation, key handling, and footer rendering. The caller owns match computation; PanelSearch owns the input, index list, cursor, and UI chrome.
func NewPanelSearch ¶
func NewPanelSearch() PanelSearch
NewPanelSearch creates a PanelSearch with up/down keys unbound so the host panel can use them for navigation.
func (*PanelSearch) Active ¶
func (ps *PanelSearch) Active() bool
Active returns true while the search input is open.
func (*PanelSearch) CurrentMatch ¶
func (ps *PanelSearch) CurrentMatch() int
CurrentMatch returns the index stored at the current match cursor, or -1 when there are no matches.
func (*PanelSearch) CycleNext ¶
func (ps *PanelSearch) CycleNext()
CycleNext moves to the next match, wrapping around.
func (*PanelSearch) CyclePrev ¶
func (ps *PanelSearch) CyclePrev()
CyclePrev moves to the previous match, wrapping around.
func (*PanelSearch) Footer ¶
func (ps *PanelSearch) Footer() string
Footer renders "Search(/) [input] N/M" or "" when inactive.
func (*PanelSearch) HandleKey ¶
HandleKey processes key messages while search is active. stopped=true means search ended (confirmed=true for Enter, false for Esc). The caller should re-run match computation after non-stop returns.
func (*PanelSearch) MatchCount ¶
func (ps *PanelSearch) MatchCount() int
MatchCount returns how many matches are currently tracked.
func (*PanelSearch) Query ¶
func (ps *PanelSearch) Query() string
Query returns the current raw search text.
func (*PanelSearch) SetMatches ¶
func (ps *PanelSearch) SetMatches(indices []int)
SetMatches replaces the match list and resets the cursor to 0.
func (*PanelSearch) SetQuery ¶
func (ps *PanelSearch) SetQuery(value string)
SetQuery replaces the current search text programmatically.
func (*PanelSearch) Start ¶
func (ps *PanelSearch) Start()
Start opens the search input and resets all match state.
func (*PanelSearch) Stop ¶
func (ps *PanelSearch) Stop()
Stop closes the search input and clears matches.
type SelectorModel ¶
type SelectorModel struct {
FilterableList
Selected string
Cancelled bool
// contains filtered or unexported fields
}
SelectorModel is the shared single-list picker engine for simple selection flows.
func NewSelector ¶
func NewSelector(items []string, prompt string, customHelp ...string) SelectorModel
func (*SelectorModel) Init ¶
func (m *SelectorModel) Init() tea.Cmd
func (*SelectorModel) View ¶
func (m *SelectorModel) View() string
type TextInput ¶
TextInput wraps a textinput.Model with reusable behaviors: cursor blinking, message forwarding, and bordered title rendering. Access the inner Model directly for Value(), Reset(), SetValue(), etc.
func NewFilterInput ¶
func NewFilterInput(opts TextInputOpts) TextInput
NewFilterInput creates a TextInput configured for filtering lists. Suggestion keys (up/down/ctrl+p/ctrl+n) are disabled so they can be used for list navigation instead.
type TextInputOpts ¶
type TextInputOpts struct {
Prompt string
Placeholder string
MinWidth int // minimum character width for the input field; 0 means use placeholder length
}
TextInputOpts configures a TextInput.
type Toggle ¶
Toggle is a reusable checkbox/toggle component for Bubble Tea TUIs. It renders as [x] label (on) or [ ] label (off) and responds to Space/Enter to flip the value. Only responds to keys when focused.
Not a full tea.Model — embed in a parent model and call Update/View explicitly, similar to Panel and TextInput.