ux

package
v0.36.3 Latest Latest
Warning

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

Go to latest
Published: Aug 5, 2026 License: Apache-2.0 Imports: 23 Imported by: 0

Documentation

Overview

Package ux is the bubbletea front-end for genspec-tui: a single root Model composing a header line, three panels (source tree, spec, diagnostics), and a status/help line.

Structure borrows from fredbi/git-janitor — one root model owning panel values, an enum-based key dispatch, mouse focus/scroll, and a recalcLayout that distributes the terminal size across panels.

What lives where

The panels (panels) and the modal overlays (help, options) are separate packages because each owns its own state and needs almost nothing from the model. What is left here does not split that way: the cross-reference layer alone reaches into all three panels, both spec indexes, the source index and the status line, so it is spread across files rather than hidden behind a package boundary it could not honestly keep.

  • model.go the Model struct, its lifecycle (New/Close/Init/Update/View), layout and focus routing
  • keys.go key dispatch: global bindings, per-pane handlers, the search input, the editor
  • crossref.go follow modes, go-to-definition, find-references, gutters, the spec render
  • refcycle.go the find-references walk, as its own small type
  • source.go the open file: loading, saving, syntax runs, diagnostic marks, buffer coordinates
  • scanflow.go running a scan and absorbing its result, the file watcher's debounce, transient notices
  • chrome.go the header, status line and follow badge
  • mouse.go click-to-focus and wheel scrolling
  • overlay.go the Overlay contract the modals satisfy, and their precedence

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

This section is empty.

Types

type Model

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

Model is the root bubbletea model.

func New

func New(cfg codescan.Options) *Model

New builds the root model around a ready-made scan config; the source tree browses cfg.WorkDir.

Taking the whole Options rather than a handful of arguments means a new CLI flag needs no signature change here — and the boolean knobs the overlay drives are the same struct the caller filled in.

A file watcher is started best-effort — if it can't initialize, live reload is simply unavailable and the user falls back to `r` (manual rescan).

func (*Model) Close

func (m *Model) Close()

Close releases the file watcher.

Call after the program exits.

func (*Model) Init

func (m *Model) Init() tea.Cmd

Init implements tea.Model: kick off the initial whole-scope scan and, if a watcher is available, begin listening for source changes.

func (*Model) Update

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

Update implements tea.Model.

func (*Model) View

func (m *Model) View() string

View implements tea.Model.

type Overlay added in v0.36.3

type Overlay interface {
	SetSize(w, h int)
	IsOpen() bool
	HandleKey(msg tea.KeyMsg) tea.Cmd
	View() string
}

Overlay is a modal layer: it covers the base UI, captures every key until it dismisses itself, and renders a self-contained box the model draws over everything else.

Like the panels, an overlay is a concrete type the model owns and drives — it is deliberately NOT a tea.Model. An overlay shaped as a tea.Model has to be handed the root model just to hand it back, and ends up deciding app policy (quitting) on the root's behalf. The panels never needed that, and neither do these.

Whatever an overlay wants to happen when it closes, it records; the model decides what applying it means.

type RefCycle added in v0.36.3

type RefCycle struct {
	Anchor string          // the definition pointer whose uses are being cycled
	Sites  []index.RefSite // its reference sites, ordered by rendered line
	Cursor int             // which site we are parked on
	Status string          // persistent status line while a cycle is active
}

RefCycle is the find-references walk (F3 / shift+F3): the definition whose uses are being visited, its reference sites in rendered-line order, and which one the cursor is parked on.

It is valid only for the render it was computed against — every line number in it is a line of THAT render — so a rescan or a format toggle drops it.

func (*RefCycle) Describe added in v0.36.3

func (c *RefCycle) Describe() string

Describe renders the status line: which site of how many, of what, pointing where.

func (*RefCycle) ParkedOn added in v0.36.3

func (c *RefCycle) ParkedOn(line int) bool

ParkedOn reports whether a cycle is under way and line is still the site it last moved to.

This is what makes "F3 repeatedly" walk one definition's uses: move the cursor off the site and the next step re-anchors on the node you are now on, rather than chasing the definition of whatever it last landed on.

func (*RefCycle) Reset added in v0.36.3

func (c *RefCycle) Reset()

Reset drops the cycle.

Called whenever the render it was computed against is replaced (rescan, format toggle) or the user moves on.

func (*RefCycle) Site added in v0.36.3

func (c *RefCycle) Site() index.RefSite

Site is the reference site the cycle is currently parked on.

func (*RefCycle) Start added in v0.36.3

func (c *RefCycle) Start(anchor string, sites []index.RefSite, dir int)

Start anchors a fresh cycle, entering at the first site for a forward step and the last for a backward one.

func (*RefCycle) Step added in v0.36.3

func (c *RefCycle) Step(dir int)

Step moves to the next (dir +1) or previous (-1) site, wrapping at both ends.

