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 }
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}