app

package
v0.18.5 Latest Latest
Warning

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

Go to latest
Published: Sep 23, 2026 License: MIT Imports: 6 Imported by: 0

Documentation

Overview

Package app provides a terminal application shell on top of Bubble Tea v2: chrome (header/footer), an overlay stack, keybindings mapped to named commands, and quit handling. It hosts a Content screen and routes input following the conventions shared by the shopware-cli TUIs: overlays capture input and close by returning nil plus an Emit(result) command; ctrl+c runs the quit command, which screens may override.

The package is a trimmed copy of lattice's app shell (github.com/shyim/lattice/app) with theme, focus, jobs, toasts, and drawer support stripped — colors come from the flat internal/tui package instead.

Index

Constants

View Source
const (
	CmdQuit       = "app.quit"
	CmdPopOverlay = "app.overlay.pop"
)

Built-in command IDs.

Variables

This section is empty.

Functions

func ApplySizeMsg

func ApplySizeMsg(c Content, msg tea.Msg, width, mainHeight int)

ApplySizeMsg is a convenience for Content.Update to react to WindowSizeMsg after chrome has already been accounted for via Context.MainHeight.

func As

func As[T any](msg tea.Msg) (T, bool)

As type-asserts a Bubble Tea message.

if res, ok := app.As[versionPickedMsg](msg); ok { ... }

func At

func At[T any](in []T, i int) (T, bool)

At returns the element at i, or the zero value if out of range.

func CenterPanel

func CenterPanel(width, height int, content string) string

CenterPanel places content centered in width×height, for overlays that replace the whole screen.

func Clamp

func Clamp[T cmp.Ordered](v, lo, hi T) T

Clamp returns v constrained to [lo, hi]. If lo > hi the bounds are swapped.

func ContentAs

func ContentAs[T Content](c Content) (T, bool)

ContentAs type-asserts Content after Update.

func Emit

func Emit(msg tea.Msg) tea.Cmd

Emit returns a Cmd that delivers msg on the next Update. Overlays use it to hand results to the hosting Content while closing (return nil, Emit(result)).

func Filter

func Filter[T any](in []T, keep func(T) bool) []T

Filter returns a new slice of elements for which keep returns true.

func FilterIndex

func FilterIndex[T any](in []T, keep func(T) bool) []int

FilterIndex returns indices of elements for which keep returns true. Used by palette/picker style UIs that track filtered row indexes.

func Frame

func Frame(width, height int, header, main, footer string) string

Frame stacks header, main, and footer into a fixed terminal height. Main is padded or truncated to fill the middle region.

func Index

func Index[T comparable](in []T, v T) int

Index returns the first index of v, or -1.

func IndexFunc

func IndexFunc[T any](in []T, f func(T) bool) int

IndexFunc returns the first index where f is true, or -1.

func JoinHorizontal

func JoinHorizontal(gap int, blocks ...string) string

JoinHorizontal joins blocks side by side with a gap, top-aligned.

func KeyString

func KeyString(msg tea.KeyPressMsg) string

KeyString normalizes a key press for matching. With Caps Lock on, terminals send "ctrl+C" instead of "ctrl+c"; lower-casing keeps shortcuts working.

func Map

func Map[T, U any](in []T, f func(T) U) []U

Map applies f to every element and returns the results.

func NotifySize

func NotifySize(c Content, width, height int)

NotifySize calls SetSize if c implements Sizeable (non-recursive). Prefer PropagateSize for app content trees.

func PropagateSize

func PropagateSize(c Content, width, height int)

PropagateSize notifies Sizeable and SizePropagator along the content tree. Container content implements SizePropagator to recurse into children.

func Ptr

func Ptr[T any](v T) *T

Ptr returns a pointer to v. Useful for optional Options fields:

app.New(app.Options{AltScreen: app.Ptr(false), FullscreenOverlay: app.Ptr(true)})

Types

type App

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

App is a Bubble Tea model that hosts content, chrome, and overlays.

func New

func New(opts Options) *App

New constructs an App shell. The returned value implements tea.Model.

func (*App) Commands

func (a *App) Commands() *CommandRegistry

Commands returns the command registry.

