1use crate::error::AppError;
2use dialoguer::theme::ColorfulTheme;
3use dialoguer::{Confirm, MultiSelect, Select};
4use std::io::{self, ErrorKind, IsTerminal};
5
6pub trait Prompt {
7 fn select(&mut self, prompt: &str, items: &[String]) -> Result<Option<usize>, AppError>;
8
9 fn multi_select(
10 &mut self,
11 prompt: &str,
12 items: &[String],
13 ) -> Result<Option<Vec<usize>>, AppError>;
14
15 fn confirm(&mut self, prompt: &str, default: bool) -> Result<Option<bool>, AppError>;
16}
17
18#[derive(Default)]
19pub struct DialoguerPrompt {
20 theme: ColorfulTheme,
21}
22
23impl Prompt for DialoguerPrompt {
24 fn select(&mut self, prompt: &str, items: &[String]) -> Result<Option<usize>, AppError> {
25 Select::with_theme(&self.theme)
26 .with_prompt(prompt)
27 .items(items)
28 .default(0)
29 .interact_opt()
30 .map_err(prompt_error)
31 }
32
33 fn multi_select(
34 &mut self,
35 prompt: &str,
36 items: &[String],
37 ) -> Result<Option<Vec<usize>>, AppError> {
38 MultiSelect::with_theme(&self.theme)
39 .with_prompt(prompt)
40 .items(items)
41 .interact_opt()
42 .map_err(prompt_error)
43 }
44
45 fn confirm(&mut self, prompt: &str, default: bool) -> Result<Option<bool>, AppError> {
46 Confirm::with_theme(&self.theme)
47 .with_prompt(prompt)
48 .default(default)
49 .interact_opt()
50 .map_err(prompt_error)
51 }
52}
53
54pub fn stdin_and_stderr_are_terminals() -> bool {
55 io::stdin().is_terminal() && io::stderr().is_terminal()
56}
57
58fn prompt_error(error: dialoguer::Error) -> AppError {
59 match error {
60 dialoguer::Error::IO(error) if error.kind() == ErrorKind::Interrupted => {
61 AppError::new("Prompt cancelled.")
62 }
63 error => AppError::new(format!("Prompt failed: {error}")),
64 }
65}