radish

package module
v0.2.0 Latest Latest
Warning

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

Go to latest
Published: Jun 11, 2026 License: MIT Imports: 10 Imported by: 0

README

radish

A small, open, testable terminal-interactivity library for Go.

Most TUI prompt libraries are built on a general-purpose, timing-based renderer that reads a real terminal in raw mode. That makes them lovely to use and nearly impossible to test: you end up mocking the whole prompt, leaving the real rendering and selection logic uncovered.

radish takes a different shape. Each prompt is a pure Model - all state, logic, and rendering, with no I/O - sitting behind a thin, swappable I/O edge (EventSource + FrameSink). In production the edge is a real terminal; in tests it's a scripted list of keystrokes and a recording sink. The same Model and View() run in both, so your tests exercise the real prompt end-to-end - deterministically - instead of mocking it away.

It's also deliberately open where comparable libraries are closed: the matcher, theme, key bindings, event source, and render target are all injectable, each with a sane default so the simple case stays a one-liner.

Install

go get github.com/amterp/radish

Quick start

model := radish.NewSelect().
    Title("Pick a fruit (type to filter)").
    Options("Apple", "Banana", "Cherry", "Date")

choice, ok, err := radish.RunSelect(model, os.Stdin, os.Stderr)
if errors.Is(err, radish.ErrNotInteractive) {
    // stdin isn't a TTY - fall back however you like
}
if !ok {
    return // canceled
}
fmt.Println(choice)

Arrow keys move, typing filters, Enter selects, Esc/Ctrl-C cancels. The menu draws to the writer you pass (conventionally stderr), keeping stdout clean for your program's actual result. When stdin isn't a terminal, RunTerminal returns ErrNotInteractive without touching the terminal, so you control the fallback.

Try it: go run ./examples/pick.

Testing - the point

Drive the real prompt with scripted keystrokes and inspect every rendered frame. No terminal, no mocking:

model := radish.NewSelect().Title("Pick").Options("Apple", "Banana", "Cherry")

driver := radish.NewScriptDriver([]radish.Event{
    radish.KeyEvent(radish.KeyDown),
    radish.KeyEvent(radish.KeyEnter),
})
driver.Run(model)

got, _ := model.Selected() // "Banana"
frames := driver.Frames()  // every frame, in order

Frames() returns the initial render, the frame after each keystroke, and the final collapsed summary - ideal for snapshot tests. Disable color (color.NoColor = true) and frames come out as clean plain text.

Customize

Everything below is optional; the defaults are sensible.

radish.NewSelect().
    Title("Pick").
    Options(opts...).
    Matcher(myMatcher).   // how typing filters; default is case-insensitive fuzzy
    Theme(myTheme).       // amterp/color styles; default is tasteful
    KeyMap(myKeyMap).     // rebind navigation; default is arrows + enter + esc
    Height(8).            // visible rows before the list scrolls
    Width(cols)           // truncate long labels to terminal width
  • Matcher func(filter, label string) (matched bool, rank int) - inject your own matching/ranking; DefaultMatcher is a fuzzy subsequence match.
  • Theme - a flat struct of *color.Color styles (via amterp/color); nil fields render plain.
  • KeyMap - []KeyType slices per action; trivially remappable.

The prompt family

All three share the Model/Run seam and the same testability story:

  • Select - single-select with live type-to-filter. RunSelect.
  • MultiSelect - multi-select; Tab/Space toggles, with optional Min/Max bounds (Max blocks extra toggles, Min gates submit). Reuses Select's filter/viewport core. RunMultiSelect.
  • Input - single-line text with an inline Prompt, optional Title heading and Placeholder, and an Echo mode: EchoNormal, EchoMasked (), or EchoNone (sudo-style no-echo for secrets - nothing is rendered as you type, so the value never appears in any frame). RunInput.

There is intentionally no Confirm widget: a yes/no prompt is just an Input whose result the caller interprets, so radish stays minimal and the policy lives with the caller.