func (*App) Content

func (a *App) Content() Content

Content returns the current content.

func (*App) Context

func (a *App) Context() Context

Context builds the current frame context.

func (*App) Init

func (a *App) Init() tea.Cmd

Init implements tea.Model.

func (*App) Keys

func (a *App) Keys() *KeyMap

Keys returns a pointer to the keymap for additional Bind calls.

func (*App) OverlayOpen

func (a *App) OverlayOpen() bool

OverlayOpen reports whether an overlay is active.

func (*App) PopOverlay

func (a *App) PopOverlay()

PopOverlay closes the top overlay if any.

func (*App) PushOverlay

func (a *App) PushOverlay(o Overlay) tea.Cmd

PushOverlay shows an overlay on top (captures input).

func (*App) RegisterCommand

func (a *App) RegisterCommand(c Command, keys ...string)

RegisterCommand adds a command and optional key binding. Registering with a built-in ID (e.g. CmdQuit) replaces the built-in behavior.

func (*App) Run

func (a *App) Run() (tea.Model, error)

Run is a convenience for tea.NewProgram(a).Run().

func (*App) RunCommand

func (a *App) RunCommand(id string) tea.Cmd

RunCommand executes a command by ID.

func (*App) SetContent

func (a *App) SetContent(c Content)

SetContent replaces the main content without initializing it. When the terminal size is already known the new content is sized immediately — otherwise it would render unsized until the next resize event.

func (*App) SetStatus

func (a *App) SetStatus(s string)

SetStatus sets a short status string (available to chrome helpers).

func (*App) Size

func (a *App) Size() (width, height int)

Size returns the last known terminal size.

func (*App) Status

func (a *App) Status() string

Status returns the status string.

func (*App) SwapContent

func (a *App) SwapContent(c Content) tea.Cmd

SwapContent replaces the main content and returns its Init cmd. Use this for phase changes (install wizard → dashboard).

func (*App) TopOverlay

func (a *App) TopOverlay() Overlay

TopOverlay returns the top overlay, or nil when none is open.

func (*App) Update

func (a *App) Update(msg tea.Msg) (tea.Model, tea.Cmd)

Update implements tea.Model.

func (*App) View

func (a *App) View() tea.View

View implements tea.Model.

type Binding

type Binding struct {
	Keys    []string
	Command string
	Help    string
	// Global bindings are always checked, even when an overlay is open.
	Global bool
}

Binding maps one or more key strings (as in tea.KeyPressMsg.String()) to a command ID.

func DefaultQuitBindings

func DefaultQuitBindings() []Binding

DefaultQuitBindings returns ctrl+c → app.quit. Unlike plain dashboards, the shopware-cli TUIs contain type-to-filter inputs, so "q" stays a character.

type Command

type Command struct {
	// ID is a stable command identifier (e.g. "app.quit").
	ID string
	// Title is a short human label for help / palettes.
	Title string
	// Run executes the command. May return a tea.Cmd.
	// The App pointer is the host shell.
	Run func(a *App) tea.Cmd
}

Command is a named action the app can run from a keybinding or palette.

type CommandRegistry

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

CommandRegistry holds runnable commands by ID.

func NewCommandRegistry

func NewCommandRegistry() *CommandRegistry

NewCommandRegistry creates an empty registry.

func (*CommandRegistry) All

func (r *CommandRegistry) All() []Command

All returns registered commands.

func (*CommandRegistry) Get

func (r *CommandRegistry) Get(id string) (Command, bool)

Get returns a command by ID.

func (*CommandRegistry) Register

func (r *CommandRegistry) Register(c Command)

Register adds or replaces a command. Re-registering a built-in (e.g. CmdQuit) overrides its behavior — that is how screens intercept quit to cancel a running job instead of exiting.

type Content

type Content interface {
	Init() tea.Cmd
	Update(msg tea.Msg) (Content, tea.Cmd)
	View(ctx Context) string
}

Content is the main screen body hosted inside App. Contents return string views; App wraps them in chrome + tea.View.

type ContentFunc

type ContentFunc struct {
	OnInit   func() tea.Cmd
	OnUpdate func(msg tea.Msg) (Content, tea.Cmd)
	OnView   func(ctx Context) string
}

