Skip to main content

actr_cli/core/components/
user_interface.rs

1use anyhow::{Context, Result};
2use async_trait::async_trait;
3use dialoguer::{Confirm, Select, theme::ColorfulTheme};
4use std::io::{self, Write};
5
6use crate::core::{ActrCliError, ProgressBar, ServiceInfo, UserInterface};
7
8pub struct ConsoleUI;
9
10impl Default for ConsoleUI {
11    fn default() -> Self {
12        Self::new()
13    }
14}
15
16impl ConsoleUI {
17    pub fn new() -> Self {
18        Self
19    }
20
21    fn read_line(&self) -> Result<String> {
22        let mut input = String::new();
23        io::stdin()
24            .read_line(&mut input)
25            .context("Failed to read from stdin")?;
26        Ok(input.trim().to_string())
27    }
28}
29
30#[async_trait]
31impl UserInterface for ConsoleUI {
32    async fn prompt_input(&self, prompt: &str) -> Result<String> {
33        print!("{prompt}: ");
34        io::stdout().flush().context("Failed to flush stdout")?;
35        self.read_line()
36    }
37
38    async fn confirm(&self, message: &str) -> Result<bool> {
39        Confirm::with_theme(&ColorfulTheme::default())
40            .with_prompt(message)
41            .default(false)
42            .interact()
43            .context("Failed to get confirmation")
44    }
45
46    async fn select_from_list(&self, items: &[String], prompt: &str) -> Result<usize> {
47        if items.is_empty() {
48            return Err(anyhow::anyhow!("Cannot select from an empty list"));
49        }
50
51        let selection = Select::with_theme(&ColorfulTheme::default())
52            .with_prompt(prompt)
53            .items(items)
54            .default(0)
55            .interact_opt()
56            .context("Failed to select from list")?;
57
58        match selection {
59            Some(index) => Ok(index),
60            None => Err(ActrCliError::OperationCancelled.into()),
61        }
62    }
63
64    async fn display_service_table(
65        &self,
66        _items: &[ServiceInfo],
67        _headers: &[&str],
68        _formatter: fn(&ServiceInfo) -> Vec<String>,
69    ) {
70        // Discovery command currently implements its own table display
71    }
72
73    async fn show_progress(&self, message: &str) -> Result<Box<dyn ProgressBar>> {
74        println!("⏳ {message}...");
75        Ok(Box::new(ConsoleProgressBar))
76    }
77}
78
79pub struct ConsoleProgressBar;
80
81impl ProgressBar for ConsoleProgressBar {
82    fn update(&self, _progress: f64) {}
83
84    fn set_message(&self, message: &str) {
85        println!("⏳ {message}...");
86    }
87
88    fn finish(&self) {}
89}