1use crate::{
4 utils::{is_abort_event, print_input_icon, print_state_icon, PromptState},
5 Prompt,
6};
7use async_trait::async_trait;
8use crossterm::{
9 cursor,
10 event::{Event, EventStream, KeyCode, KeyEvent, KeyModifiers},
11 queue,
12 style::{style, Attribute, Color, Print, PrintStyledContent},
13 terminal::{disable_raw_mode, enable_raw_mode, Clear, ClearType},
14};
15use futures::StreamExt;
16use std::fmt;
17use std::io::{stdout, Write};
18
19#[derive(Default)]
38pub struct ConfirmPrompt {
39 message: String,
40 state: PromptState,
41 answer: bool,
42 initial: Option<bool>,
43}
44impl fmt::Debug for ConfirmPrompt {
45 fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
46 fmt.debug_struct("ConfirmPrompt")
47 .field("message", &self.message)
48 .field("initial", &self.initial)
49 .finish()
50 }
51}
52impl ConfirmPrompt {
53 pub fn new<S>(message: S) -> ConfirmPrompt
59 where
60 S: Into<String>,
61 {
62 ConfirmPrompt {
63 message: message.into(),
64 ..Default::default()
65 }
66 }
67
68 pub fn set_initial(mut self, initial: bool) -> ConfirmPrompt {
70 self.initial = Some(initial);
71 self
72 }
73}
74#[async_trait]
75impl Prompt<bool> for ConfirmPrompt {
76 async fn run(&mut self) -> std::result::Result<Option<bool>, crossterm::ErrorKind> {
81 enable_raw_mode()?;
82 let mut reader = EventStream::new();
83
84 self.display()?;
85
86 loop {
87 match reader.next().await {
88 Some(Ok(Event::Key(event))) => self.handle_key_event(event),
89 Some(Err(e)) => {
90 disable_raw_mode()?;
91 return Err(e);
92 }
93 _ => {}
94 }
95
96 self.display()?;
97
98 match self.state {
99 PromptState::Aborted => {
100 disable_raw_mode()?;
101 return Ok(None);
102 }
103 PromptState::Success => {
104 disable_raw_mode()?;
105 return Ok(Some(self.answer));
106 }
107 _ => (),
108 }
109 }
110 }
111 fn display(&mut self) -> crossterm::Result<()> {
112 let mut stdout = stdout();
113
114 queue!(
115 stdout,
116 cursor::MoveToColumn(0),
117 Clear(ClearType::FromCursorDown),
118 print_state_icon(&self.state),
119 Print(" "),
120 PrintStyledContent(style(&self.message).attribute(Attribute::Bold)),
121 Print(" "),
122 print_input_icon(&self.state),
123 )?;
124 if !self.state.is_done() {
125 queue!(
126 stdout,
127 PrintStyledContent(
128 style(match self.initial {
129 Some(true) => "(Y/n)",
130 Some(false) => "(y/N)",
131 None => "(y/n)",
132 })
133 .with(Color::DarkGrey)
134 )
135 )?;
136 }
137 if self.state == PromptState::Success {
138 queue!(stdout, Print(if self.answer { "yes" } else { "no" }))?;
139 }
140 if self.state.is_done() {
141 queue!(stdout, Print("\n\r"), cursor::Show)?;
142 }
143 stdout.flush()?;
144 crossterm::Result::Ok(())
145 }
146 fn handle_key_event(&mut self, event: KeyEvent) {
147 if is_abort_event(event) {
148 self.state = PromptState::Aborted;
149 return;
150 }
151 if event.modifiers == KeyModifiers::empty() {
152 match event.code {
153 KeyCode::Enter => {
154 if let Some(initial) = self.initial {
155 self.answer = initial;
156 self.state = PromptState::Success;
157 }
158 }
159 KeyCode::Char('y') => {
160 self.answer = true;
161 self.state = PromptState::Success;
162 }
163 KeyCode::Char('n') => {
164 self.answer = false;
165 self.state = PromptState::Success;
166 }
167 _ => {}
168 }
169 }
170 }
171}