cli_prompts/prompts/
mod.rs

1//! The module that contains the high-level abstractions for the prompts. 
2//!
3//! To implement a new prompt, you need to implement the `Prompt` trait, which 
4//! requires a method to draw the prompt and to react to input. Having implemented
5//! this trait, you will be able to call `display()` on your prompt object which 
6//! handle the rest
7
8mod confirmation;
9mod input;
10mod options;
11
12pub use confirmation::Confirmation;
13pub use input::Input;
14pub use options::multiselect::Multiselect;
15pub use options::selection::Selection;
16pub use options::{Options, multioption_prompt::MultiOptionPrompt};
17
18use std::io::stdout;
19
20use crate::{
21    engine::{Clear, CommandBuffer, CrosstermEngine, Engine},
22    input::Key,
23};
24
25/// Describes the reason for prompt abortion
26#[derive(Debug)]
27pub enum AbortReason {
28
29    /// The prompt was interrupted by the user
30    Interrupt,
31
32    /// The error occured with I/O
33    Error(std::io::Error),
34}
35
36/// This describes the final result of the prompt
37#[derive(Debug)]
38pub enum EventOutcome<T> {
39
40    /// The prompt has successfully completed.
41    /// The inner field will contain the result.
42    Done(T),
43
44    /// This signals that the prompt hasn't completed and should continue
45    Continue,
46
47    /// Prompt has been aborted
48    Abort(AbortReason),
49}
50
51/// The trait for defining interactive prompts.
52///
53/// ```rust
54///use cli_prompts::{
55///   prompts::{Prompt, EventOutcome, AbortReason},
56///   engine::CommandBuffer,
57///   style::{Formatting, Color},
58///   input::Key,
59///};
60///
61///struct MyPrompt {
62///   name: String
63///}
64///
65/// impl Prompt<String> for MyPrompt {
66///    fn draw(&self, commands: &mut impl CommandBuffer) {
67///       commands.print("Input your name: ");
68///       commands.set_formatting(&Formatting::default().foreground_color(Color::Green));
69///       commands.print(&self.name);
70///       commands.reset_formatting();
71///    }
72///
73///    fn on_key_pressed(&mut self, key: Key) -> EventOutcome<String> {
74///           match key {
75///               Key::Char(c) => {
76///               self.name.push(c);
77///               EventOutcome::Continue
78///           },
79///           Key::Backspace => {
80///               self.name.pop();
81///               EventOutcome::Continue
82///           },
83///           Key::Enter => EventOutcome::Done(self.name.clone()),
84///           Key::Esc => EventOutcome::Abort(AbortReason::Interrupt),
85///           _ => EventOutcome::Continue,
86///       }
87///    }
88/// }
89/// ```
90pub trait Prompt<TOut> {
91
92    /// Defines how to draw the prompt with a set of commands.
93    /// The goal of this method is to populate the `commands` buffer
94    /// with a set of commands that will draw your prompt to the screen.
95    fn draw(&self, commands: &mut impl CommandBuffer);
96
97    /// This should handle the keyboard key presses. Should return the 
98    /// outcome of the keypress:
99    /// - EventOutcome::Continue - the input was handled and the prompt should continue displaying
100    /// - EventOutcome::Done(TOut) - the prompt has successfully completed. Pass the result as the
101    /// enum's field
102    /// - EventOutcome::Abort(AbortReason) - the prompt has finished abruptly. Specify a reason in
103    /// the enum's field
104    fn on_key_pressed(&mut self, key: Key) -> EventOutcome<TOut>;
105}
106
107/// A trait that is implemented for every type that implements `Prompt`. Provides a convenient way
108/// to display a prompt on the screen, handle the input, raw mode, etc.
109pub trait DisplayPrompt<T> {
110
111    /// Draws the prompt on the screen and handles the input.
112    /// - Returns `Ok(T)` if the prompt is completed successfully.
113    /// - Returns `Err(AbortReason)` if it failed. Check the `AbortReason` to find out why
114    fn display(self) -> Result<T, AbortReason>;
115}
116
117impl<T, P> DisplayPrompt<T> for P
118where
119    P: Prompt<T> + Sized,
120{
121    fn display(mut self) -> Result<T, AbortReason> {
122        let buffer = stdout();
123        let mut engine = CrosstermEngine::new(buffer);
124        let mut commands = engine.get_command_buffer();
125
126        loop {
127            self.draw(&mut commands);
128            engine.render(&commands)?;
129
130            let key_pressed = engine.read_key()?;
131            match self.on_key_pressed(key_pressed) {
132                EventOutcome::Done(result) => {
133                    commands.clear();
134                    self.draw(&mut commands);
135                    engine.render(&commands)?;
136                    engine.finish_rendering()?;
137
138                    return Ok(result);
139                }
140                EventOutcome::Continue => {
141                    commands.clear();
142                    continue;
143                }
144                EventOutcome::Abort(reason) => return Err(reason),
145            }
146        }
147    }
148}
149
150impl From<std::io::Error> for AbortReason {
151    fn from(error: std::io::Error) -> Self {
152        AbortReason::Error(error)
153    }
154}