build

package
v0.2.2 Latest Latest
Warning

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

Go to latest
Published: Sep 7, 2026 License: MIT Imports: 22 Imported by: 0

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

Constants

This section is empty.

Variables

This section is empty.

Functions

func ApplyChrome added in v0.2.2

func ApplyChrome(themes []theme.Theme, a *cfg.App) []theme.Theme

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

func ApplyData(c *Component, data any, th theme.Theme)

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 ApplyStreamLine

func ApplyStreamLine(c *Component, line string, th theme.Theme)

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 ApplyWindow added in v0.2.1

func ApplyWindow(c *Component, items []any, offset, total int, th theme.Theme)

ApplyWindow installs one window of a larger remote set: items are the logical rows [offset, offset+len(items)) of a set total rows long, with total < 0 meaning the source can't say.

This is the windowed counterpart to ApplyData, and it is deliberately separate rather than a branch inside it. SetRows means "these are all the rows there are" — it clears any window and lets the table filter and sort what it holds. Routing a window through it would quietly turn a 30,000-row set into a 100-row one.

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

func MarkedCount(c *Component) int

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 Substitute

func Substitute(s string, sel Selection) string

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

func SubstituteAll(argv []string, sel Selection, prompts map[string]string) []string

SubstituteAll runs ${selection.*}/${env.*}/${prompt.*} 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 prompt values entered moments before dispatch. Pass nil for prompts when there were none.

func SubstituteCursor

func SubstituteCursor(s string, cursor Selection) string

SubstituteCursor resolves both ${cursor.*} (against the live cursor Selection) AND every other namespace (env, prompt-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 (tryPush) 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.

func TableRows added in v0.2.1

func TableRows(c *Component, items []any, th theme.Theme) []table.Row

TableRows projects items through a table component's columns — dot-path pluck per column, then color rules. Shared by the whole-set path (applyTable) and the windowed path (ApplyWindow) so a window's rows are built exactly like any other row.

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

	// 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
}

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

func NewComponent(c *cfg.Component, th theme.Theme) (*Component, error)

NewComponent builds a Component from a config leaf and the active theme.

func (*Component) Rebuild

func (c *Component) Rebuild(th theme.Theme)

Rebuild reconstructs the component against a new theme, preserving as much in-flight state as the component exposes accessors for.

type Kind

type Kind int

Kind enumerates the supported leaf component types.

const (
	KList Kind = iota
	KTable
	KLogview
	KTree
	KInspector
	KTextview
)

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
}

Selection is the data captured from a source component (list or table) when an on_key binding fires. It seeds the ${selection} token in the pushed screen's config.

func MarkedSelections added in v0.2.2

func MarkedSelections(c *Component) []Selection

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

func Build(root *cfg.Node, defs map[string]*cfg.Component, th theme.Theme) (*Tree, error)

Build instantiates every component defined in the config (lazily — only names referenced by the layout are built) and wires the layout tree.

func (*Tree) All

func (t *Tree) All() []*Component

All returns components in walk order — handy for screen-level fanout.

func (*Tree) RenderNode

func (t *Tree) RenderNode() layout.Node

RenderNode walks the config tree producing a live layout.Node tree. Component leaves are resolved by name through Tree.Components.

Jump to

Keyboard shortcuts

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