Documentation
¶
Overview ¶
Package radish is a small, open, testable terminal-interactivity library.
radish provides interactive terminal prompts - single-select (Select), multi-select (MultiSelect), and single-line text (Input) - built around a strict separation between a pure, deterministic Model and a thin I/O edge:
- A Model holds all state, logic, and rendering. It is pure: no I/O, no globals, no time. Update(Event) advances state; View() renders a frame.
- The I/O edge is two interfaces - EventSource (where input Events come from) and FrameSink (where rendered frames go) - that Run is parameterized by.
In production the edge is a real terminal in raw mode. In tests it is a scripted source of Events and a recording sink of frames. The same Model and View run in both, so the real rendering and logic are exercised directly in deterministic tests rather than mocked away.
radish is deliberately open where comparable libraries are closed: the match function, key bindings, styling, the event source, and the render target are all injectable, each with a sane default so the simple case stays a one-liner.
Index ¶
- Variables
- func DefaultMatcher(filter, label string) (bool, int)
- func Run(model Model, src EventSource, sink FrameSink) (Result, Model, error)
- func RunInput(m *InputModel, in *os.File, out io.Writer) (value string, ok bool, err error)
- func RunMultiSelect(m *MultiSelectModel, in *os.File, out io.Writer) (values []string, ok bool, err error)
- func RunSelect(m *SelectModel, in *os.File, out io.Writer) (value string, ok bool, err error)
- func RunTerminal(model Model, in *os.File, out io.Writer) (Result, Model, error)
- type Cmd
- type CursorSink
- type Cursorer
- type EchoMode
- type EditorModel
- func (m *EditorModel) ContPrompt(s string) *EditorModel
- func (m *EditorModel) Cursor() (int, int)
- func (m *EditorModel) History(entries []string) *EditorModel
- func (m *EditorModel) Indent(fn func(string) string) *EditorModel
- func (m *EditorModel) IsComplete(fn func(string) bool) *EditorModel
- func (m *EditorModel) KeyMap(k KeyMap) *EditorModel
- func (m *EditorModel) MaxHeight(n int) *EditorModel
- func (m *EditorModel) Outcome() EditorOutcome
- func (m *EditorModel) Prompt(s string) *EditorModel
- func (m *EditorModel) Summary() string
- func (m *EditorModel) Text() string
- func (m *EditorModel) Theme(t *Theme) *EditorModel
- func (m *EditorModel) Update(e Event) (Model, Cmd)
- func (m *EditorModel) Value() (string, bool)
- func (m *EditorModel) View() string
- func (m *EditorModel) Width(n int) *EditorModel
- type EditorOutcome
- type Event
- type EventSource
- type FrameSink
- type InputModel
- func (m *InputModel) Echo(mode EchoMode) *InputModel
- func (m *InputModel) KeyMap(k KeyMap) *InputModel
- func (m *InputModel) Placeholder(s string) *InputModel
- func (m *InputModel) Prompt(s string) *InputModel
- func (m *InputModel) Summary() string
- func (m *InputModel) SummaryFunc(fn func(value string) string) *InputModel
- func (m *InputModel) Theme(t *Theme) *InputModel
- func (m *InputModel) Title(s string) *InputModel
- func (m *InputModel) Update(e Event) (Model, Cmd)
- func (m *InputModel) Validate(fn func(string) error) *InputModel
- func (m *InputModel) Value() (string, bool)
- func (m *InputModel) View() string
- func (m *InputModel) Width(n int) *InputModel
- type KeyMap
- type KeyType
- type Matcher
- type Model
- type Modifier
- type MultiSelectModel
- func (m *MultiSelectModel) Height(n int) *MultiSelectModel
- func (m *MultiSelectModel) Hint(s string) *MultiSelectModel
- func (m *MultiSelectModel) KeyMap(k KeyMap) *MultiSelectModel
- func (m *MultiSelectModel) Matcher(fn Matcher) *MultiSelectModel
- func (m *MultiSelectModel) Max(n int) *MultiSelectModel
- func (m *MultiSelectModel) Min(n int) *MultiSelectModel
- func (m *MultiSelectModel) Options(opts ...string) *MultiSelectModel
- func (m *MultiSelectModel) Preselect(labels ...string) *MultiSelectModel
- func (m *MultiSelectModel) Selected() []string
- func (m *MultiSelectModel) Summary() string
- func (m *MultiSelectModel) SummaryFunc(fn func(selected []string) string) *MultiSelectModel
- func (m *MultiSelectModel) Theme(t *Theme) *MultiSelectModel
- func (m *MultiSelectModel) Title(s string) *MultiSelectModel
- func (m *MultiSelectModel) Update(e Event) (Model, Cmd)
- func (m *MultiSelectModel) View() string
- func (m *MultiSelectModel) Width(n int) *MultiSelectModel
- type Result
- type ScriptDriver
- type SelectModel
- func (m *SelectModel) Height(n int) *SelectModel
- func (m *SelectModel) KeyMap(k KeyMap) *SelectModel
- func (m *SelectModel) Matcher(fn Matcher) *SelectModel
- func (m *SelectModel) Options(opts ...string) *SelectModel
- func (m *SelectModel) Selected() (string, bool)
- func (m *SelectModel) Summary() string
- func (m *SelectModel) SummaryFunc(fn func(selected string) string) *SelectModel
- func (m *SelectModel) Theme(t *Theme) *SelectModel
- func (m *SelectModel) Title(s string) *SelectModel
- func (m *SelectModel) Update(e Event) (Model, Cmd)
- func (m *SelectModel) View() string
- func (m *SelectModel) Width(n int) *SelectModel
- type Summarizer
- type TermDriver
- type Theme
Constants ¶
This section is empty.
Variables ¶
var ErrNotInteractive = errors.New("radish: not an interactive terminal")
ErrNotInteractive is returned by RunTerminal when the input is not a TTY.
Functions ¶
func DefaultMatcher ¶
DefaultMatcher is a case-insensitive subsequence ("fuzzy") match: every rune of filter must appear in label, in order. Exact (case-insensitive) equality ranks best, then a prefix match, then everything else.
func Run ¶
Run drives a Model to completion against the given source and sink. It is the single place the event loop lives:
- render the initial frame
- read an event; on io.EOF, treat as cancel
- Update the model; on CmdSubmit/CmdCancel, collapse and return
- otherwise render the new frame and repeat
It returns the Result, the final Model (type-assert it to read a typed value, e.g. *SelectModel), and any I/O error from the sink/source.
func RunInput ¶
RunInput runs a single-line text prompt on a real terminal and returns the typed value. ok is false when the user canceled (Esc/Ctrl-C). It returns ErrNotInteractive when in is not a TTY. The convenient one-call form; for full control over the I/O edge (e.g. tests) use Run with a ScriptDriver.
func RunMultiSelect ¶
func RunMultiSelect(m *MultiSelectModel, in *os.File, out io.Writer) (values []string, ok bool, err error)
RunMultiSelect runs a multi-select prompt on a real terminal and returns the chosen values. ok is false when the user canceled (Esc/Ctrl-C). It returns ErrNotInteractive when in is not a TTY. The convenient one-call form; for full control over the I/O edge (e.g. tests) use Run with a ScriptDriver.
func RunSelect ¶
RunSelect runs a single-select prompt on a real terminal and returns the chosen value. ok is false when the user canceled (Esc/Ctrl-C). It returns ErrNotInteractive when in is not a TTY. This is the convenient one-call form; for full control over the I/O edge (e.g. tests) use Run with your own EventSource/FrameSink, or a ScriptDriver.
func RunTerminal ¶
RunTerminal drives a Model against a real terminal: in is put into raw mode and read for keystrokes, frames are rendered inline to out. It returns ErrNotInteractive (without touching the terminal) when in is not a TTY, so callers can fall back to non-interactive behavior. out is conventionally stderr, keeping stdout clean for a program's actual result.
Types ¶
type Cmd ¶
type Cmd int
Cmd is a control-flow signal returned by Update. It is a tiny synchronous enum, not an async command runtime: Run evaluates it inline.
type CursorSink ¶ added in v0.3.0
CursorSink is the FrameSink half of Cursorer. A sink that implements it is handed the cursor position along with the frame; one that doesn't just gets Render, so existing sinks need no change.
type Cursorer ¶ added in v0.3.0
type Cursorer interface {
Cursor() (row, col int)
}
Cursorer is an optional Model capability: where the terminal's own cursor belongs inside the frame just rendered, as a zero-based row and *display* column (the Model does the width accounting, so a frame containing wide runes still lands the cursor correctly).
Prompts don't implement it - they draw a cursor glyph instead, which survives in color-stripped snapshot frames. That trade is right for a one-line field and wrong for an editor, where a glyph shifts the text as it moves. A Model that implements Cursorer gets the real thing.
type EditorModel ¶ added in v0.3.0
type EditorModel struct {
// contains filtered or unexported fields
}
EditorModel is a multi-line text buffer with a real cursor: the input half of a REPL. It is a pure Model like the prompts, and deliberately much smaller than a readline - no completion, no highlighting, no reverse search.
Two hooks keep it language-agnostic. IsComplete decides whether Enter submits or opens a new line, and Indent supplies the leading whitespace for that new line. Both default to single-line behavior, so an EditorModel with no hooks is just a text field that happens to know about the cursor.
It implements Cursorer, so it gets the terminal's own cursor rather than the glyph the prompts draw - a glyph would shift the text as it moved, which is tolerable in a one-line field and not in a code buffer.
func NewEditor ¶ added in v0.3.0
func NewEditor() *EditorModel
NewEditor returns an empty editor with the default theme and keymap.
func (*EditorModel) ContPrompt ¶ added in v0.3.0
func (m *EditorModel) ContPrompt(s string) *EditorModel
ContPrompt sets the prefix for every line after the first (e.g. "... "). Defaults to spaces matching the main prompt's width, so text stays aligned.
func (*EditorModel) Cursor ¶ added in v0.3.0
func (m *EditorModel) Cursor() (int, int)
Cursor reports where the terminal cursor belongs. It implements Cursorer.
func (*EditorModel) History ¶ added in v0.3.0
func (m *EditorModel) History(entries []string) *EditorModel
History supplies previously submitted entries, oldest first, for Up/Down recall. The editor never mutates the slice.
func (*EditorModel) Indent ¶ added in v0.3.0
func (m *EditorModel) Indent(fn func(string) string) *EditorModel
Indent supplies the leading whitespace for a newly opened line, given the buffer text above it. A nil fn (the default) starts new lines at column zero. It is not consulted while pasting, since pasted text carries its own indent.
func (*EditorModel) IsComplete ¶ added in v0.3.0
func (m *EditorModel) IsComplete(fn func(string) bool) *EditorModel
IsComplete decides what Enter does: submit when fn reports the buffer is a complete unit, otherwise open a new line. A nil fn (the default) always submits, giving single-line behavior.
func (*EditorModel) KeyMap ¶ added in v0.3.0
func (m *EditorModel) KeyMap(k KeyMap) *EditorModel
KeyMap overrides the key bindings.
func (*EditorModel) MaxHeight ¶ added in v0.3.0
func (m *EditorModel) MaxHeight(n int) *EditorModel
MaxHeight caps how many rows the editor draws, scrolling within the buffer to keep the cursor visible. Zero means no cap. Set it to the terminal height: the inline renderer walks back up over the rows it drew, so a block taller than the screen scrolls part of itself out of reach and corrupts the next redraw.
func (*EditorModel) Outcome ¶ added in v0.3.0
func (m *EditorModel) Outcome() EditorOutcome
Outcome reports how the edit ended.
func (*EditorModel) Prompt ¶ added in v0.3.0
func (m *EditorModel) Prompt(s string) *EditorModel
Prompt sets the prefix rendered before the first line (e.g. "> ").
func (*EditorModel) Summary ¶ added in v0.3.0
func (m *EditorModel) Summary() string
Summary leaves the submitted input in the scrollback, prompts and all, so the session reads as a transcript. Implements Summarizer.
func (*EditorModel) Text ¶ added in v0.3.0
func (m *EditorModel) Text() string
Text returns the buffer contents, lines joined with newlines.
func (*EditorModel) Theme ¶ added in v0.3.0
func (m *EditorModel) Theme(t *Theme) *EditorModel
Theme overrides the styling. A nil argument is ignored.
func (*EditorModel) Update ¶ added in v0.3.0
func (m *EditorModel) Update(e Event) (Model, Cmd)
Update advances the model in response to one event. It implements Model.
func (*EditorModel) Value ¶ added in v0.3.0
func (m *EditorModel) Value() (string, bool)
Value returns the buffer and true, or ("", false) if the edit did not end in a submit. It mirrors InputModel.Value so callers can treat the two alike.
func (*EditorModel) View ¶ added in v0.3.0
func (m *EditorModel) View() string
View renders the wrapped buffer. It implements Model.
func (*EditorModel) Width ¶ added in v0.3.0
func (m *EditorModel) Width(n int) *EditorModel
Width sets the terminal width the buffer wraps to. Zero disables wrapping.
type EditorOutcome ¶ added in v0.3.0
type EditorOutcome int
EditorOutcome says how an edit ended. Result{Canceled} only carries one bit, which cannot distinguish "throw this line away and prompt me again" from "I am done giving you input" - a distinction a REPL loop lives or dies by.
const ( EditorEOF EditorOutcome = iota // Ctrl-D on an empty buffer, or input ran out EditorSubmitted // Enter on a complete buffer EditorDiscarded // Ctrl-C: abandon the buffer, prompt again )
EditorEOF is deliberately the zero value: Run also ends on a source EOF, which sets no outcome at all, and "the input stream ended" is the truthful reading of that. Making Submitted the zero value would have an abandoned edit claim its half-typed buffer was submitted.
type Event ¶
Event is one logical input. For KeyRune, Rune holds the character; for named keys, Rune is unused.
func Parse ¶
Parse converts raw terminal input bytes into high-level Events.
It returns the parsed events and the number of leading bytes consumed. A trailing *incomplete* escape sequence or partial UTF-8 rune is left unconsumed (consumed < len(b)), so a streaming caller can keep b[consumed:], append the next read, and call Parse again. Bytes that decode to nothing meaningful (e.g. unmapped control characters) are consumed but produce no event.
Parse is pure: no I/O, no state. It is the entire cross-platform input surface, which is why it is unit-tested exhaustively in isolation.
func ParseKeyName ¶
ParseKeyName returns the Event for a named key, the inverse of Event.String for named keys - e.g. "up", "enter", "ctrl-c", "shift-tab", or "space" for the space rune. It returns ok=false for an unknown name. Printable characters are not handled here; pass those as runes via RuneEvent. Sharing keyNames with Event.String keeps the display and parse vocabularies from drifting.
type EventSource ¶
EventSource yields input Events, blocking until one is available. It returns io.EOF when input is exhausted. In production this reads a real terminal; in tests it replays a scripted slice.
type FrameSink ¶
FrameSink consumes rendered frames. Render replaces the previously-shown frame; Finish performs the terminal "collapse" - clearing the interactive frame and emitting a final summary (or nothing). In production this drives a real terminal; in tests it records frames.
type InputModel ¶
type InputModel struct {
// contains filtered or unexported fields
}
InputModel is a single-line text prompt: an optional title heading, an inline prompt prefix, and an editable value with a visible cursor. Like the other components it is a pure Model - all state and rendering, no I/O - so it runs identically in production and under scripted test events.
Build one with the chained setters (NewInput().Prompt(...)), then hand it to Run. After Run, read the text with Value.
EchoNone renders nothing as the user types (the terminal convention for passwords): every keystroke produces a byte-identical frame and the typed runes never appear in any rendered frame, only in the returned Value. In EchoNone the cursor cannot be moved (append and backspace only), matching tools like sudo.
func NewInput ¶
func NewInput() *InputModel
NewInput returns an InputModel with default theme and keymap and normal echo.
func (*InputModel) Echo ¶
func (m *InputModel) Echo(mode EchoMode) *InputModel
Echo sets how typed text is displayed (EchoNormal, EchoNone, EchoMasked).
func (*InputModel) KeyMap ¶
func (m *InputModel) KeyMap(k KeyMap) *InputModel
KeyMap overrides the key bindings.
func (*InputModel) Placeholder ¶
func (m *InputModel) Placeholder(s string) *InputModel
Placeholder sets ghost/hint text shown (dimmed) while the field is empty. It is only shown in EchoNormal mode.
func (*InputModel) Prompt ¶
func (m *InputModel) Prompt(s string) *InputModel
Prompt sets the inline prefix rendered immediately before the editable field (e.g. "> " or "Confirm? Y/n > ").
func (*InputModel) Summary ¶
func (m *InputModel) Summary() string
Summary is the collapsed line shown after submit. It returns "" on cancel, and - crucially - never echoes a secret: EchoNone collapses to nothing and EchoMasked to dots. A SummaryFunc, when set, replaces all of this. Implements Summarizer.
func (*InputModel) SummaryFunc ¶ added in v0.2.0
func (m *InputModel) SummaryFunc(fn func(value string) string) *InputModel
SummaryFunc overrides the collapsed line shown after submit: fn receives the submitted value and its result replaces the default summary entirely (an empty result collapses to nothing). Cancel still collapses to nothing. Note fn receives the real value even in masked/secret echo modes - it is the caller's job not to leak it.
func (*InputModel) Theme ¶
func (m *InputModel) Theme(t *Theme) *InputModel
Theme overrides the styling. A nil argument is ignored.
func (*InputModel) Title ¶
func (m *InputModel) Title(s string) *InputModel
Title sets an optional heading line shown above the input field.
func (*InputModel) Update ¶
func (m *InputModel) Update(e Event) (Model, Cmd)
Update advances the model in response to one event. It implements Model. Printable runes and Backspace are intrinsic edits; cursor movement is bindable but disabled entirely in EchoNone (append + backspace only).
func (*InputModel) Validate ¶ added in v0.2.0
func (m *InputModel) Validate(fn func(string) error) *InputModel
Validate sets a submit gate: on Enter the current value is passed to fn, and a non-nil error blocks the submit, rendering the error message under the field until the value is next edited. A nil fn (the default) accepts everything.
func (*InputModel) Value ¶
func (m *InputModel) Value() (string, bool)
Value returns the typed text and true, or ("", false) if the prompt was canceled.
func (*InputModel) View ¶
func (m *InputModel) View() string
View renders the title (if any), the field line, and a pending validation error (if any) below it. No trailing newline.
func (*InputModel) Width ¶
func (m *InputModel) Width(n int) *InputModel
Width caps the rendered field to this terminal width (with an ellipsis). Zero disables truncation.
type KeyMap ¶
type KeyMap struct {
Up []KeyType
Down []KeyType
Left []KeyType // move the text cursor left (Input)
Right []KeyType // move the text cursor right (Input)
PageUp []KeyType
PageDown []KeyType
Home []KeyType
End []KeyType
Toggle []KeyType // toggle the row's selection (MultiSelect); Space also toggles
Submit []KeyType
Cancel []KeyType
ClearFilter []KeyType
// Editor-only actions. Discard and EndOfInput exist because an editor has to
// tell "throw this away and give me a fresh prompt" (Ctrl-C) apart from "I am
// done providing input" (Ctrl-D on an empty buffer), which the single Cancel
// action cannot express.
Discard []KeyType
EndOfInput []KeyType
KillLine []KeyType // delete the whole current line
KillToEnd []KeyType // delete from the cursor to end of line
DeleteWordBack []KeyType
}
KeyMap binds actions to one or more KeyTypes. Override any field to remap an action; a nil/empty binding means the action is unbound. Typing a printable rune and Backspace are the intrinsic text-input pair and are always filter edits (never remappable), which keeps "type to filter" unambiguous; higher-level editing commands like ClearFilter are bindable actions so they stay open to customization.
One KeyMap serves the whole prompt family, so a given component reads only the actions it supports and silently ignores the rest (e.g. Input ignores Up/Down/ Toggle; Select ignores Left/Right). MultiSelect also toggles on Space, which is a deliberate, non-remappable exception to "only runes and Backspace are intrinsic" (Space-to-toggle is a near-universal convention and Space is useless as a filter character).
func DefaultKeyMap ¶
func DefaultKeyMap() KeyMap
DefaultKeyMap is the conventional binding: arrows navigate (Up/Down) or move the text cursor (Left/Right), PageUp/PageDown jump a page, Home/End jump to the ends (Ctrl-A/Ctrl-E too, as in every readline), Enter submits, Esc/Ctrl-C/Ctrl-D cancel, and Ctrl-U clears the filter.
type KeyType ¶
type KeyType int
KeyType identifies a logical key. Printable input is delivered as KeyRune (the character is in Event.Rune); everything else is a named key. The set is kept deliberately small - just what the interactive components need - and grows only as new components require it.
const ( KeyNone KeyType = iota // no event / dropped input KeyRune // a printable rune; Event.Rune holds the character KeyUp KeyDown KeyLeft KeyRight KeyEnter KeyBackspace KeyDelete KeyTab KeyShiftTab KeyEsc KeyHome KeyEnd KeyPageUp KeyPageDown KeyCtrlA KeyCtrlC KeyCtrlD KeyCtrlE KeyCtrlK KeyCtrlL KeyCtrlU KeyCtrlW // KeyPasteStart and KeyPasteEnd bracket a run of events the terminal // delivered as a paste rather than as typing. A Model that cares (a // multi-line editor, where Enter otherwise submits) inserts everything // between them literally; one that doesn't can ignore both and behave // exactly as before. KeyPasteStart KeyPasteEnd )
type Matcher ¶
Matcher reports whether an option label matches the current filter text, and a rank for ordering (lower sorts first; equal ranks keep original order). Components that don't reorder may ignore rank. By convention an empty filter matches everything.
Matcher is the primary openness hook: callers inject their own to control how typing narrows the list, while the built-in DefaultMatcher covers the common fuzzy case.
type Model ¶
Model is a pure, deterministic interactive component. Update advances state in response to one Event; View renders the current frame as a multi-line string with no trailing newline. A Model performs no I/O and holds no global state, so the same Model runs identically in production and in tests.
type Modifier ¶
type Modifier uint8
Modifier is a reserved bitfield for chord modifiers. The current parser never sets it (Ctrl-C / Ctrl-D have dedicated KeyTypes), but the field on Event keeps the type source-stable for when Alt/Ctrl chords are modelled later.
const ModNone Modifier = 0
type MultiSelectModel ¶
type MultiSelectModel struct {
// contains filtered or unexported fields
}
MultiSelectModel is a multi-select prompt: a title, a list of options with live type-to-filter and a moving cursor (all shared with Select via the embedded list), plus a per-option selection toggle and optional min/max bounds on how many may be chosen. It is a pure Model, driven identically in production and in tests.
Build one with the chained setters (NewMultiSelect().Title(...).Options(...)), then hand it to Run. After Run, read the chosen labels with Selected.
Tab or Space toggles the row under the cursor. With a Max set, toggling a new option on is blocked once Max are selected (deselect is always allowed). With a Min set, Enter does nothing until at least Min are selected.
func NewMultiSelect ¶
func NewMultiSelect() *MultiSelectModel
NewMultiSelect returns a MultiSelectModel with sane defaults (fuzzy matcher, default theme and keymap, 10 visible rows, no min/max). Customize via the setters.
func (*MultiSelectModel) Height ¶
func (m *MultiSelectModel) Height(n int) *MultiSelectModel
Height sets how many option rows are visible at once (the viewport size). Non-positive values are ignored.
func (*MultiSelectModel) Hint ¶ added in v0.2.0
func (m *MultiSelectModel) Hint(s string) *MultiSelectModel
Hint sets an interaction hint rendered as a faint footer line, e.g. "space to toggle, enter to confirm" for first-time users. Empty = no line.
func (*MultiSelectModel) KeyMap ¶
func (m *MultiSelectModel) KeyMap(k KeyMap) *MultiSelectModel
KeyMap overrides the key bindings.
func (*MultiSelectModel) Matcher ¶
func (m *MultiSelectModel) Matcher(fn Matcher) *MultiSelectModel
Matcher overrides the filter-matching function. A nil argument is ignored.
func (*MultiSelectModel) Max ¶
func (m *MultiSelectModel) Max(n int) *MultiSelectModel
Max sets the maximum number of options that may be selected (0 = unlimited). Negative values are ignored.
func (*MultiSelectModel) Min ¶
func (m *MultiSelectModel) Min(n int) *MultiSelectModel
Min sets the minimum number of options that must be selected before Enter submits. Negative values are ignored.
func (*MultiSelectModel) Options ¶
func (m *MultiSelectModel) Options(opts ...string) *MultiSelectModel
Options sets the selectable options (in display order).
func (*MultiSelectModel) Preselect ¶ added in v0.2.0
func (m *MultiSelectModel) Preselect(labels ...string) *MultiSelectModel
Preselect marks the options with the given labels as already selected, so the prompt opens with them checked (e.g. reflecting defaults). Labels that match no option are ignored. Order-independent with Options: labels are remembered and reapplied whenever the options change.
func (*MultiSelectModel) Selected ¶
func (m *MultiSelectModel) Selected() []string
Selected returns the chosen option labels in original option order.
func (*MultiSelectModel) Summary ¶
func (m *MultiSelectModel) Summary() string
Summary is the collapsed line shown after submit: title plus the chosen values. Returns "" on cancel. A SummaryFunc, when set, replaces the default rendering. Implements Summarizer.
func (*MultiSelectModel) SummaryFunc ¶ added in v0.2.0
func (m *MultiSelectModel) SummaryFunc(fn func(selected []string) string) *MultiSelectModel
SummaryFunc overrides the collapsed line shown after submit: fn receives the selected labels (in option order) and its result replaces the default summary entirely (an empty result collapses to nothing). Cancel still collapses to nothing.
func (*MultiSelectModel) Theme ¶
func (m *MultiSelectModel) Theme(t *Theme) *MultiSelectModel
Theme overrides the styling. A nil argument is ignored.
func (*MultiSelectModel) Title ¶
func (m *MultiSelectModel) Title(s string) *MultiSelectModel
Title sets the line shown above the options (the question being asked).
func (*MultiSelectModel) Update ¶
func (m *MultiSelectModel) Update(e Event) (Model, Cmd)
Update advances the model in response to one event. It implements Model.
func (*MultiSelectModel) View ¶
func (m *MultiSelectModel) View() string
View renders the title, optional filter line, the visible window of options with checkboxes and the cursor row marked, scroll hints, and a min hint while under the minimum. No trailing newline; line count is fully data-driven.
func (*MultiSelectModel) Width ¶
func (m *MultiSelectModel) Width(n int) *MultiSelectModel
Width caps each option row to this terminal width (with an ellipsis). Zero disables truncation.
type Result ¶
type Result struct {
Canceled bool
}
Result reports how an interaction ended. A run that returns a nil error ended in exactly one of two ways: the user submitted (Canceled == false) or aborted (Canceled == true, via Esc/Ctrl-C/Ctrl-D or EOF). The chosen value, if any, lives on the component (e.g. SelectModel.Selected) or is returned by the typed runners (RunSelect).
type ScriptDriver ¶
type ScriptDriver struct {
// contains filtered or unexported fields
}
ScriptDriver replays a fixed sequence of Events through one or more Models and records every frame the Models render. It fills the same role as the production terminal driver, but with no terminal: the real Model and View run unchanged - only the I/O edge is scripted. This is what makes deterministic, end-to-end snapshot tests of the real interactive UI possible.
The event source is consumed across Run calls: a second Run continues from wherever the first stopped, mirroring how sequential prompts share one real terminal. Frames likewise accumulate across Runs.
func NewScriptDriver ¶
func NewScriptDriver(events []Event) *ScriptDriver
NewScriptDriver creates a driver that will feed events in order, then EOF.
func (*ScriptDriver) Events ¶
func (d *ScriptDriver) Events() []Event
Events returns the scripted events, for callers that label frames by their triggering keystroke.
func (*ScriptDriver) FrameEventIdx ¶ added in v0.2.0
func (d *ScriptDriver) FrameEventIdx() []int
FrameEventIdx returns, for each recorded frame, the index into Events() of the event that triggered it, or -1 for a frame rendered without consuming an event (a prompt's initial render). Aligned with Frames().
func (*ScriptDriver) Frames ¶
func (d *ScriptDriver) Frames() []string
Frames returns the recorded frames in order across all Runs: each prompt's initial render, the render after each consumed event, and (on submit) the final collapsed summary. Use FrameEventIdx to map a frame back to the event that produced it.
type SelectModel ¶
type SelectModel struct {
// contains filtered or unexported fields
}
SelectModel is a single-select prompt: a title, a list of options, live type-to-filter, and a moving cursor. It is a pure Model - all state, logic, and rendering, no I/O - so it is driven identically by the production terminal and by scripted test events.
Build one with the chained setters (NewSelect().Title(...).Options(...)), then hand it to Run. After Run, read the choice with Selected. The option set, filter, cursor, and viewport live in the embedded list, shared with MultiSelect.
func NewSelect ¶
func NewSelect() *SelectModel
NewSelect returns a SelectModel with sane defaults (fuzzy matcher, default theme and keymap, 10 visible rows). Customize via the chained setters.
func (*SelectModel) Height ¶
func (m *SelectModel) Height(n int) *SelectModel
Height sets how many option rows are visible at once (the viewport size). Non-positive values are ignored.
func (*SelectModel) KeyMap ¶
func (m *SelectModel) KeyMap(k KeyMap) *SelectModel
KeyMap overrides the key bindings.
func (*SelectModel) Matcher ¶
func (m *SelectModel) Matcher(fn Matcher) *SelectModel
Matcher overrides the filter-matching function. A nil argument is ignored.
func (*SelectModel) Options ¶
func (m *SelectModel) Options(opts ...string) *SelectModel
Options sets the selectable options (in display order).
func (*SelectModel) Selected ¶
func (m *SelectModel) Selected() (string, bool)
Selected returns the chosen option and true, or ("", false) if the prompt was canceled or nothing matched. Whether a run was canceled is reported by Result.Canceled from Run; this reports the value.
func (*SelectModel) Summary ¶
func (m *SelectModel) Summary() string
Summary is the collapsed line shown after submit: prompt plus chosen value. It returns "" on cancel so nothing is left behind. Implements Summarizer.
func (*SelectModel) SummaryFunc ¶ added in v0.2.0
func (m *SelectModel) SummaryFunc(fn func(selected string) string) *SelectModel
SummaryFunc overrides the collapsed line shown after submit: fn receives the selected option and its result replaces the default summary entirely (an empty result collapses to nothing). Cancel still collapses to nothing.
func (*SelectModel) Theme ¶
func (m *SelectModel) Theme(t *Theme) *SelectModel
Theme overrides the styling. A nil argument is ignored.
func (*SelectModel) Title ¶
func (m *SelectModel) Title(s string) *SelectModel
Title sets the line shown above the options (the question being asked).
func (*SelectModel) Update ¶
func (m *SelectModel) Update(e Event) (Model, Cmd)
Update advances the model in response to one event. It implements Model.
func (*SelectModel) View ¶
func (m *SelectModel) View() string
View renders the current frame: title, optional filter line, the visible window of options with the cursor row marked, and scroll hints. No trailing newline. Line count is fully data-driven so the renderer's redraw accounting stays exact.
func (*SelectModel) Width ¶
func (m *SelectModel) Width(n int) *SelectModel
Width caps each option row to this terminal width (with an ellipsis). Zero disables truncation.
type Summarizer ¶
type Summarizer interface {
Summary() string
}
Summarizer is an optional Model capability: the collapsed line shown after the interaction ends (e.g. "Pick a food: Pizza"). Models that don't implement it collapse to nothing.
type TermDriver ¶
type TermDriver struct {
// contains filtered or unexported fields
}
TermDriver reads raw keystrokes from a terminal and renders frames to it. It implements both EventSource and FrameSink. The escape-sequence accounting lives in inlineRenderer (pure, unit-tested); TermDriver only adds the raw-mode lifecycle and the byte reads, which inherently need a real terminal.
func NewTermDriver ¶
NewTermDriver puts in into raw mode and returns a driver writing to out. It returns ErrNotInteractive if in is not a terminal.
func (*TermDriver) Close ¶
func (d *TermDriver) Close() error
Close restores the terminal. It is safe to call more than once (Run defers it for both the source and the sink role).
func (*TermDriver) Finish ¶
func (d *TermDriver) Finish(final string) error
func (*TermDriver) Next ¶
func (d *TermDriver) Next() (Event, error)
Next returns the next key event, reading and parsing more bytes as needed. A partial escape sequence at the end of a read is retained and completed by the following read.
func (*TermDriver) Render ¶
func (d *TermDriver) Render(frame string) error
func (*TermDriver) RenderCursor ¶ added in v0.3.0
func (d *TermDriver) RenderCursor(frame string, row, col int) error
RenderCursor draws the frame and leaves the terminal's own cursor at (row, col) within it, visible. This is the Cursorer path; Render remains the hidden-cursor path the prompts use.
type Theme ¶
type Theme struct {
Title *color.Color // prompt / title line
Cursor *color.Color // the "> " pointer on the active row, or the text cursor
Selected *color.Color // the active row's label
Normal *color.Color // inactive rows / typed text (nil = plain)
Filter *color.Color // the typed filter line
Placeholder *color.Color // an input's ghost/hint text shown while empty
ScrollHint *color.Color // "↑/↓ N more" and "(no matches)"
Error *color.Color // an input's validation error line
}
Theme holds the styles applied while rendering. Each field is an amterp/color Color (or nil for "no styling"). Because color.Sprint returns plain text when color is disabled (color.NoColor / NO_COLOR), themed output collapses to clean plain text automatically - which is what makes rendered frames stable in tests with no special handling.
One Theme serves the whole prompt family; a component uses only the fields it renders and ignores the rest (e.g. Placeholder is Input-only; Filter/ScrollHint are used by the list-based prompts).
func DefaultTheme ¶
func DefaultTheme() *Theme
DefaultTheme is a tasteful default: bold title, green cursor/selection, faint filter, placeholder, and scroll hints, red validation errors.
Source Files
¶
Directories
¶
| Path | Synopsis |
|---|---|
|
examples
|
|
|
input
command
Command input is a tiny manual smoke test for radish's text-input prompt.
|
Command input is a tiny manual smoke test for radish's text-input prompt. |
|
multiselect
command
Command multiselect is a tiny manual smoke test for radish's multi-select prompt.
|
Command multiselect is a tiny manual smoke test for radish's multi-select prompt. |
|
pick
command
Command pick is a tiny manual smoke test for radish's single-select prompt.
|
Command pick is a tiny manual smoke test for radish's single-select prompt. |