Prompt overview
urushi-prompt is for short decisions made while a command is running: asking
for text, choosing from known values, confirming an action, or combining those
steps into a form. A form temporarily owns terminal interaction, then returns
submitted typed values or cancellation to the command.
Prefer flags and arguments when the complete operation can be specified before execution. Use a prompt when the user needs context from the running command to make the decision.
See the complete interaction
Section titled “See the complete interaction”This form collects text, moves focus into a typed selection, confirms the choice, restores the terminal, and returns values to the command:
The moving focus rail, selection highlight, contextual help, and final
Good day, Aki. are all part of the same blocking Form::run call. The code
below builds that shape; the field guides explain each behavior separately.
Run the complete Prompt quickstart ↓
Install the prompt surface
Section titled “Install the prompt surface”cargo add urushi-prompt[dependencies]urushi-prompt = "0.1.0"The crate re-exports urushi for styling, so a prompt-only application does
not need a separate direct dependency on the core crate.
Quickstart: run a form
Section titled “Quickstart: run a form”The form model, field behavior, validation, and theme are configured together:
use urushi_prompt::{ Confirm, ConfirmAnswer, FieldKey, Form, FormOutcome, Group, Input, Select, SelectOption, urushi::ThemePreset,};
fn main() -> Result<(), Box<dyn std::error::Error>> { let name_key = FieldKey::<String>::new("name"); let style_key = FieldKey::<String>::new("style"); let proceed_key = FieldKey::<ConfirmAnswer>::new("proceed"); let group = Group::builder() .title("Greeting setup") .description("Review three values before generating a greeting.") .field( Input::new(name_key.clone(), "What is your name?", "")? .placeholder("e.g. Alex") .required(), ) .field(Select::new( style_key.clone(), "Choose a greeting style", vec![ SelectOption::new("Friendly", "friendly".to_owned()), SelectOption::new("Formal", "formal".to_owned()), ], )?) .field(Confirm::new( proceed_key.clone(), "Generate the greeting?", Some(true), )?) .build()?;
let form = Form::builder().group(group).build()?; let theme = ThemePreset::get("Catppuccin Mocha") .expect("built-in theme") .theme(); if let FormOutcome::Submitted(values) = form.run(&theme)? { let name = values.get(&name_key).expect("submitted name"); let style = values.get(&style_key).expect("submitted style"); let proceed = values.get(&proceed_key).expect("submitted confirmation"); let greeting = match (style.as_str(), proceed.value) { (_, false) => "No greeting generated.".to_owned(), ("formal", true) => format!("Good day, {name}."), (_, true) => format!("Hi, {name}!"), }; println!("{greeting}"); } Ok(())}When the form opens, the active field is marked by the focused gutter and the remaining fields stay visible as space allows:
Greeting setup
Review three values before generating a greeting.
┃ What is your name?
┃ › e.g. Alex
Choose a greeting style
› Friendly
Formal
Generate the greeting?
Yes No
enter continue • shift+tab back • esc cancel
After the user enters Aki, chooses Formal, accepts Yes, and submits, the
command resumes with typed values:
Good day, Aki.FormOutcome::Submitted contains the typed values addressed by the three keys;
FormOutcome::Cancelled contains no partial values.
What a form can do
Section titled “What a form can do”| Need | Use | See it in the recording |
|---|---|---|
| Collect editable text | Input |
Aki remains visible after advancing |
| Choose a typed value | Select<T> |
the blue selection moves to Formal |
| Confirm an action | Confirm |
Yes is focused and submitted |
| Validate before advancing | required/custom validators | the field keeps its value and shows the error |
| Navigate a guided flow | groups and ordered fields | the focus rail moves through one form |
| Return typed values or cancellation | FieldKey<T> and FormOutcome |
ordinary output appears only after restoration |
Longer selections can filter and scroll a bounded set of rows. Inline placement can begin on a new line, reuse the current line, or start at a known column. Those settings are detailed in the task guides rather than duplicated in the first runnable example.
Presentation and terminal ownership
Section titled “Presentation and terminal ownership”Form::run is blocking. It owns input, redraws, cursor placement, and terminal
session restoration until the form is submitted, cancelled, resized beyond
the selected policy, or fails. The supplied core Theme determines its visual
roles, so prompts can match the application’s static output.
The default inline presentation keeps surrounding output in the primary buffer and leaves the answered form in scrollback. Its origin, width, and resize policy are explicit because terminal reflow can make a previously drawn region impossible to locate safely.
Form::run opens the default connection for a convenient inline prompt.
Form::run_with_terminal is the public caller-owned path for applications that
already opened an interactive connection. Both use the inline presentation in
0.1.0. The prompt architecture also defines an alternate-screen presentation
that shares the same form and field model, but selecting that presentation is
not yet public.