Fields and validation
All prompt content is plain text. Use the form Theme for styling rather than
embedding ANSI sequences in questions, descriptions, labels, or messages.
Text input
Section titled “Text input”use urushi_prompt::{FieldKey, Input, ValidationError};
let project_key = FieldKey::new("project");let project = Input::new(project_key, "Project name", "")? .description("Used as the generated directory name.") .placeholder("my-project") .required() .required_message("Enter a project name.") .validate(Box::new(|value| { if value.contains(' ') { Err(ValidationError::new("Use a name without spaces.")) } else { Ok(()) } }));The configured field appears as one focused region. The placeholder and description use muted theme roles:
┃ Project name
┃ Used as the generated directory name.
┃ › my-project
Submitting the empty value runs the required check before custom validators:
┃ Project name
┃ ›
┃ ! Enter a project name.
Validators are synchronous and run against the String value. Multiple
validators run in insertion order.
If the user enters my project, the validator adds the error below the
control without replacing the entered value:
┃ Project name
┃ Used as the generated directory name.
┃ › my project
┃ ! Use a name without spaces.
Edit the one-line value
Section titled “Edit the one-line value”Input editing follows terminal grapheme boundaries, so a combined emoji or a base character plus combining mark moves and deletes as one unit.
| Action | Keys |
|---|---|
| Move one grapheme | Left / Right, Ctrl-B / Ctrl-F |
| Move to an edge | Home / End, Ctrl-A / Ctrl-E |
| Delete one grapheme | Backspace / Delete |
| Delete to an edge | Ctrl-U / Ctrl-K |
| Validate and continue | Enter / Tab |
| Return to the previous field | Shift-Tab |
| Cancel the form | Esc |
Typing or pasting inserts at the cursor and clears a displayed validation
error. Because Input is one line, paste converts CR, LF, and tab boundaries
to spaces and removes other control characters. For example, pasting
api\tserver\r\nrelease at the cursor produces this observable state:
┃ Project name
┃ › api server release
Moving left twice and pressing Delete removes the grapheme under the cursor; Ctrl-U then removes everything before it. Validation runs only when Enter or Tab attempts to advance.
Typed selection
Section titled “Typed selection”Select<T> stores values independently from their visible labels.
use urushi_prompt::{FieldKey, Select, SelectOption};
#[derive(Debug)]enum Profile { Debug, Release, Test, Bench, Minimal, Production,}
let profile_key = FieldKey::new("profile");let profile = Select::new( profile_key, "Build profile", vec![ SelectOption::new("Debug", Profile::Debug), SelectOption::new("Release", Profile::Release), SelectOption::new("Test", Profile::Test), SelectOption::new("Bench", Profile::Bench), SelectOption::new("Minimal", Profile::Minimal), SelectOption::new("Production", Profile::Production), ],)?.visible_rows(3) .filter_help("type filter • enter apply", "enter choose • / edit") .no_matches_message("No build profiles match");The first option starts selected. Three rows are visible; moving farther scrolls the window instead of increasing the field height:
┃ Build profile
┃ Test
┃ › Bench
┃ Minimal
┃ ↑ 2 • ↓ 1
┃ ↑/↓ select • enter continue • shift+tab back • esc cancel
Press / to edit a case-insensitive substring filter. Typing pro narrows the
list while showing the editing help configured above:
┃ Build profile / pro
┃ › Production
┃
┃
┃ = 1
┃ type filter • enter apply
When no label contains the filter, the configured message replaces the option window without inventing a submitted value:
┃ Build profile / xyz
┃ No build profiles match
┃
┃
┃ = 0
┃ type filter • enter apply
Confirmation
Section titled “Confirmation”use urushi_prompt::{ Confirm, ConfirmAnswer, FieldKey, urushi::Align,};
let overwrite_key = FieldKey::<ConfirmAnswer>::new("overwrite");let overwrite = Confirm::new( overwrite_key, "Overwrite the existing file?", None,)?.labels("Overwrite", "Keep") .button_alignment(Align::Left) .unanswered_message("Choose an action.");The two choices share one left-aligned control row. With no default, neither is selected initially:
┃ Overwrite the existing file?
┃
┃ Overwrite Keep
Pressing Enter before choosing keeps the field active and displays the
configured error:
┃ Overwrite the existing file?
┃ Overwrite Keep
┃ ! Choose an action.
Pressing Right or n explicitly chooses Keep; submission then returns
ConfirmAnswer { value: false, source: ConfirmSource::Explicit }:
┃ Overwrite the existing file?
┃ Overwrite Keep
ConfirmAnswer::value is the yes-or-no result. ConfirmAnswer::source tells
whether the user made an explicit choice or submitted the configured default.
When no default exists, the field remains active until the user chooses.
Help and descriptions
Section titled “Help and descriptions”Every field supports a description and configurable navigation help. Select fields provide separate help for editing and applying a filter:
let input = Input::new(FieldKey::new("name"), "Name", "")? .description("Used in the greeting.") .help("enter next • esc cancel");
let select = Select::new( FieldKey::new("region"), "Region", vec![SelectOption::new("Tokyo", "ap-northeast-1")],)?.help("↑/↓ choose • enter next").filter_help("type filter • enter apply", "enter next • / edit");┃ Name
┃ Used in the greeting.
┃ ›
┃ enter next • esc cancel
Keep help short enough to remain useful at narrow widths. See the prompt reference for all defaults and builders.