Documentation
¶
Overview ¶
Package build constructs live tuilib components and a layout.Node tree from the YAML config. The output is owned by the screen, which holds the component pointers for state preservation across theme swaps.
Index ¶
- func AnchoredKey(cursor, key string) string
- func ApplyChrome(themes []theme.Theme, a *cfg.App) []theme.Theme
- func ApplyData(c *Component, data any, th theme.Theme)
- func ApplyRead(c *Component, rd activity.Read, data any, err error, th theme.Theme) bool
- func ApplyStreamLine(c *Component, line string, th theme.Theme)
- func BeginRead(c *Component) activity.Read
- func CursorKey(c *Component) string
- func Dispatch(c *Component, keys []string, label string, mode activity.Mode, settle int) (activity.Op, tea.Cmd)
- func Done(c *Component, op activity.Op, err error) activity.Outcome
- func EventlogItems(c *Component, items []any, th theme.Theme) []eventlog.Item
- func HasActivity(c *Component) bool
- func ItemInspector(title string, data any, th theme.Theme) inspector.Model
- func KeyedTableRows(c *Component, items []any, th theme.Theme) ([]table.KeyedRow, map[string]Selection)
- func MarkedCount(c *Component) int
- func NewRemoteEventlog(c *Component, th theme.Theme, shape remote.Shape[eventlog.Item])
- func NewRemoteTable(c *Component, th theme.Theme, shape remote.Shape[table.KeyedRow])
- func SetTitleSuffix(c *Component, suffix string)
- func SplitAnchoredKey(k string) (cursor, key string)
- func Substitute(s string, sel Selection) string
- func SubstituteAll(argv []string, sel Selection, inputs map[string]string) []string
- func SubstituteCursor(s string, cursor Selection) string
- func SubstituteScreen(s *cfg.Screen, components map[string]*cfg.Component, ...) (*cfg.Screen, map[string]*cfg.Component, map[string]*cfg.Source, error)
- func TableRows(c *Component, items []any, th theme.Theme) []table.Row
- type Component
- type Kind
- type Selection
- type Tree
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
func AnchoredKey ¶ added in v0.5.0
AnchoredKey is the key tuilib holds for an Anchored item.
func ApplyChrome ¶ added in v0.2.2
ApplyChrome writes the config's `app.glyphs` / `app.borders` overrides onto every theme in themes and returns the result.
Every theme, not just the one `app.theme` names: a palette is a choice of color, while glyphs and border shapes are a choice of vocabulary. Cycling palettes at runtime (`t`) walks the whole slice, and a cursor that turned back into "▸" partway through that cycle would read as a bug.
Unset fields are left alone rather than filled in here. tuilib resolves a zero border shape to its own default and a zero glyph via glyph.Default, so "unset" keeps exactly one meaning and it lives upstream — which is also why a palette that ships its own glyphs one day will keep the ones this config doesn't mention.
func ApplyData ¶
ApplyData maps a data source response onto a bound component. Component shape and binding fields decide how the response is consumed:
- list Cfg.Item (dot-path to display string)
- table Column.Value (dot-path per column)
- inspector field.Path (dot-path per field; static Value preserved when Path is empty)
The iterable root is selected via Cfg.Source's root path (already applied in screen.Model before this is called) so data is the already-rooted value. All three updates happen in place; tuilib's SetItems/SetRows/SetFields preserve cursor + filter + InitialDepth pre-expansion across refreshes.
func ApplyRead ¶ added in v0.5.0
ApplyRead applies a fetch an activity component stamped with BeginRead: the rows, keyed and carrying their data so every row's status is re-evaluated, or the error, which marks reads failing and stops the spinners that were waiting on a read. False means it was dropped: overtaken by a newer read, or failed.
func ApplyStreamLine ¶
ApplyStreamLine handles one line from a streaming source bound to a non-logview component. Logview-bound streams are handled directly in screen.handleStream (just Append the line); this function exists for the cases that need parsing + projection.
Two table modes, decided by whether Cfg.RowKey is set:
- Ring buffer (no RowKey): prepend each event as a new row, trimmed to Cfg.MaxRows. The live-tape pattern — chat, log overlays, recent-trades feeds.
- Keyed upsert (RowKey set): each event identifies the row it updates via Cfg.RowKey. Matching key → update in place; unseen key → append. The L1 order-book / status-grid pattern.
In both modes, lines that don't parse as JSON or that project to all-empty cells are skipped silently — that's how diagnostic / handshake frames ("(connecting to …)", an empty subscription_succeeded confirmation) drop out of the live data view. Color rules apply per cell exactly as they do on a fetch-driven table.
func BeginRead ¶ added in v0.5.0
BeginRead stamps a fetch for an activity component when it's issued, so the reply can be told apart from one a newer fetch overtook, and from a read that raced a write. See ApplyRead.
func CursorKey ¶ added in v0.5.0
CursorKey is the focused row's key, for a selection taken from the cursor rather than from marks. "" for components without keys.
func Dispatch ¶ added in v0.5.0
func Dispatch(c *Component, keys []string, label string, mode activity.Mode, settle int) (activity.Op, tea.Cmd)
Dispatch opens a claim on rows of c: they spin with label from now until Done says the work is over (Held) or the data takes over (Observed). settle is this claim's own allowance of unchanged reads (an Observed claim's `settle:`). The command is the spinner's first tick.
func Done ¶ added in v0.5.0
Done reports that a claim's request answered, err non-nil when it failed.
func EventlogItems ¶ added in v0.5.0
EventlogItems turns a page of source items into eventlog items: the key and mark plucked by dot-path, the text split into lines with the component's color rules applied per line, and the raw item kept as Data for enter and the inspector.
func HasActivity ¶ added in v0.5.0
HasActivity reports whether c shows row activity, and so applies its fetches through BeginRead / ApplyRead rather than ApplyData.
func ItemInspector ¶ added in v0.5.0
ItemInspector builds the inspector enter opens on an eventlog item when no action is bound to it: the raw item, every field, first level open. An eventlog draws only an item's text, so this is the way to reach the rest of it.
func KeyedTableRows ¶ added in v0.2.2
func KeyedTableRows(c *Component, items []any, th theme.Theme) ([]table.KeyedRow, map[string]Selection)
KeyedTableRows projects items exactly as TableRows does, then pairs each row with the identity at Cfg.MarkKey and the Selection that key resolves back to.
The map is what makes a mark usable: tuilib hands back keys, and every consumer downstream wants the row. Building it in the same pass that renders the cells is what keeps the two from disagreeing.
func MarkedCount ¶ added in v0.2.2
MarkedCount is how many rows a verb would act on, for a confirm string or a menu title. Mirrors MarkedSelections, so the number the user is shown and the set that runs cannot disagree.
func NewRemoteEventlog ¶ added in v0.5.0
NewRemoteEventlog binds c to its windowed source through tuilib's pkg/remote. Eventlog keeps pointing at the binding's embedded model, so everything that reads the eventlog reads this one.
func NewRemoteTable ¶ added in v0.5.0
NewRemoteTable turns c into a table bound to a remote source through tuilib's pkg/remote, which routes the viewport, the filter and the sort to the source and installs what comes back. c.Table keeps pointing at the embedded table, so everything that reads a table reads this one.
func SetTitleSuffix ¶ added in v0.5.0
SetTitleSuffix redraws c's pane title as its configured title plus suffix ("" to clear), for state the screen reports on the border such as "· reads failing".
func SplitAnchoredKey ¶ added in v0.5.0
SplitAnchoredKey recovers the cursor and component key from an AnchoredKey. A key with no separator is a plain (Seekable) key.
func Substitute ¶
Substitute resolves ${selection.*} and ${env.*} tokens in s against the given Selection. Used by push-site `bind:` resolution to turn a template like `${selection.Namespace}` into the focused row's actual namespace value before that value is passed into a destination source's parameter binder.
func SubstituteAll ¶
SubstituteAll runs ${selection.*}/${env.*}/${inputs.*} substitution on every entry in argv and returns a new slice. Used by action dispatch — the run argv references the focused row's selection AND any input values bound or entered moments before dispatch. Pass nil for inputs when there are none.
func SubstituteCursor ¶
SubstituteCursor resolves both ${cursor.*} (against the live cursor Selection) AND every other namespace (env, input-less) so a cursor bind template like `${cursor.Name}-${env.CLUSTER}` composes cleanly. Selection tokens pass through as literals — this is the runtime path, not the push-time one, and no push-site sel is in scope.
func SubstituteScreen ¶
func SubstituteScreen(s *cfg.Screen, components map[string]*cfg.Component, sources map[string]*cfg.Source, sel Selection, params map[string]string) (*cfg.Screen, map[string]*cfg.Component, map[string]*cfg.Source, error)
SubstituteScreen returns deep-copied Screen + Components + Entries with every ${selection*} token replaced from sel. Original config is left untouched, so each push can re-substitute against a fresh selection. Leaf-kind entries go through the same substitution so a child's URL / headers / body can reference the parent row.
When params is non-nil, every cloned leaf entry that declares `parameters:` ALSO has BindLeafParams called against the subset of params it actually declares. This is how the explicit push-site bind: block feeds into the destination screen's parameterized sources. Missing required params surface as an error so the caller (pushAction) can pop an alert instead of building a broken screen.
Operator entries (filter, sort, …) are cloned without substitution — they don't carry templated string fields and the expression language is bind-time, not screen-time.
Pass nil for params to skip parameter binding (single-screen New and initial multi-screen construction — no push site context). Parameterized sources in that path will be left with unresolved ${params.*} templates and will fail at fetch with a clear URL.
Types ¶
type Component ¶
type Component struct {
Cfg *cfg.Component
Kind Kind
// Exactly one of these is non-nil based on Kind.
List *list.Model
Table *table.Model
Logview *logview.Model
Tree *tree.Model
Inspector *inspector.Model
Textview *textview.Model
Eventlog *eventlog.Model
// Remote is set on a table bound to a windowed source, which tuilib's
// pkg/remote drives; Table then points at its embedded table. Every
// message goes through Remote.Update rather than Table.Update.
Remote *remote.Table
// AtNewest is set on a span table anchored at its newest row. tuilib
// puts a span table's cursor on its first row whenever a new answer
// lands, which for a view anchored at the newest is the wrong end and
// leaves it at the older edge, loading older rows by itself. The
// screen moves it to the newest row instead; landed records which
// answer it last did that for.
AtNewest bool
// RemoteLog is the eventlog's counterpart: an eventlog always binds a
// windowed source, and Eventlog points at this binding's embedded
// model once the screen has built it.
RemoteLog *remote.Eventlog
// StreamRows is the ring buffer used by KTable components bound to
// a streaming source. Newest events first; trimmed to Cfg.MaxRows
// (defaulted to 100 for streaming bindings) on every append. nil
// for non-streaming tables and non-table kinds.
StreamRows []any
// RowsByKey resolves a markable table's mark key back to the row it
// names. Rebuilt on every data application, alongside the rows
// themselves, so it can't describe a set the table no longer holds.
// nil unless Cfg.Markable is set on a table.
RowsByKey map[string]Selection
// contains filtered or unexported fields
}
Component is a live, themed component pointer plus the originating config — kept so SetTheme can rebuild the same component against a new theme.
func NewComponent ¶
NewComponent builds a Component from a config leaf and the active theme.
func (*Component) LandAtNewest ¶ added in v0.5.0
func (c *Component) LandAtNewest()
LandAtNewest puts a span table anchored at its newest row on that row each time a new answer lands: the first page, and the first page of a re-anchor after a filter. Called after every message the remote table handles; it does nothing between answers, so scrolling is left alone.
func (*Component) Rebuild ¶
Rebuild reconstructs the component against a new theme. Options carry the palette, so tuilib can't restyle a component in place (its rule 4); instead each component's State captures everything the user and the data did to it, and Restore puts that back on the fresh build. That includes the data itself, so a fetched table or list stays populated across a theme swap rather than waiting for a refetch a one-shot source will never make.
Cells already rendered keep the colors they were rendered with: a `theme:` token in a color rule shows the old palette until the next fetch re-renders the rows.
type Selection ¶
type Selection struct {
// String is the primary representation: a list's selected item, or a
// table's first cell. Drives bare ${selection}.
String string
// Cells are the full row of cells for a table source. Drives
// ${selection.N} (1-based) and ${selection.COLNAME}.
Cells []string
// Columns are the parallel column titles for Cells. Used to resolve
// ${selection.COLNAME} to the right cell.
Columns []string
// Key is the row's identity in its component (a table's mark_key, a
// list's item, a tree's node path), when it has one: what a claim
// targets.
Key string
}
Selection is the data captured from a source component (list or table) when an action binding fires. It seeds the ${selection} token in the pushed screen's config.
func ItemSelection ¶ added in v0.5.0
ItemSelection is the Selection for an eventlog item, which has no columns: bare ${selection} is the item's key, and every scalar field of its data is a column named by its dot-path, so ${selection.event} and ${selection.event_data.host} read straight off the raw item.
func MarkedSelections ¶ added in v0.2.2
MarkedSelections resolves a component's live selection into rows.
It follows tuilib's Selection() contract rather than reading marks directly: marked rows when there are any, otherwise the cursor row. That is deliberate — a hand-written "if marked, else cursor" branch is how a verb quietly acts on one row when the user marked six.
Returns nil for kinds that carry no row identity, and for a markable table whose keys have not been installed yet (no fetch has landed).
type Tree ¶
type Tree struct {
Root *cfg.Node
Components map[string]*Component
Order []string // names, in the order encountered while walking Root
}
Tree owns the live components and the layout config they're wired into. Components are keyed by the name used in the layout's `component:` refs; Order keeps an iteration order for fanout (Update/SetTheme/Help/etc.).
func Build ¶
Build instantiates every component defined in the config (lazily — only names referenced by the layout are built) and wires the layout tree.
func (*Tree) RenderNode ¶
RenderNode walks the config tree producing a live layout.Node tree. Component leaves are resolved by name through Tree.Components.