cli_prompts/prompts/
confirmation.rs

1use crate::{engine::CommandBuffer, input::Key, prompts::EventOutcome, style::ConfirmationStyle};
2
3use super::Prompt;
4
5/// A prompt that expects a "yes or no" answer.
6/// You can press 'y' or 'n' for positive or negative result.
7/// Pressing 'Enter' without any previous input will trigger the default ansewer, which is
8/// configurable
9///
10/// ```rust
11/// use cli_prompts::{
12///     prompts::{Confirmation, AbortReason},
13///     style::{ConfirmationStyle, Formatting},
14///     DisplayPrompt,
15/// };
16///
17/// fn main() {
18///     let prompt = Confirmation::new("Are you sure you want to delete this file?")
19///                     .default_positive(false)
20///                     .style(ConfirmationStyle::default());
21///     let answer : Result<bool, AbortReason> = prompt.display();
22///     match answer {
23///         Ok(is_positive) => println!("The answer is {}", is_positive),
24///         Err(abort_reason) => println!("The prompt was aborted because of {:?}", abort_reason),
25///     }
26/// }
27/// ```
28pub struct Confirmation {
29    label: String,
30    default_positive: bool,
31    is_submitted: bool,
32    selected_option: Option<bool>,
33    style: ConfirmationStyle,
34}
35
36impl Confirmation {
37
38    /// Constructs a new prompt with a given label
39    pub fn new<S: Into<String>>(label: S) -> Self {
40        Confirmation {
41            label: label.into(),
42            default_positive: true,
43            is_submitted: false,
44            selected_option: None,
45            style: ConfirmationStyle::default(),
46        }
47    }
48
49    /// Sets whether the default value is positive or negative
50    pub fn default_positive(mut self, default_positive: bool) -> Self {
51        self.default_positive = default_positive;
52        self
53    }
54
55    /// Sets the style of the prompt
56    pub fn style(mut self, s: ConfirmationStyle) -> Self {
57        self.style = s;
58        self
59    }
60}
61
62impl Prompt<bool> for Confirmation {
63    fn draw(&self, commands: &mut impl CommandBuffer) {
64        self.style.label_style.print(
65            format!(
66                "{} [{y}/{n}]",
67                self.label,
68                y = if self.default_positive { 'Y' } else { 'y' },
69                n = if !self.default_positive { 'N' } else { 'n' },
70            ),
71            commands,
72        );
73
74        let result: String = if let Some(is_positive) = self.selected_option.as_ref() {
75            if *is_positive {
76                "Yes".into()
77            } else {
78                "No".into()
79            }
80        } else {
81            String::new()
82        };
83
84        let formatting = if self.is_submitted {
85            &self.style.submitted_formatting
86        } else {
87            &self.style.input_formatting
88        };
89
90        formatting.print(result, commands);
91    }
92
93    fn on_key_pressed(&mut self, key: Key) -> EventOutcome<bool> {
94        match key {
95            Key::Enter => {
96                self.is_submitted = true;
97                if let Some(is_positive) = self.selected_option.as_ref() {
98                    EventOutcome::Done(*is_positive)
99                } else {
100                    self.selected_option = Some(self.default_positive);
101                    EventOutcome::Done(self.default_positive)
102                }
103            }
104            Key::Char(c) if self.selected_option.is_none() => match c {
105                'y' | 'Y' => {
106                    self.selected_option = Some(true);
107                    EventOutcome::Continue
108                }
109                'n' | 'N' => {
110                    self.selected_option = Some(false);
111                    EventOutcome::Continue
112                }
113                _ => EventOutcome::Continue,
114            },
115            Key::Backspace => {
116                self.selected_option = None;
117                EventOutcome::Continue
118            }
119            Key::Esc => EventOutcome::Abort(super::AbortReason::Interrupt),
120            _ => EventOutcome::Continue,
121        }
122    }
123}