1pub mod app;
2pub mod commands;
3pub mod control;
4pub mod conversation;
5pub mod help;
6pub mod input;
7pub mod settings;
8pub mod sidebar;
9pub mod theme;
10
11use std::time::Duration;
12
13use crossterm::{
14 event::{
15 self, DisableBracketedPaste, DisableMouseCapture, EnableBracketedPaste, EnableMouseCapture,
16 Event as CrosstermEvent, KeyboardEnhancementFlags, PopKeyboardEnhancementFlags,
17 PushKeyboardEnhancementFlags,
18 },
19 execute,
20 terminal::{EnterAlternateScreen, LeaveAlternateScreen, disable_raw_mode, enable_raw_mode},
21};
22use ratatui::{Terminal, backend::CrosstermBackend};
23use tokio::sync::mpsc;
24use tuillem_core::actions::Event;
25
26use tracing::debug;
27
28use crate::app::App;
29
30pub async fn run(
31 mut app: App,
32 mut event_rx: mpsc::UnboundedReceiver<Event>,
33 mouse_enabled: bool,
34) -> anyhow::Result<()> {
35 let (cols, rows) = crossterm::terminal::size()?;
37 if cols < 60 || rows < 20 {
38 anyhow::bail!("Terminal too small ({cols}x{rows}). Minimum size is 60x20.");
39 }
40
41 enable_raw_mode()?;
43 let mut stdout = std::io::stdout();
44
45 let keyboard_enhanced = execute!(
47 stdout,
48 PushKeyboardEnhancementFlags(KeyboardEnhancementFlags::REPORT_ALL_KEYS_AS_ESCAPE_CODES)
49 )
50 .is_ok();
51
52 if mouse_enabled {
53 execute!(
54 stdout,
55 EnterAlternateScreen,
56 EnableMouseCapture,
57 EnableBracketedPaste
58 )?;
59 } else {
60 execute!(stdout, EnterAlternateScreen, EnableBracketedPaste)?;
61 }
62 let backend = CrosstermBackend::new(stdout);
63 let mut terminal = Terminal::new(backend)?;
64
65 loop {
67 if app.needs_redraw {
69 terminal.clear()?;
70 app.needs_redraw = false;
71 }
72
73 terminal.draw(|frame| {
75 app.draw(frame);
76 })?;
77
78 let mut event_count = 0;
80 while let Ok(event) = event_rx.try_recv() {
81 debug!("TUI received event: {:?}", event);
82 app.apply_event(&event);
83 event_count += 1;
84 }
85 if event_count > 0 {
86 debug!("TUI processed {} events this frame", event_count);
87 }
88
89 if event::poll(Duration::from_millis(16))? {
91 match event::read()? {
92 CrosstermEvent::Key(key) => {
93 app.handle_key_event(key);
94 }
95 CrosstermEvent::Mouse(mouse) => {
96 app.handle_mouse_event(mouse);
97 }
98 CrosstermEvent::Paste(text) => {
99 app.handle_paste(text);
100 }
101 _ => {}
102 }
103 }
104
105 if app.should_quit {
106 break;
107 }
108 }
109
110 if keyboard_enhanced {
112 let _ = execute!(terminal.backend_mut(), PopKeyboardEnhancementFlags);
113 }
114 disable_raw_mode()?;
115 if mouse_enabled {
116 execute!(
117 terminal.backend_mut(),
118 DisableMouseCapture,
119 DisableBracketedPaste,
120 LeaveAlternateScreen
121 )?;
122 } else {
123 execute!(
124 terminal.backend_mut(),
125 DisableBracketedPaste,
126 LeaveAlternateScreen
127 )?;
128 }
129 terminal.show_cursor()?;
130
131 Ok(())
132}