cli_ui/prompt/core/mod.rs
1//! Core abstractions every prompt implements.
2//!
3//! A prompt is three small things:
4//!
5//! ```text
6//! impl Prompt for MyPrompt {
7//! type Output = String;
8//! fn handle(&mut self, key: Key) -> Step<String> { ... }
9//! fn render(&self, ctx: RenderCtx) -> Frame { ... }
10//! fn render_answered(&self, v: &String) -> Frame { ... }
11//! }
12//! ```
13//!
14//! The generic [`run`] function in `core::runner` owns everything
15//! else: raw mode, the key loop, line-count bookkeeping, the validate
16//! → success transition, the answered redraw, and Ctrl+C cleanup. Prompts
17//! never touch crossterm or stderr directly.
18
19mod frame;
20mod runner;
21mod step;
22
23pub use frame::{Frame, RenderCtx};
24pub use runner::run;
25pub use step::Step;
26
27pub use super::engine::Key;
28use super::error::Result;
29
30/// The contract every interactive prompt implements.
31pub trait Prompt {
32 /// The value returned to the caller on successful submission.
33 type Output;
34
35 /// Apply a key press to the prompt's internal state. Return:
36 ///
37 /// * [`Step::Continue`] — state changed, but we're not done.
38 /// * `Step::Submit(v)` — done, hand `v` back to the caller.
39 /// * `Step::Reject(msg)` — block submission, show `msg` as an error
40 /// below the input. The runner clears the error when the next key
41 /// arrives.
42 /// * [`Step::Cancel`] — user asked to abort (Esc / Ctrl-C).
43 fn handle(&mut self, key: Key) -> Step<Self::Output>;
44
45 /// Render the prompt's current state as one frame (a `Vec<String>`,
46 /// one entry per terminal row). The runner takes care of writing the
47 /// frame and clearing it on the next iteration — prompts never call
48 /// `eprintln!` themselves.
49 fn render(&self, ctx: RenderCtx) -> Frame;
50
51 /// Render the prompt's post-submission display. Typically the
52 /// `◇ question / │ value / │` triple from `theme::answered`.
53 fn render_answered(&self, value: &Self::Output) -> Frame;
54
55 /// Optional non-interactive fallback for piped/CI environments. The
56 /// default is to refuse — most prompts override this with a numeric
57 /// or line-buffered alternative.
58 fn run_fallback(self) -> Result<Self::Output>
59 where
60 Self: Sized,
61 {
62 Err(super::error::PromptError::Interrupted)
63 }
64}