main
 1use crate::protocol::Event;
 2use ratatui::style::{Color, Modifier, Style};
 3use ratatui::text::{Line as TextLine, Span};
 4
 5pub enum Row {
 6    Text(String),
 7    Thinking(String),
 8    Header(String),
 9    Error(String),
10}
11
12pub struct App {
13    pub rows: Vec<Row>,
14    pub input: String,
15    pub waiting: bool,
16    pub pending_prompt: Option<String>,
17    pub should_quit: bool,
18}
19
20impl App {
21    pub fn new() -> Self {
22        Self {
23            rows: Vec::new(),
24            input: String::new(),
25            waiting: false,
26            pending_prompt: None,
27            should_quit: false,
28        }
29    }
30
31    pub fn apply(&mut self, event: Event) {
32        if !matches!(event, Event::Waiting) {
33            self.waiting = false;
34        }
35
36        match event {
37            Event::Say { text, .. } => {
38                if !text.trim().is_empty() {
39                    self.rows.push(Row::Text(text));
40                }
41            }
42            Event::Print { text } => {
43                if !text.trim().is_empty() {
44                    self.rows.push(Row::Text(text));
45                }
46            }
47            Event::Thinking { text } => {
48                if !text.trim().is_empty() {
49                    self.rows.push(Row::Thinking(text));
50                }
51            }
52            Event::Doing { header } => self.rows.push(Row::Header(header)),
53            Event::Waiting => self.waiting = true,
54            Event::Oops { message } => self.rows.push(Row::Error(message)),
55            Event::DisplayFile { path, fallback } => {
56                self.rows.push(Row::Text(fallback.unwrap_or(path)));
57            }
58            Event::Ask { prompt } => self.pending_prompt = Some(prompt),
59        }
60    }
61
62    pub fn render_rows(&self) -> Vec<TextLine<'_>> {
63        self.rows
64            .iter()
65            .map(|row| match row {
66                Row::Text(t) => TextLine::from(t.as_str()),
67                Row::Thinking(t) => TextLine::from(Span::styled(
68                    t.as_str(),
69                    Style::default()
70                        .fg(Color::DarkGray)
71                        .add_modifier(Modifier::ITALIC),
72                )),
73                Row::Header(t) => TextLine::from(Span::styled(
74                    t.as_str(),
75                    Style::default().fg(Color::Cyan),
76                )),
77                Row::Error(t) => TextLine::from(Span::styled(
78                    format!("✗ {t}"),
79                    Style::default().fg(Color::Red),
80                )),
81            })
82            .collect()
83    }
84}