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}