Skip to main content

acp_cli/output/
text.rs

1use std::io::{self, IsTerminal, Write};
2
3use indicatif::{ProgressBar, ProgressStyle};
4
5use super::OutputRenderer;
6
7pub struct TextRenderer {
8    spinner: Option<ProgressBar>,
9    is_tty: bool,
10    suppress_reads: bool,
11}
12
13impl TextRenderer {
14    pub fn new(suppress_reads: bool) -> Self {
15        let is_tty = io::stdout().is_terminal();
16        Self {
17            spinner: None,
18            is_tty,
19            suppress_reads,
20        }
21    }
22
23    fn clear_spinner(&mut self) {
24        if let Some(spinner) = self.spinner.take() {
25            spinner.finish_and_clear();
26        }
27    }
28
29    fn show_spinner(&mut self, message: &str) {
30        self.clear_spinner();
31        if self.is_tty {
32            let pb = ProgressBar::new_spinner();
33            pb.set_style(
34                ProgressStyle::with_template("{spinner:.cyan} {msg}")
35                    .unwrap()
36                    .tick_chars("⠋⠙⠹⠸⠼⠴⠦⠧⠇⠏"),
37            );
38            pb.set_message(message.to_string());
39            pb.enable_steady_tick(std::time::Duration::from_millis(80));
40            self.spinner = Some(pb);
41        } else {
42            eprintln!("{message}");
43        }
44    }
45}
46
47impl OutputRenderer for TextRenderer {
48    fn text_chunk(&mut self, text: &str) {
49        self.clear_spinner();
50        print!("{text}");
51        let _ = io::stdout().flush();
52    }
53
54    fn tool_status(&mut self, tool: &str) {
55        self.show_spinner(&format!("Using tool: {tool}"));
56    }
57
58    fn tool_result(&mut self, _tool: &str, output: &str, is_read: bool) {
59        self.clear_spinner();
60        if self.suppress_reads && is_read {
61            eprintln!("  [read suppressed — {} bytes]", output.len());
62        }
63        // Non-read tools: result body is not displayed in text mode to avoid
64        // cluttering the terminal. The agent's own response text (via TextChunk)
65        // describes what was found.
66    }
67
68    fn permission_denied(&mut self, tool: &str) {
69        self.clear_spinner();
70        eprintln!("Permission denied: {tool}");
71    }
72
73    fn error(&mut self, err: &str) {
74        self.clear_spinner();
75        eprintln!("Error: {err}");
76    }
77
78    fn session_info(&mut self, id: &str) {
79        self.clear_spinner();
80        eprintln!("Session: {id}");
81    }
82
83    fn done(&mut self) {
84        self.clear_spinner();
85        let _ = io::stdout().flush();
86    }
87}