1use std::io;
6
7use anyhow::Result;
8use crossterm::{
9 event::{
10 self, DisableMouseCapture, EnableMouseCapture, Event, KeyCode, KeyEventKind, KeyModifiers,
11 },
12 execute,
13 terminal::{disable_raw_mode, enable_raw_mode, EnterAlternateScreen, LeaveAlternateScreen},
14};
15use ratatui::{backend::CrosstermBackend, Terminal};
16
17use super::app::{App, AppMode, ExportFormat};
18use super::ui;
19
20pub fn run_tui() -> Result<()> {
22 enable_raw_mode()?;
24 let mut stdout = io::stdout();
25 execute!(stdout, EnterAlternateScreen, EnableMouseCapture)?;
26 let backend = CrosstermBackend::new(stdout);
27 let mut terminal = Terminal::new(backend)?;
28
29 let mut app = App::new()?;
31
32 let res = run_app(&mut terminal, &mut app);
34
35 disable_raw_mode()?;
37 execute!(
38 terminal.backend_mut(),
39 LeaveAlternateScreen,
40 DisableMouseCapture
41 )?;
42 terminal.show_cursor()?;
43
44 if let Err(err) = res {
45 eprintln!("Error: {}", err);
46 }
47
48 Ok(())
49}
50
51fn run_app<B: ratatui::backend::Backend>(terminal: &mut Terminal<B>, app: &mut App) -> Result<()> {
53 terminal.draw(|f| ui::render(f, app))?;
55
56 loop {
57 if let Event::Key(key) = event::read()? {
59 if key.kind != KeyEventKind::Press {
61 continue;
62 }
63
64 if !app.confirm_delete {
66 app.status_message = None;
67 }
68
69 if app.export_picker_active {
71 match key.code {
72 KeyCode::Char('j') | KeyCode::Down => {
73 let max = ExportFormat::all().len();
74 if app.export_format_index + 1 < max {
75 app.export_format_index += 1;
76 }
77 }
78 KeyCode::Char('k') | KeyCode::Up => {
79 if app.export_format_index > 0 {
80 app.export_format_index -= 1;
81 }
82 }
83 KeyCode::Enter => app.confirm_export(),
84 KeyCode::Esc => app.cancel_export(),
85 KeyCode::Char('1') => {
86 app.export_format_index = 0;
87 app.confirm_export();
88 }
89 KeyCode::Char('2') => {
90 app.export_format_index = 1;
91 app.confirm_export();
92 }
93 KeyCode::Char('3') => {
94 app.export_format_index = 2;
95 app.confirm_export();
96 }
97 KeyCode::Char('4') => {
98 app.export_format_index = 3;
99 app.confirm_export();
100 }
101 _ => {}
102 }
103 terminal.draw(|f| ui::render(f, app))?;
104 continue;
105 }
106
107 if app.search_active {
109 match key.code {
110 KeyCode::Enter => app.execute_search(),
111 KeyCode::Esc => app.cancel_search(),
112 KeyCode::Backspace => app.search_backspace(),
113 KeyCode::Char(c) => app.search_input(c),
114 _ => {}
115 }
116 terminal.draw(|f| ui::render(f, app))?;
117 continue;
118 }
119
120 if app.filter_active {
122 match key.code {
123 KeyCode::Enter => app.confirm_filter(),
124 KeyCode::Esc => app.cancel_filter(),
125 KeyCode::Backspace => app.filter_backspace(),
126 KeyCode::Char(c) => app.filter_input(c),
127 _ => {}
128 }
129 terminal.draw(|f| ui::render(f, app))?;
130 continue;
131 }
132
133 if app.session_filter_active {
135 match key.code {
136 KeyCode::Enter => app.confirm_session_filter(),
137 KeyCode::Esc => app.cancel_session_filter(),
138 KeyCode::Backspace => app.session_filter_backspace(),
139 KeyCode::Char(c) => app.session_filter_input(c),
140 _ => {}
141 }
142 terminal.draw(|f| ui::render(f, app))?;
143 continue;
144 }
145
146 if app.mode == AppMode::Help {
148 app.back();
149 terminal.draw(|f| ui::render(f, app))?;
150 continue;
151 }
152
153 match key.code {
155 KeyCode::Char('q') => {
156 return Ok(());
157 }
158 KeyCode::Char('c') if key.modifiers.contains(KeyModifiers::CONTROL) => {
159 return Ok(());
160 }
161 KeyCode::Char('?') => {
162 app.toggle_help();
163 }
164 KeyCode::Char('j') | KeyCode::Down => {
166 app.navigate_down();
167 }
168 KeyCode::Char('k') | KeyCode::Up => {
169 app.navigate_up();
170 }
171 KeyCode::Char('g') => {
172 app.go_to_top();
173 }
174 KeyCode::Char('G') => {
175 app.go_to_bottom();
176 }
177 KeyCode::PageUp => {
178 app.page_up();
179 }
180 KeyCode::PageDown => {
181 app.page_down();
182 }
183 KeyCode::Enter => {
184 app.enter();
185 }
186 KeyCode::Esc | KeyCode::Backspace => {
187 if app.confirm_delete {
188 app.cancel_delete();
189 } else {
190 app.back();
191 }
192 }
193 KeyCode::Char('/') if matches!(app.mode, AppMode::Workspaces | AppMode::Sessions) => {
195 app.start_filter();
196 }
197 KeyCode::Char('s') if !matches!(app.mode, AppMode::SessionDetail) => {
199 app.start_search();
200 }
201 KeyCode::Char('r') => {
203 app.refresh();
204 }
205 KeyCode::Char('e') if matches!(app.mode, AppMode::Sessions | AppMode::SessionDetail | AppMode::SearchResults) => {
207 app.export_current_session();
208 }
209 KeyCode::Char('o') if app.mode == AppMode::Sessions => {
211 app.cycle_sort();
212 }
213 KeyCode::Char('d') if matches!(app.mode, AppMode::Sessions | AppMode::SessionDetail) => {
215 app.delete_current_session();
216 }
217 KeyCode::Char('y') if matches!(app.mode, AppMode::Sessions | AppMode::SessionDetail | AppMode::SearchResults) => {
219 app.yank_session();
220 }
221 _ => continue, }
223
224 terminal.draw(|f| ui::render(f, app))?;
226 }
227 }
228}