Skip to main content

codex_ops/
prompt.rs

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}