type ScanState added in v0.36.3

type ScanState struct {
	Running bool
	Spin    spinner.Model
	Elapsed time.Duration

	NumPaths int
	NumDefs  int

	JSON string
	YAML string

	Diags []grammar.Diagnostic
	Err   error // hard error from the last codescan.Run, shown in the diag pane
}

ScanState is what the last scan produced, plus whether another is in flight.

It has exactly two writers — startScan, when one begins, and absorbScan, when its result lands. Everything else only reads it: the header and status line are projections of it, and the cross-ref layer renders from its JSON or YAML. Keeping it in one struct is what makes that one-way traffic visible.

func NewScanState added in v0.36.3

func NewScanState() ScanState

NewScanState builds the idle state, with the spinner the header shows while a scan runs.

func (*ScanState) Body added in v0.36.3

func (s *ScanState) Body(yamlFmt bool) string

Body is the rendered spec in the requested format.

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

SearchBox is the spec-pane search prompt: the text input, and whether it currently holds the keyboard.

While it is active it takes the status line and every key, which is why "is the user typing a query" has to be answerable from outside — but nothing outside needs to know how the input works.

func NewSearchBox added in v0.36.3

func NewSearchBox() SearchBox

NewSearchBox builds a closed search prompt.

func (*SearchBox) Active added in v0.36.3

func (s *SearchBox) Active() bool

Active reports whether the prompt is capturing input.

func (*SearchBox) Close added in v0.36.3

func (s *SearchBox) Close()

Close dismisses the prompt, releasing the keyboard.

func (*SearchBox) Open added in v0.36.3

func (s *SearchBox) Open() tea.Cmd

Open clears the prompt and gives it the keyboard.

Always from empty: a search is started to look for something, not to resume the last one.

func (*SearchBox) Query added in v0.36.3

func (s *SearchBox) Query() string

Query is what the user has typed.

func (*SearchBox) Update added in v0.36.3

func (s *SearchBox) Update(msg tea.Msg) tea.Cmd

Update forwards a key to the input.

func (*SearchBox) View added in v0.36.3

func (s *SearchBox) View() string

View renders the prompt, which occupies the status line while it is active.

type SourceWatch added in v0.36.3

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

SourceWatch is the live-reload plumbing: the file watcher, the channel its events arrive on, and the debounce generation that lets a newer change discard a timer already in flight.

func NewSourceWatch added in v0.36.3

func NewSourceWatch(dir string) SourceWatch

NewSourceWatch starts a watcher on dir, best-effort.

If it cannot initialize, live reload is simply unavailable and the user falls back to `r` (manual rescan) — so the error is deliberately swallowed rather than failing the whole TUI.

func (*SourceWatch) Bump added in v0.36.3

func (s *SourceWatch) Bump() int

Bump opens a new debounce window, invalidating any timer already running.

func (*SourceWatch) Close added in v0.36.3

func (s *SourceWatch) Close()

Close releases the watcher.

func (*SourceWatch) Current added in v0.36.3

func (s *SourceWatch) Current(gen int) bool

Current reports whether gen is still the newest debounce window — false means a later change superseded it.

func (*SourceWatch) Events added in v0.36.3

func (s *SourceWatch) Events() <-chan struct{}

Events is the channel a change arrives on.

func (*SourceWatch) Listening added in v0.36.3

func (s *SourceWatch) Listening() bool

Listening reports whether a watcher came up and events can be waited on.

Directories

Path Synopsis
Package gadgets holds small, self-contained TUI helpers.
Package gadgets holds small, self-contained TUI helpers.
Package help is the keymap overlay: a scrollable modal listing every binding, grouped by the context it applies in.
Package help is the keymap overlay: a scrollable modal listing every binding, grouped by the context it applies in.
Package key normalizes tea.KeyMsg values into a small enum of named bindings, so the model dispatches on a plain string switch rather than a key-binding library.
Package key normalizes tea.KeyMsg values into a small enum of named bindings, so the model dispatches on a plain string switch rather than a key-binding library.
Package options is the scanner-options overlay: a scrollable modal of boolean toggles bound directly to the codescan.Options the app scans with.
Package options is the scanner-options overlay: a scrollable modal of boolean toggles bound directly to the codescan.Options the app scans with.
Package panels holds the three scrollable sub-panels of the genspec-tui layout: the source tree (left), the generated spec (right) and the diagnostics (bottom).
Package panels holds the three scrollable sub-panels of the genspec-tui layout: the source tree (left), the generated spec (right) and the diagnostics (bottom).
Package theme holds the lipgloss styles shared by the model and its panels: a rounded-border panel box (bright when focused, dim otherwise), a panel title, and the status line.
Package theme holds the lipgloss styles shared by the model and its panels: a rounded-border panel box (bright when focused, dim otherwise), a panel title, and the status line.

Jump to

Keyboard shortcuts

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