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 and help line.
Structure borrows from fredbi/git-janitor - one root model owning panel values, an enum-based key dispatch, mouse focus or 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 and 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 ¶
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 ¶
Init implements tea.Model.
It kicks off the initial whole-scope scan and, when a watcher is available, begins listening for source changes.
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 behind F3 and shift+F3.
It holds 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
Describe renders the status line: which site of how many, of what, pointing where.
func (*RefCycle) ParkedOn ¶ added in v0.36.3
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
Site is the reference site the cycle is currently parked on.
type ScanState ¶ added in v0.36.3
type ScanState struct {
Running bool
Spin spinner.Model
Elapsed time.Duration
// What the run cost to produce, for the overlay m opens. Read from the runtime around the work itself, so it is
// carried here rather than derived: once the scan has returned, nobody can recover it.
Cost scan.Cost
NumPaths int
NumDefs int
JSON string
// YAML is the same document for the other view, rendered on demand: it costs more than the JSON it is made from,
// and most sessions never open it. Empty until the YAML view is first asked for, and dropped by the next scan.
YAML string
// YAMLPending says a conversion is in flight, so the view can say so and a second toggle does not start another.
YAMLPending bool
// Gen counts the scans, so a conversion that lands after a rescan is discarded instead of shown against the
// document that replaced the one it was made from.
Gen int
Diags []grammar.Diagnostic
Err error // hard error from the last codescan.Run, shown in the diag pane
}
ScanState holds 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 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.
type SearchBox ¶ added in v0.36.3
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) 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
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.
type SourceWatch ¶ added in v0.36.3
type SourceWatch struct {
// contains filtered or unexported fields
}
SourceWatch is the live-reload plumbing.
It holds 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.
type Startup ¶ added in v0.36.4
type Startup struct {
// Options holds the settings the first scan runs with. Everything in it is live afterwards: the options overlay
// writes to this very struct, so it decides the session's starting point, not its limits - and the caller hands it
// over rather than keeping a hand on it.
//
// Required: a session with nothing to scan is not one.
Options *codescan.Options
// Profiling says what each scan captures about itself. Fixed for the session - see [scan.Profiling].
Profiling *scan.Profiling
// ConfigPath is the configuration file that preset the flags, and ConfigSet the flags it decided. Carried only to
// be reported: by the time the model exists the answers are already in Options, and what is worth saying is that
// they did not all come from the command line.
ConfigPath string
ConfigSet []string
}
Startup is everything the command settled before the UI existed: what to scan, what to observe about the scan, and where those answers came from.
One struct rather than a growing argument list, because these arrive together and are decided together - the configuration file presets the flags, the flags fill the options, and the options overlay takes over from there. A caller that only wants a scan leaves the rest zero.
type ValidationState ¶ added in v0.36.4
type ValidationState struct {
Findings []validation.Finding
Err error
Ran bool
Cursor int
}
ValidationState is the last validation's outcome.
Held apart from ScanState because it has a different lifetime: a scan happens on its own, whereas a validation only happens when asked for - and is retired the moment the spec it judged is replaced.
Source Files
¶
Directories
¶
| Path | Synopsis |
|---|---|
|
Package confirm is the yes or no modal: a question that must be answered before something irreversible happens.
|
Package confirm is the yes or no modal: a question that must be answered before something irreversible happens. |
|
Package diagnostics renders the scan half of the diagnostics pane: a severity tally, then one row per finding.
|
Package diagnostics renders the scan half of the diagnostics pane: a severity tally, then one row per finding. |
|
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 humanize renders durations, sizes and counts the way the chrome shows them to a reader.
|
Package humanize renders durations, sizes and counts the way the chrome shows them to a reader. |
|
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 reference is the annotation-reference popup: what the swagger: directive on this line means, and what may be written under it, without leaving the file to go and look it up.
|
Package reference is the annotation-reference popup: what the swagger: directive on this line means, and what may be written under it, without leaving the file to go and look it up. |
|
Package runstats shows what the last scan cost: the wall clock, and the memory the process moved across the run.
|
Package runstats shows what the last scan cost: the wall clock, and the memory the process moved across the run. |
|
Package safetext makes the control characters in scanned text visible, so nothing a repository contains can steer the terminal the TUI is drawn on.
|
Package safetext makes the control characters in scanned text visible, so nothing a repository contains can steer the terminal the TUI is drawn on. |
|
Package scan runs codescan and reports the outcome as a bubbletea message.
|
Package scan runs codescan and reports the outcome as a bubbletea message. |
|
Package testutils holds helpers shared by the tests of several ux packages.
|
Package testutils holds helpers shared by the tests of several ux packages. |
|
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. |
|
Package validation runs the produced spec through go-openapi/validate and normalises what comes back into findings the TUI can list and navigate to.
|
Package validation runs the produced spec through go-openapi/validate and normalises what comes back into findings the TUI can list and navigate to. |
|
Package watcher reports changes under a source tree, so the spec can be regenerated on save.
|
Package watcher reports changes under a source tree, so the spec can be regenerated on save. |