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}