cli_prompts/prompts/input.rs
1use super::Prompt;
2use crate::{
3 engine::CommandBuffer,
4 input::Key,
5 prompts::{AbortReason, EventOutcome},
6 style::InputStyle,
7};
8
9/// This is a normal text input prompt with the following features:
10/// - Custom label
11/// - Validation of the input with error reporting
12/// - Transformation of the text input to arbitrary Rust type
13/// - Optional default value
14/// - Optional help message
15/// - Customizable colors and formatting
16///
17/// ```rust
18/// use cli_prompts::{
19/// prompts::{Input, AbortReason::{self, Error}},
20/// style::{InputStyle, Formatting},
21/// DisplayPrompt
22/// };
23///
24/// fn validate_and_transform(input: &str) -> Result<u16, String> {
25/// input
26/// .parse::<u16>()
27/// .map_err(|e| "Provided input is not a valid time of the day".into())
28/// .and_then(|n| if n <= 24 { Ok(n) } else { Err("Provided input is not a valid time of the day".into()) })
29/// }
30///
31/// fn main() {
32/// let input = Input::new("At what time do you usually eat lunch?", validate_and_transform)
33/// .default_value("12")
34/// .help_message("Enter an hour of the day as a number from 0 to 24")
35/// .style(InputStyle::default()
36/// .default_value_formatting(Formatting::default().bold())
37/// );
38///
39/// let lunch_time : Result<u16, AbortReason> = input.display();
40/// match lunch_time {
41/// Ok(time) => println!("You eat lunch at {} o'clock", time),
42/// Err(abort_reason) => match abort_reason {
43/// Interrupt => println!("The prompt was interrupted by pressing the ESC key"),
44/// Error(err) => println!("I/O error has occured: {:?}", err),
45/// }
46/// }
47///
48/// }
49/// ```
50pub struct Input<F> {
51 label: String,
52 input: String,
53 help_message: Option<String>,
54 is_first_input: bool,
55 is_submitted: bool,
56 error: Option<String>,
57 validation: F,
58 style: InputStyle,
59}
60
61impl<F, T> Input<F>
62where
63 F: Fn(&str) -> Result<T, String>,
64{
65 /// Constructs an input prompt with a given label and a validation function
66 /// The function serves both as validator and transformer: it should return `Ok`
67 /// of the arbitrary type `T` if validation passed and `Err(String)` if it failed.
68 /// The containing String will be displayed as an error message and the prompt will continue
69 /// until this function returns Ok
70 pub fn new(label: impl Into<String>, validation: F) -> Self {
71 Self {
72 label: label.into(),
73 input: String::new(),
74 help_message: None,
75 is_first_input: true,
76 is_submitted: false,
77 error: None,
78 validation,
79 style: InputStyle::default(),
80 }
81 }
82
83 /// Sets a help message which will be displayed after the input string
84 /// until the prompt is completed
85 pub fn help_message<S: Into<String>>(mut self, message: S) -> Self {
86 self.help_message = Some(message.into());
87 self
88 }
89
90 /// Sets the default value for the prompt. It is cleared once any key
91 /// is pressed other than Enter.
92 pub fn default_value<S: Into<String>>(mut self, val: S) -> Self {
93 self.input = val.into();
94 self
95 }
96
97 /// Sets the style for the prompt
98 pub fn style(mut self, style: InputStyle) -> Self {
99 self.style = style;
100 self
101 }
102}
103
104impl<T, F> Prompt<T> for Input<F>
105where
106 F: Fn(&str) -> Result<T, String>,
107{
108 fn draw(&self, commands: &mut impl CommandBuffer) {
109 self.style.label_style.print(&self.label, commands);
110
111 if let Some(error) = self.error.as_ref() {
112 self.style
113 .error_formatting
114 .print(format!("[{}]", error), commands);
115 } else if self.is_submitted {
116 self.style.submitted_formatting.print(&self.input, commands);
117 } else if self.is_first_input && self.input.len() > 0 {
118 self.style
119 .default_value_formatting
120 .print(format!("[{}]", self.input), commands);
121 } else if !self.is_first_input {
122 self.style.input_formatting.print(&self.input, commands);
123 }
124
125 if let Some(help_message) = self.help_message.as_ref() {
126 self.style
127 .help_message_formatting
128 .print(format!("[{}]", help_message), commands);
129 }
130 }
131
132 fn on_key_pressed(&mut self, key: Key) -> EventOutcome<T> {
133 let is_first_input = self.is_first_input;
134 self.is_first_input = false;
135 match key {
136 Key::Char(c) => {
137 if is_first_input {
138 self.input.clear();
139 }
140 self.error = None;
141 self.input.push(c);
142 EventOutcome::Continue
143 }
144 Key::Backspace => {
145 if is_first_input {
146 self.input.clear();
147 }
148 self.error = None;
149 self.input.pop();
150 EventOutcome::Continue
151 }
152 Key::Enter => {
153 self.error = (self.validation)(&self.input).err();
154 match self.error {
155 Some(_) => {
156 self.input.clear();
157 EventOutcome::Continue
158 }
159 None => {
160 self.is_submitted = true;
161 EventOutcome::Done((self.validation)(&self.input).unwrap())
162 }
163 }
164 }
165 Key::Esc => EventOutcome::Abort(AbortReason::Interrupt),
166 _ => EventOutcome::Continue,
167 }
168 }
169}