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}
11
12impl TextRenderer {
13 pub fn new() -> Self {
14 let is_tty = io::stdout().is_terminal();
15 Self {
16 spinner: None,
17 is_tty,
18 }
19 }
20
21 fn clear_spinner(&mut self) {
22 if let Some(spinner) = self.spinner.take() {
23 spinner.finish_and_clear();
24 }
25 }
26
27 fn show_spinner(&mut self, message: &str) {
28 self.clear_spinner();
29 if self.is_tty {
30 let pb = ProgressBar::new_spinner();
31 pb.set_style(
32 ProgressStyle::with_template("{spinner:.cyan} {msg}")
33 .unwrap()
34 .tick_chars("⠋⠙⠹⠸⠼⠴⠦⠧⠇⠏"),
35 );
36 pb.set_message(message.to_string());
37 pb.enable_steady_tick(std::time::Duration::from_millis(80));
38 self.spinner = Some(pb);
39 } else {
40 eprintln!("{message}");
41 }
42 }
43}
44
45impl Default for TextRenderer {
46 fn default() -> Self {
47 Self::new()
48 }
49}
50
51impl OutputRenderer for TextRenderer {
52 fn text_chunk(&mut self, text: &str) {
53 self.clear_spinner();
54 print!("{text}");
55 let _ = io::stdout().flush();
56 }
57
58 fn tool_status(&mut self, tool: &str) {
59 self.show_spinner(&format!("Using tool: {tool}"));
60 }
61
62 fn permission_denied(&mut self, tool: &str) {
63 self.clear_spinner();
64 eprintln!("Permission denied: {tool}");
65 }
66
67 fn error(&mut self, err: &str) {
68 self.clear_spinner();
69 eprintln!("Error: {err}");
70 }
71
72 fn session_info(&mut self, id: &str) {
73 self.clear_spinner();
74 eprintln!("Session: {id}");
75 }
76
77 fn done(&mut self) {
78 self.clear_spinner();
79 let _ = io::stdout().flush();
80 }
81}