// MultiSelect
picks, ok, err := radish.RunMultiSelect(
    radish.NewMultiSelect().Title("Pick toppings").Options(opts...).Max(3),
    os.Stdin, os.Stderr)

// Input (secret)
pw, ok, err := radish.RunInput(
    radish.NewInput().Prompt("Password > ").Echo(radish.EchoNone),
    os.Stdin, os.Stderr)

Try them: go run ./examples/multiselect, go run ./examples/input.

Known limitations: the prompt reads terminal width once at construction, so a mid-prompt resize (SIGWINCH) may misalign one redraw; the seam can carry a resize event later without changing the Model contract. Input truncates an over-width line at the edges rather than horizontally scrolling - the full value is always returned regardless of display.

License

MIT - see LICENSE.

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

Constants

This section is empty.

Variables

View Source
var ErrNotInteractive = errors.New("radish: not an interactive terminal")

ErrNotInteractive is returned by RunTerminal when the input is not a TTY.

Functions

func DefaultMatcher

func DefaultMatcher(filter, label string) (bool, int)

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

func Run(model Model, src EventSource, sink FrameSink) (Result, Model, error)

Run drives a Model to completion against the given source and sink. It is the single place the event loop lives:

  1. render the initial frame
  2. read an event; on io.EOF, treat as cancel
  3. Update the model; on CmdSubmit/CmdCancel, collapse and return
  4. 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

func RunInput(m *InputModel, in *os.File, out io.Writer) (value string, ok bool, err error)

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

func RunSelect(m *SelectModel, in *os.File, out io.Writer) (value string, ok bool, err error)

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

func RunTerminal(model Model, in *os.File, out io.Writer) (Result, Model, error)

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.

const (
	CmdNone   Cmd = iota // keep going
	CmdSubmit            // user confirmed; Run stops with Result{} (Canceled:false)
	CmdCancel            // user aborted; Run stops with Result{Canceled:true}
)

type EchoMode

type EchoMode int

EchoMode controls how an InputModel renders the text being typed.

const (
	EchoNormal EchoMode = iota // show the typed text
	EchoNone                   // show nothing (sudo-style secret entry)
	EchoMasked                 // show one '•' per rune
)

type Event

type Event struct {
	Type KeyType
	Rune rune
	Mods Modifier
}

Event is one logical input. For KeyRune, Rune holds the character; for named keys, Rune is unused.

func KeyEvent

func KeyEvent(t KeyType) Event

KeyEvent builds a named-key Event.

func Parse

func Parse(b []byte) (events []Event, consumed int)

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

func ParseKeyName(name string) (ev Event, ok bool)

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.

func RuneEvent

func RuneEvent(r rune) Event

RuneEvent builds a printable-key Event.

func (Event) String

func (e Event) String() string

String returns a short, human-readable label, e.g. "up", "enter", "ctrl-c", or the character itself ("a", or "space" for ' '). Used in debug output and in snapshot frame markers.

type EventSource

type EventSource interface {
	Next() (Event, error)
	Close() error
}

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

type FrameSink interface {
	Render(frame string) error
	Finish(final string) error
	Close() error
}

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
}

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, 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
	KeyTab
	KeyShiftTab
	KeyEsc
	KeyHome
	KeyEnd
	KeyPageUp
	KeyPageDown
	KeyCtrlC
	KeyCtrlD
	KeyCtrlU
)

type Matcher

type Matcher func(filter, label string) (matched bool, rank int)

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

type Model interface {
	Update(Event) (Model, Cmd)
	View() string
}

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 (
	ModCtrl Modifier = 1 << iota // 1
	ModAlt                       // 2
)
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

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

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

Max sets the maximum number of options that may be selected (0 = unlimited). Negative values are ignored.

func (*MultiSelectModel) Min

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

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.

func (*ScriptDriver) Run

func (d *ScriptDriver) Run(model Model) (Result, Model, error)

Run drives model to completion against the scripted events, recording frames.

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

func NewTermDriver(in *os.File, out io.Writer) (*TermDriver, error)

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

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.

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.

Jump to

Keyboard shortcuts

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