1use super::themes::Theme;
4use crate::models::AliasEntry;
5
6#[derive(Debug, Clone, Copy)]
8pub enum ExitAction {
9 Execute,
11 Populate,
13}
14
15use super::state::{
17 EntryData, EntryFilter, FilterState, GroupMode, InputMode, InputState, NavigationState, Panel, ScrollManager,
18 SearchState, SortOrder, UiState,
19};
20
21pub struct App {
23 data: EntryData,
25 search: SearchState,
27 ui: UiState,
29 input: InputState,
31 navigation: NavigationState,
33 filter: FilterState,
35 theme: Theme,
37 pub should_quit: bool,
39 pub exit_action: Option<ExitAction>,
41}
42
43impl App {
44 pub fn new(
46 entries: Vec<AliasEntry>,
47 theme: Theme,
48 ) -> Self {
49 let mut app = Self {
50 data: EntryData::new(entries),
51 search: SearchState::new(),
52 ui: UiState::default(),
53 input: InputState::default(),
54 navigation: NavigationState::default(),
55 filter: FilterState::default(),
56 theme,
57 should_quit: false,
58 exit_action: None,
59 };
60
61 app.update_visible_entries();
63
64 app
65 }
66
67 pub fn selected_entry(&self) -> Option<&AliasEntry> {
69 self.data.get_visible_entry(self.navigation.selected_index())
70 }
71
72 pub fn update_visible_entries(&mut self) {
74 self.filter.update_visible_entries(&mut self.data, self.search.query());
75 self.navigation.clamp(self.data.visible_count());
76 self.ui.reset_detail_scroll();
77 }
78
79 pub fn entries(&self) -> &[AliasEntry] {
83 self.data.entries()
84 }
85
86 pub fn visible_indices(&self) -> &[usize] {
88 self.data.visible_indices()
89 }
90
91 pub fn search_query(&self) -> &str {
93 self.search.query()
94 }
95
96 pub fn cursor_position(&self) -> usize {
98 self.search.cursor_position()
99 }
100
101 pub fn selected_index(&self) -> usize {
103 self.navigation.selected_index()
104 }
105
106 pub fn input_mode(&self) -> InputMode {
108 self.input.mode()
109 }
110
111 pub fn active_panel(&self) -> Panel {
113 self.ui.active_panel()
114 }
115
116 pub fn filter(&self) -> EntryFilter {
118 self.filter.filter()
119 }
120
121 pub fn list_scroll_offset(&self) -> usize {
123 self.ui.list_scroll_offset()
124 }
125
126 pub fn description_scroll_offset(&self) -> usize {
128 self.ui.description_scroll_offset()
129 }
130
131 pub fn description_max_scroll(&self) -> usize {
133 self.ui.description_max_scroll()
134 }
135
136 pub fn script_scroll_offset(&self) -> usize {
138 self.ui.script_scroll_offset()
139 }
140
141 pub fn script_max_scroll(&self) -> usize {
143 self.ui.script_max_scroll()
144 }
145
146 pub fn pending_key(&self) -> Option<char> {
148 self.input.pending_key()
149 }
150
151 pub fn pending_key_time(&self) -> Option<std::time::Instant> {
153 self.input.pending_key_time()
154 }
155
156 pub fn show_help(&self) -> bool {
158 self.ui.show_help()
159 }
160
161 pub fn help_scroll_offset(&self) -> usize {
163 self.ui.help_scroll_offset()
164 }
165
166 pub fn help_max_scroll(&self) -> usize {
168 self.ui.help_max_scroll()
169 }
170
171 pub fn group_mode(&self) -> GroupMode {
173 self.filter.group_mode()
174 }
175
176 pub fn sort_order(&self) -> SortOrder {
178 self.filter.sort_order()
179 }
180
181 pub fn move_up(&mut self) {
185 self.navigation.move_up();
186 self.ui.reset_detail_scroll();
187 }
188
189 pub fn move_down(&mut self) {
191 self.navigation.move_down(self.data.visible_count());
192 self.ui.reset_detail_scroll();
193 }
194
195 pub fn move_top(&mut self) {
197 ScrollManager::move_top(&mut self.ui, &mut self.navigation);
198 }
199
200 pub fn move_bottom(&mut self) {
202 ScrollManager::move_bottom(&mut self.ui, &mut self.navigation, self.data.visible_count());
203 }
204
205 pub fn scroll_up(
207 &mut self,
208 amount: usize,
209 ) {
210 ScrollManager::scroll_up(&mut self.ui, &mut self.navigation, amount);
211 }
212
213 pub fn scroll_down(
215 &mut self,
216 amount: usize,
217 ) {
218 ScrollManager::scroll_down(&mut self.ui, &mut self.navigation, amount, self.data.visible_count());
219 }
220
221 pub fn cycle_panel(&mut self) {
225 self.ui.cycle_panel();
226 }
227
228 pub fn cycle_panel_backward(&mut self) {
230 self.ui.cycle_panel_backward();
231 }
232
233 pub fn cycle_filter(&mut self) {
237 self.filter.cycle_filter();
238 self.update_visible_entries();
239 }
240
241 pub fn cycle_filter_backward(&mut self) {
243 self.filter.cycle_filter_backward();
244 self.update_visible_entries();
245 }
246
247 pub fn set_filter(
249 &mut self,
250 filter: EntryFilter,
251 ) {
252 self.filter.set_filter(filter);
253 self.update_visible_entries();
254 }
255
256 pub fn enter_search_mode(&mut self) {
260 self.input.enter_search();
261 }
262
263 pub fn exit_search_keep_query(&mut self) {
265 self.input.exit_search();
266 }
267
268 pub fn exit_search_clear_query(&mut self) {
270 self.input.exit_search();
271 self.search.clear();
272 self.update_visible_entries();
273 }
274
275 pub fn clear_search(&mut self) {
277 self.search.clear();
278 self.update_visible_entries();
279 }
280
281 pub fn set_search_query(
283 &mut self,
284 query: String,
285 ) {
286 self.search.set_query(query);
287 self.update_visible_entries();
288 }
289
290 pub fn search_insert_char(
292 &mut self,
293 c: char,
294 ) {
295 self.search.insert_char(c);
296 self.update_visible_entries();
297 }
298
299 pub fn search_delete_char(&mut self) {
301 self.search.delete_char();
302 self.update_visible_entries();
303 }
304
305 pub fn search_cursor_left(&mut self) {
307 self.search.move_cursor_left();
308 }
309
310 pub fn search_cursor_right(&mut self) {
312 self.search.move_cursor_right();
313 }
314
315 pub fn toggle_help(&mut self) {
319 self.ui.toggle_help();
320 }
321
322 pub fn help_scroll_down(&mut self) {
324 ScrollManager::help_scroll_down(&mut self.ui);
325 }
326
327 pub fn help_scroll_up(&mut self) {
329 ScrollManager::help_scroll_up(&mut self.ui);
330 }
331
332 pub fn help_jump_top(&mut self) {
334 ScrollManager::help_jump_top(&mut self.ui);
335 }
336
337 pub fn help_jump_bottom(&mut self) {
339 ScrollManager::help_jump_bottom(&mut self.ui);
340 }
341
342 pub fn update_help_max_scroll(
344 &mut self,
345 total_lines: usize,
346 visible_lines: usize,
347 ) {
348 self.ui.update_help_max_scroll(total_lines, visible_lines);
349 }
350
351 pub fn update_description_max_scroll(
353 &mut self,
354 total_lines: usize,
355 visible_lines: usize,
356 ) {
357 self.ui.update_description_max_scroll(total_lines, visible_lines);
358 }
359
360 pub fn update_script_max_scroll(
362 &mut self,
363 total_lines: usize,
364 visible_lines: usize,
365 ) {
366 self.ui.update_script_max_scroll(total_lines, visible_lines);
367 }
368
369 pub fn cycle_group_mode(&mut self) {
373 self.filter.cycle_group_mode();
374 self.update_visible_entries();
375 }
376
377 pub fn cycle_group_mode_backward(&mut self) {
379 self.filter.cycle_group_mode_backward();
380 self.update_visible_entries();
381 }
382
383 pub fn toggle_sort_order(&mut self) {
385 self.filter.toggle_sort_order();
386 self.update_visible_entries();
387 }
388
389 pub fn is_pending_key_expired(&self) -> bool {
393 self.input.is_pending_key_expired()
394 }
395
396 pub fn clear_pending_key(&mut self) {
398 self.input.clear_pending_key();
399 }
400
401 pub fn set_pending_key(
403 &mut self,
404 key: char,
405 ) {
406 self.input.set_pending_key(key);
407 }
408
409 pub fn select_entry(
413 &mut self,
414 action: ExitAction,
415 ) {
416 if self.selected_entry().is_some() {
417 self.exit_action = Some(action);
418 }
419 self.should_quit = true;
420 }
421
422 pub fn tick(&mut self) {
424 if self.input.is_pending_key_expired() {
426 self.input.clear_pending_key();
427 }
428 }
429
430 pub fn cycle_theme_next(&mut self) {
434 let themes = Theme::available_themes();
435 let current = themes.iter().position(|n| n == &self.theme.name).unwrap_or(0);
436 let next = (current + 1) % themes.len();
437 if let Some(t) = Theme::from_name(&themes[next]) {
438 self.theme = t;
439 }
440 }
441
442 pub fn cycle_theme_prev(&mut self) {
444 let themes = Theme::available_themes();
445 let current = themes.iter().position(|n| n == &self.theme.name).unwrap_or(0);
446 let prev = (current + themes.len() - 1) % themes.len();
447 if let Some(t) = Theme::from_name(&themes[prev]) {
448 self.theme = t;
449 }
450 }
451
452 pub fn theme(&self) -> &Theme {
456 &self.theme
457 }
458}
459
460#[cfg(test)]
461#[path = "app_tests.rs"]
462pub(crate) mod app_tests;