ContentFunc adapts functions to Content.

func (ContentFunc) Init

func (c ContentFunc) Init() tea.Cmd

Init implements Content.

func (ContentFunc) Update

func (c ContentFunc) Update(msg tea.Msg) (Content, tea.Cmd)

Update implements Content.

func (ContentFunc) View

func (c ContentFunc) View(ctx Context) string

View implements Content.

type Context

type Context struct {
	// Width and Height are the full terminal dimensions.
	Width  int
	Height int
	// MainHeight is the rows available to Content after header/footer chrome.
	MainHeight int
	// OverlayOpen reports whether a modal overlay currently captures input.
	OverlayOpen bool
	// Chrome holds the measured header/main/footer regions.
	Chrome Region
}

Context is passed to header/footer/content helpers each frame.

type FuncOverlay

type FuncOverlay struct {
	Name string
	// OnInit optional.
	OnInit func() tea.Cmd
	// OnUpdate required. Return next=nil to close.
	OnUpdate func(msg tea.Msg) (next Overlay, cmd tea.Cmd)
	// OnView required; receives the terminal size.
	OnView func(width, height int) string
}

FuncOverlay adapts functions into an Overlay (handy for quick dialogs).

func (*FuncOverlay) ID

func (f *FuncOverlay) ID() string

ID implements Overlay.

func (*FuncOverlay) Init

func (f *FuncOverlay) Init() tea.Cmd

Init implements Overlay.

func (*FuncOverlay) Update

func (f *FuncOverlay) Update(msg tea.Msg) (Overlay, tea.Cmd)

Update implements Overlay.

func (*FuncOverlay) View

func (f *FuncOverlay) View(width, height int) string

View implements Overlay.

type Harness

type Harness struct {
	App *App
}

Harness drives an App without a TTY for tests: construct with a size, Send messages, and assert on View output. The Init command is executed during construction; commands returned by later Update calls are NOT executed automatically — use SendCmd when a test needs the resulting message.

func NewHarness

func NewHarness(opts Options, width, height int) *Harness

NewHarness builds an App from opts, runs Init, and delivers the initial window size.

func (*Harness) Send

func (h *Harness) Send(msgs ...tea.Msg) tea.Cmd

Send delivers messages to the App in order and returns the last command.

func (*Harness) SendCmd

func (h *Harness) SendCmd(cmd tea.Cmd)

SendCmd resolves cmd (if any) and feeds every produced message back into the App, following tea.Batch trees. Depth and command-count limits prevent recurring commands such as spinner ticks from making the harness loop forever.

func (*Harness) View

func (h *Harness) View() string

View renders the current frame content.

type Host

type Host interface {
	Size() (width, height int)
	Commands() *CommandRegistry
	Keys() *KeyMap
	Status() string
	SetStatus(s string)
	Content() Content
	SetContent(c Content)
	SwapContent(c Content) tea.Cmd
	PushOverlay(o Overlay) tea.Cmd
	PopOverlay()
	OverlayOpen() bool
	RunCommand(id string) tea.Cmd
	RegisterCommand(c Command, keys ...string)
}

Host is the shell surface that Content and Overlay implementations use to drive the app without depending on private fields.

*App implements Host. Screens hold a Host so they can open modals, swap phases, and run commands:

type dashboard struct {
    host app.Host
}
func (d *dashboard) openPicker() tea.Cmd {
    return d.host.PushOverlay(&picker)
}

type KeyMap

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

KeyMap is a set of bindings.

func NewKeyMap

func NewKeyMap(bindings ...Binding) KeyMap

NewKeyMap creates a keymap.

func (*KeyMap) Bind

func (k *KeyMap) Bind(b ...Binding)

Bind appends bindings.

func (KeyMap) HelpLines

func (k KeyMap) HelpLines() [][2]string

HelpLines returns key/help pairs for display.

func (KeyMap) Match

func (k KeyMap) Match(key string, overlayOpen bool) (commandID string, ok bool)

Match returns the command ID for a key press, if any. When overlayOpen is true, only Global bindings match. Keys are compared case-insensitively (see KeyString).

type Options

type Options struct {
	// Content is the main screen (required for a useful app).
	Content Content
	// Header renders above the main content each frame (optional).
	Header func(ctx Context) string
	// Footer renders below the main content each frame (optional).
	Footer func(ctx Context) string
	// WindowTitle sets the terminal window title when non-empty.
	WindowTitle string
	// WindowTitleFunc overrides WindowTitle each frame when set.
	WindowTitleFunc func(ctx Context) string
	// QuitKeys binds extra keys to the quit command (ctrl+c is always bound
	// unless DisableDefaultKeys is set).
	QuitKeys []string
	// DisableDefaultKeys skips the default ctrl+c quit binding.
	DisableDefaultKeys bool
	// Bindings are extra key bindings.
	Bindings []Binding
	// FullscreenOverlay when true (default) replaces the entire view with the
	// top overlay. When false, the overlay replaces only the main region and
	// the header/footer chrome stays visible.
	FullscreenOverlay *bool
	// AltScreen enables the alternate screen buffer (default true).
	AltScreen *bool
	// Mouse enables cell-motion mouse when true.
	Mouse bool
}

Options configure a new App shell.

type Overlay

type Overlay interface {
	// Init is called once when the overlay is pushed.
	Init() tea.Cmd
	// Update handles a message. Returning a nil overlay closes it.
	// Prefer Emit(result) for outcomes instead of getters after close.
	Update(msg tea.Msg) (next Overlay, cmd tea.Cmd)
	// View renders the overlay for the given terminal size (full screen).
	View(width, height int) string
	// ID optionally names the overlay for debugging; may be empty.
	ID() string
}

Overlay is a modal layer that captures input until it closes.

Contract:

  • Update returns the next overlay; nil means "closed"
  • Results are delivered with Emit(resultMsg) so the parent handles outcomes
  • View receives the terminal size for full-screen layout

type OverlayStack

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

OverlayStack is a LIFO stack of overlays. The top overlay receives input.

func (*OverlayStack) Len

func (s *OverlayStack) Len() int

Len returns the number of open overlays.

func (*OverlayStack) Open

func (s *OverlayStack) Open() bool

Open reports whether any overlay is showing.

func (*OverlayStack) Pop

func (s *OverlayStack) Pop()

Pop removes the top overlay if any.

func (*OverlayStack) Push

func (s *OverlayStack) Push(o Overlay) tea.Cmd

Push adds an overlay on top and returns its Init cmd.

func (*OverlayStack) Top

func (s *OverlayStack) Top() Overlay

Top returns the top overlay, or nil.

func (*OverlayStack) Update

func (s *OverlayStack) Update(msg tea.Msg) (cmd tea.Cmd, closed bool)

Update routes a message to the top overlay. If the overlay returns nil, it is popped and closed reports true.

func (*OverlayStack) View

func (s *OverlayStack) View(width, height int) string

View returns the top overlay view for the given size, or empty if none.

type Region

type Region struct {
	Header int
	Main   int
	Footer int
	Width  int
	Height int
}

Region sizes for a frame layout.

func ComputeRegion

func ComputeRegion(width, height int, header, footer string) Region

ComputeRegion allocates vertical space: header and footer keep their natural height (clamped), main gets the remainder.

type SizePropagator

type SizePropagator interface {
	PropagateSize(width, height int)
}

SizePropagator is implemented by container Content (tabs, split, phases, …) to recurse size into children. Keeps the host free of concrete container types.

type Sizeable

type Sizeable interface {
	SetSize(width, height int)
}

Sizeable content can receive terminal size updates when chrome is measured.

func (d *dashboard) SetSize(w, h int) {
    d.listW, d.listH = w/3, h
}

type StaticContent

type StaticContent struct {
	Text string
}

StaticContent shows a fixed string (useful for tests and placeholders).

func (StaticContent) Init

func (s StaticContent) Init() tea.Cmd

Init implements Content.

func (StaticContent) Update

func (s StaticContent) Update(tea.Msg) (Content, tea.Cmd)

Update implements Content.

func (StaticContent) View

func (s StaticContent) View(Context) string

View implements Content.

Jump to

Keyboard shortcuts

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