1use crate::buffer::AppMode;
7use crate::ui::input::actions::{Action, ActionContext, ActionResult, NavigateAction, YankTarget};
8use anyhow::Result;
9
10pub trait ActionHandler {
12 fn handle_action(
14 &self,
15 action: &Action,
16 context: &ActionContext,
17 tui: &mut dyn ActionHandlerContext,
18 ) -> Option<Result<ActionResult>>;
19
20 fn name(&self) -> &'static str;
22}
23
24pub trait ActionHandlerContext {
27 fn previous_row(&mut self);
29 fn next_row(&mut self);
30 fn move_column_left(&mut self);
31 fn move_column_right(&mut self);
32 fn page_up(&mut self);
33 fn page_down(&mut self);
34 fn goto_first_row(&mut self);
35 fn goto_last_row(&mut self);
36 fn goto_first_column(&mut self);
37 fn goto_last_column(&mut self);
38 fn goto_row(&mut self, row: usize);
39 fn goto_column(&mut self, col: usize);
40
41 fn set_mode(&mut self, mode: AppMode);
43 fn get_mode(&self) -> AppMode;
44 fn set_status_message(&mut self, message: String);
45
46 fn toggle_column_pin(&mut self);
48 fn hide_current_column(&mut self);
49 fn unhide_all_columns(&mut self);
50 fn clear_all_pinned_columns(&mut self);
51
52 fn export_to_csv(&mut self);
54 fn export_to_json(&mut self);
55
56 fn yank_cell(&mut self);
58 fn yank_row(&mut self);
59 fn yank_column(&mut self);
60 fn yank_all(&mut self);
61 fn yank_query(&mut self);
62
63 fn toggle_selection_mode(&mut self);
65 fn toggle_row_numbers(&mut self);
66 fn toggle_compact_mode(&mut self);
67 fn toggle_case_insensitive(&mut self);
68 fn toggle_key_indicator(&mut self);
69
70 fn clear_filter(&mut self);
72 fn clear_line(&mut self);
73
74 fn start_search(&mut self);
76 fn start_column_search(&mut self);
77 fn start_filter(&mut self);
78 fn start_fuzzy_filter(&mut self);
79 fn exit_current_mode(&mut self);
80 fn toggle_debug_mode(&mut self);
81
82 fn move_current_column_left(&mut self);
84 fn move_current_column_right(&mut self);
85
86 fn next_search_match(&mut self);
88 fn previous_search_match(&mut self);
89
90 fn show_column_statistics(&mut self);
92 fn cycle_column_packing(&mut self);
93
94 fn navigate_to_viewport_top(&mut self);
96 fn navigate_to_viewport_middle(&mut self);
97 fn navigate_to_viewport_bottom(&mut self);
98
99 fn move_input_cursor_left(&mut self);
101 fn move_input_cursor_right(&mut self);
102 fn move_input_cursor_home(&mut self);
103 fn move_input_cursor_end(&mut self);
104 fn backspace(&mut self);
105 fn delete(&mut self);
106 fn undo(&mut self);
107 fn redo(&mut self);
108
109 fn start_jump_to_row(&mut self);
111 fn clear_jump_to_row_input(&mut self);
112
113 fn show_debug_info(&mut self);
115 fn show_pretty_query(&mut self);
116 fn show_help(&mut self);
117
118 fn kill_line(&mut self);
120 fn kill_line_backward(&mut self);
121 fn delete_word_backward(&mut self);
122 fn delete_word_forward(&mut self);
123 fn expand_asterisk(&mut self);
124 fn expand_asterisk_visible(&mut self);
125
126 fn previous_history_command(&mut self);
128 fn next_history_command(&mut self);
129
130 fn toggle_cursor_lock(&mut self);
132 fn toggle_viewport_lock(&mut self);
133}
134
135pub struct NavigationActionHandler;
137
138impl ActionHandler for NavigationActionHandler {
139 fn handle_action(
140 &self,
141 action: &Action,
142 _context: &ActionContext,
143 tui: &mut dyn ActionHandlerContext,
144 ) -> Option<Result<ActionResult>> {
145 match action {
146 Action::Navigate(nav_action) => match nav_action {
147 NavigateAction::Up(count) => {
148 for _ in 0..*count {
149 tui.previous_row();
150 }
151 Some(Ok(ActionResult::Handled))
152 }
153 NavigateAction::Down(count) => {
154 for _ in 0..*count {
155 tui.next_row();
156 }
157 Some(Ok(ActionResult::Handled))
158 }
159 NavigateAction::Left(count) => {
160 for _ in 0..*count {
161 tui.move_column_left();
162 }
163 Some(Ok(ActionResult::Handled))
164 }
165 NavigateAction::Right(count) => {
166 for _ in 0..*count {
167 tui.move_column_right();
168 }
169 Some(Ok(ActionResult::Handled))
170 }
171 NavigateAction::PageUp => {
172 tui.page_up();
173 Some(Ok(ActionResult::Handled))
174 }
175 NavigateAction::PageDown => {
176 tui.page_down();
177 Some(Ok(ActionResult::Handled))
178 }
179 NavigateAction::Home => {
180 tui.goto_first_row();
181 Some(Ok(ActionResult::Handled))
182 }
183 NavigateAction::End => {
184 tui.goto_last_row();
185 Some(Ok(ActionResult::Handled))
186 }
187 NavigateAction::FirstColumn => {
188 tui.goto_first_column();
189 Some(Ok(ActionResult::Handled))
190 }
191 NavigateAction::LastColumn => {
192 tui.goto_last_column();
193 Some(Ok(ActionResult::Handled))
194 }
195 NavigateAction::JumpToRow(row) => {
196 tui.goto_row(*row);
197 Some(Ok(ActionResult::Handled))
198 }
199 NavigateAction::JumpToColumn(col) => {
200 tui.goto_column(*col);
201 Some(Ok(ActionResult::Handled))
202 }
203 },
204 Action::NextColumn => {
205 tui.move_column_right();
206 Some(Ok(ActionResult::Handled))
207 }
208 Action::PreviousColumn => {
209 tui.move_column_left();
210 Some(Ok(ActionResult::Handled))
211 }
212 _ => None, }
214 }
215
216 fn name(&self) -> &'static str {
217 "Navigation"
218 }
219}
220
221pub struct ColumnActionHandler;
223
224impl ActionHandler for ColumnActionHandler {
225 fn handle_action(
226 &self,
227 action: &Action,
228 _context: &ActionContext,
229 tui: &mut dyn ActionHandlerContext,
230 ) -> Option<Result<ActionResult>> {
231 match action {
232 Action::ToggleColumnPin => {
233 tui.toggle_column_pin();
234 Some(Ok(ActionResult::Handled))
235 }
236 Action::HideColumn => {
237 tui.hide_current_column();
238 Some(Ok(ActionResult::Handled))
239 }
240 Action::UnhideAllColumns => {
241 tui.unhide_all_columns();
242 Some(Ok(ActionResult::Handled))
243 }
244 Action::ClearAllPins => {
245 tui.clear_all_pinned_columns();
246 Some(Ok(ActionResult::Handled))
247 }
248 _ => None,
249 }
250 }
251
252 fn name(&self) -> &'static str {
253 "Column"
254 }
255}
256
257pub struct ExportActionHandler;
259
260impl ActionHandler for ExportActionHandler {
261 fn handle_action(
262 &self,
263 action: &Action,
264 _context: &ActionContext,
265 tui: &mut dyn ActionHandlerContext,
266 ) -> Option<Result<ActionResult>> {
267 match action {
268 Action::ExportToCsv => {
269 tui.export_to_csv();
270 Some(Ok(ActionResult::Handled))
271 }
272 Action::ExportToJson => {
273 tui.export_to_json();
274 Some(Ok(ActionResult::Handled))
275 }
276 _ => None,
277 }
278 }
279
280 fn name(&self) -> &'static str {
281 "Export"
282 }
283}
284
285pub struct YankActionHandler;
287
288impl ActionHandler for YankActionHandler {
289 fn handle_action(
290 &self,
291 action: &Action,
292 _context: &ActionContext,
293 tui: &mut dyn ActionHandlerContext,
294 ) -> Option<Result<ActionResult>> {
295 match action {
296 Action::Yank(target) => {
297 match target {
298 YankTarget::Cell => tui.yank_cell(),
299 YankTarget::Row => tui.yank_row(),
300 YankTarget::Column => tui.yank_column(),
301 YankTarget::All => tui.yank_all(),
302 YankTarget::Query => tui.yank_query(),
303 }
304 Some(Ok(ActionResult::Handled))
305 }
306 _ => None,
307 }
308 }
309
310 fn name(&self) -> &'static str {
311 "Yank"
312 }
313}
314
315pub struct UIActionHandler;
317
318impl ActionHandler for UIActionHandler {
319 fn handle_action(
320 &self,
321 action: &Action,
322 _context: &ActionContext,
323 tui: &mut dyn ActionHandlerContext,
324 ) -> Option<Result<ActionResult>> {
325 match action {
326 Action::ShowHelp => {
327 tui.set_mode(AppMode::Help);
328 tui.set_status_message("Help mode - Press 'q' or Escape to return".to_string());
329 Some(Ok(ActionResult::Handled))
330 }
331 _ => None,
334 }
335 }
336
337 fn name(&self) -> &'static str {
338 "UI"
339 }
340}
341
342pub struct ToggleActionHandler;
344
345impl ActionHandler for ToggleActionHandler {
346 fn handle_action(
347 &self,
348 action: &Action,
349 _context: &ActionContext,
350 tui: &mut dyn ActionHandlerContext,
351 ) -> Option<Result<ActionResult>> {
352 match action {
353 Action::ToggleSelectionMode => {
354 tui.toggle_selection_mode();
355 Some(Ok(ActionResult::Handled))
356 }
357 Action::ToggleRowNumbers => {
358 tui.toggle_row_numbers();
359 Some(Ok(ActionResult::Handled))
360 }
361 Action::ToggleCompactMode => {
362 tui.toggle_compact_mode();
363 Some(Ok(ActionResult::Handled))
364 }
365 Action::ToggleCaseInsensitive => {
366 tui.toggle_case_insensitive();
367 Some(Ok(ActionResult::Handled))
368 }
369 Action::ToggleKeyIndicator => {
370 tui.toggle_key_indicator();
371 Some(Ok(ActionResult::Handled))
372 }
373 _ => None,
374 }
375 }
376
377 fn name(&self) -> &'static str {
378 "Toggle"
379 }
380}
381
382pub struct ClearActionHandler;
384
385impl ActionHandler for ClearActionHandler {
386 fn handle_action(
387 &self,
388 action: &Action,
389 context: &ActionContext,
390 tui: &mut dyn ActionHandlerContext,
391 ) -> Option<Result<ActionResult>> {
392 match action {
393 Action::ClearFilter => {
394 tui.clear_filter();
395 Some(Ok(ActionResult::Handled))
396 }
397 Action::ClearLine => {
398 if context.mode == AppMode::Command {
400 tui.clear_line();
401 Some(Ok(ActionResult::Handled))
402 } else {
403 None
404 }
405 }
406 _ => None,
407 }
408 }
409
410 fn name(&self) -> &'static str {
411 "Clear"
412 }
413}
414
415pub struct ExitActionHandler;
417
418impl ActionHandler for ExitActionHandler {
419 fn handle_action(
420 &self,
421 action: &Action,
422 _context: &ActionContext,
423 _tui: &mut dyn ActionHandlerContext,
424 ) -> Option<Result<ActionResult>> {
425 match action {
426 Action::Quit | Action::ForceQuit => Some(Ok(ActionResult::Exit)),
427 _ => None,
428 }
429 }
430
431 fn name(&self) -> &'static str {
432 "Exit"
433 }
434}
435
436pub struct ModeActionHandler;
438
439impl ActionHandler for ModeActionHandler {
440 fn handle_action(
441 &self,
442 action: &Action,
443 _context: &ActionContext,
444 tui: &mut dyn ActionHandlerContext,
445 ) -> Option<Result<ActionResult>> {
446 match action {
447 Action::StartSearch => {
448 tui.start_search();
449 Some(Ok(ActionResult::Handled))
450 }
451 Action::StartColumnSearch => {
452 tui.start_column_search();
453 Some(Ok(ActionResult::Handled))
454 }
455 Action::StartFilter => {
456 tui.start_filter();
457 Some(Ok(ActionResult::Handled))
458 }
459 Action::StartFuzzyFilter => {
460 tui.start_fuzzy_filter();
461 Some(Ok(ActionResult::Handled))
462 }
463 Action::ExitCurrentMode => {
464 tui.exit_current_mode();
465 Some(Ok(ActionResult::Handled))
466 }
467 Action::ShowDebugInfo => {
468 tui.toggle_debug_mode();
469 Some(Ok(ActionResult::Handled))
470 }
471 _ => None,
472 }
473 }
474
475 fn name(&self) -> &'static str {
476 "Mode"
477 }
478}
479
480pub struct ViewportNavigationHandler;
482
483impl ActionHandler for ViewportNavigationHandler {
484 fn handle_action(
485 &self,
486 action: &Action,
487 _context: &ActionContext,
488 tui: &mut dyn ActionHandlerContext,
489 ) -> Option<Result<ActionResult>> {
490 match action {
491 Action::NavigateToViewportTop => {
492 tui.navigate_to_viewport_top();
493 Some(Ok(ActionResult::Handled))
494 }
495 Action::NavigateToViewportMiddle => {
496 tui.navigate_to_viewport_middle();
497 Some(Ok(ActionResult::Handled))
498 }
499 Action::NavigateToViewportBottom => {
500 tui.navigate_to_viewport_bottom();
501 Some(Ok(ActionResult::Handled))
502 }
503 _ => None,
504 }
505 }
506
507 fn name(&self) -> &'static str {
508 "ViewportNavigation"
509 }
510}
511
512pub struct ColumnArrangementActionHandler;
514
515impl ActionHandler for ColumnArrangementActionHandler {
516 fn handle_action(
517 &self,
518 action: &Action,
519 _context: &ActionContext,
520 tui: &mut dyn ActionHandlerContext,
521 ) -> Option<Result<ActionResult>> {
522 match action {
523 Action::MoveColumnLeft => {
524 tui.move_current_column_left();
525 Some(Ok(ActionResult::Handled))
526 }
527 Action::MoveColumnRight => {
528 tui.move_current_column_right();
529 Some(Ok(ActionResult::Handled))
530 }
531 _ => None,
532 }
533 }
534
535 fn name(&self) -> &'static str {
536 "ColumnArrangement"
537 }
538}
539
540pub struct SearchNavigationActionHandler;
542
543impl ActionHandler for SearchNavigationActionHandler {
544 fn handle_action(
545 &self,
546 action: &Action,
547 context: &ActionContext,
548 tui: &mut dyn ActionHandlerContext,
549 ) -> Option<Result<ActionResult>> {
550 match action {
551 Action::NextSearchMatch => {
552 tui.next_search_match();
553 Some(Ok(ActionResult::Handled))
554 }
555 Action::PreviousSearchMatch => {
556 if context.has_search {
559 tui.previous_search_match();
560 } else {
561 tui.toggle_row_numbers();
562 }
563 Some(Ok(ActionResult::Handled))
564 }
565 _ => None,
566 }
567 }
568
569 fn name(&self) -> &'static str {
570 "SearchNavigation"
571 }
572}
573
574pub struct StatisticsActionHandler;
576
577impl ActionHandler for StatisticsActionHandler {
578 fn handle_action(
579 &self,
580 action: &Action,
581 _context: &ActionContext,
582 tui: &mut dyn ActionHandlerContext,
583 ) -> Option<Result<ActionResult>> {
584 match action {
585 Action::ShowColumnStatistics => {
586 tui.show_column_statistics();
587 Some(Ok(ActionResult::Handled))
588 }
589 Action::CycleColumnPacking => {
590 tui.cycle_column_packing();
591 Some(Ok(ActionResult::Handled))
592 }
593 _ => None,
594 }
595 }
596
597 fn name(&self) -> &'static str {
598 "Statistics"
599 }
600}
601
602pub struct InputCursorActionHandler;
604
605impl ActionHandler for InputCursorActionHandler {
606 fn handle_action(
607 &self,
608 action: &Action,
609 context: &ActionContext,
610 tui: &mut dyn ActionHandlerContext,
611 ) -> Option<Result<ActionResult>> {
612 if context.mode != AppMode::Command {
614 return None;
615 }
616
617 match action {
618 Action::MoveCursorLeft => {
619 tui.move_input_cursor_left();
620 Some(Ok(ActionResult::Handled))
621 }
622 Action::MoveCursorRight => {
623 tui.move_input_cursor_right();
624 Some(Ok(ActionResult::Handled))
625 }
626 Action::MoveCursorHome => {
627 tui.move_input_cursor_home();
628 Some(Ok(ActionResult::Handled))
629 }
630 Action::MoveCursorEnd => {
631 tui.move_input_cursor_end();
632 Some(Ok(ActionResult::Handled))
633 }
634 _ => None,
635 }
636 }
637
638 fn name(&self) -> &'static str {
639 "InputCursor"
640 }
641}
642
643pub struct DebugViewportActionHandler;
645
646impl ActionHandler for DebugViewportActionHandler {
647 fn handle_action(
648 &self,
649 action: &Action,
650 _context: &ActionContext,
651 tui: &mut dyn ActionHandlerContext,
652 ) -> Option<Result<ActionResult>> {
653 match action {
654 Action::ShowDebugInfo => {
655 tui.toggle_debug_mode();
656 Some(Ok(ActionResult::Handled))
657 }
658 Action::StartJumpToRow => {
659 tui.start_jump_to_row();
660 Some(Ok(ActionResult::Handled))
661 }
662 Action::ToggleCursorLock => {
663 tui.toggle_cursor_lock();
664 Some(Ok(ActionResult::Handled))
665 }
666 Action::ToggleViewportLock => {
667 tui.toggle_viewport_lock();
668 Some(Ok(ActionResult::Handled))
669 }
670 _ => None,
671 }
672 }
673
674 fn name(&self) -> &'static str {
675 "DebugViewport"
676 }
677}
678
679pub struct FunctionKeyActionHandler;
681
682impl ActionHandler for FunctionKeyActionHandler {
683 fn handle_action(
684 &self,
685 action: &Action,
686 _context: &ActionContext,
687 tui: &mut dyn ActionHandlerContext,
688 ) -> Option<Result<ActionResult>> {
689 match action {
690 Action::ShowHelp => {
691 tui.show_help();
692 Some(Ok(ActionResult::Handled))
693 }
694 Action::ShowPrettyQuery => {
695 tui.show_pretty_query();
696 Some(Ok(ActionResult::Handled))
697 }
698 Action::ShowDebugInfo => {
699 tui.toggle_debug_mode();
700 Some(Ok(ActionResult::Handled))
701 }
702 Action::ToggleRowNumbers => {
703 tui.toggle_row_numbers();
704 Some(Ok(ActionResult::Handled))
705 }
706 Action::ToggleCompactMode => {
707 tui.toggle_compact_mode();
708 Some(Ok(ActionResult::Handled))
709 }
710 Action::ToggleCaseInsensitive => {
711 tui.toggle_case_insensitive();
712 Some(Ok(ActionResult::Handled))
713 }
714 Action::ToggleKeyIndicator => {
715 tui.toggle_key_indicator();
716 Some(Ok(ActionResult::Handled))
717 }
718 Action::KillLine => {
719 tui.kill_line();
720 Some(Ok(ActionResult::Handled))
721 }
722 Action::KillLineBackward => {
723 tui.kill_line_backward();
724 Some(Ok(ActionResult::Handled))
725 }
726 _ => None,
727 }
728 }
729
730 fn name(&self) -> &'static str {
731 "FunctionKey"
732 }
733}
734
735pub struct TextEditActionHandler;
737
738impl ActionHandler for TextEditActionHandler {
739 fn handle_action(
740 &self,
741 action: &Action,
742 context: &ActionContext,
743 tui: &mut dyn ActionHandlerContext,
744 ) -> Option<Result<ActionResult>> {
745 if context.mode != AppMode::Command {
747 return None;
748 }
749
750 match action {
751 Action::Backspace => {
752 tui.backspace();
753 Some(Ok(ActionResult::Handled))
754 }
755 Action::Delete => {
756 tui.delete();
757 Some(Ok(ActionResult::Handled))
758 }
759 Action::Undo => {
760 tui.undo();
761 Some(Ok(ActionResult::Handled))
762 }
763 Action::Redo => {
764 tui.redo();
765 Some(Ok(ActionResult::Handled))
766 }
767 Action::DeleteWordBackward => {
768 tui.delete_word_backward();
769 Some(Ok(ActionResult::Handled))
770 }
771 Action::DeleteWordForward => {
772 tui.delete_word_forward();
773 Some(Ok(ActionResult::Handled))
774 }
775 Action::KillLine => {
776 tui.kill_line();
777 Some(Ok(ActionResult::Handled))
778 }
779 Action::KillLineBackward => {
780 tui.kill_line_backward();
781 Some(Ok(ActionResult::Handled))
782 }
783 Action::ExpandAsterisk => {
784 tui.expand_asterisk();
785 Some(Ok(ActionResult::Handled))
786 }
787 Action::ExpandAsteriskVisible => {
788 tui.expand_asterisk_visible();
789 Some(Ok(ActionResult::Handled))
790 }
791 Action::PreviousHistoryCommand => {
792 tui.previous_history_command();
793 Some(Ok(ActionResult::Handled))
794 }
795 Action::NextHistoryCommand => {
796 tui.next_history_command();
797 Some(Ok(ActionResult::Handled))
798 }
799 _ => None,
800 }
801 }
802
803 fn name(&self) -> &'static str {
804 "TextEdit"
805 }
806}
807
808pub struct ActionDispatcher {
810 handlers: Vec<Box<dyn ActionHandler>>,
811}
812
813impl ActionDispatcher {
814 #[must_use]
815 pub fn new() -> Self {
816 let handlers: Vec<Box<dyn ActionHandler>> = vec![
817 Box::new(NavigationActionHandler),
818 Box::new(ColumnActionHandler),
819 Box::new(ExportActionHandler),
820 Box::new(YankActionHandler),
821 Box::new(UIActionHandler),
822 Box::new(ToggleActionHandler),
823 Box::new(ClearActionHandler),
824 Box::new(ExitActionHandler),
825 Box::new(FunctionKeyActionHandler),
826 Box::new(ModeActionHandler),
827 Box::new(ColumnArrangementActionHandler),
828 Box::new(SearchNavigationActionHandler),
829 Box::new(StatisticsActionHandler),
830 Box::new(InputCursorActionHandler),
831 Box::new(TextEditActionHandler),
832 Box::new(ViewportNavigationHandler),
833 Box::new(DebugViewportActionHandler),
834 ];
835
836 Self { handlers }
837 }
838
839 pub fn dispatch(
841 &self,
842 action: &Action,
843 context: &ActionContext,
844 tui: &mut dyn ActionHandlerContext,
845 ) -> Result<ActionResult> {
846 for handler in &self.handlers {
847 if let Some(result) = handler.handle_action(action, context, tui) {
848 return result;
849 }
850 }
851
852 Ok(ActionResult::NotHandled)
854 }
855}
856
857impl Default for ActionDispatcher {
858 fn default() -> Self {
859 Self::new()
860 }
861}
862
863#[cfg(test)]
864mod tests {
865 use super::*;
866 use crate::ui::input::actions::{Action, NavigateAction};
867
868 struct MockTui {
870 pub last_action: String,
871 pub mode: AppMode,
872 pub status_message: String,
873 }
874
875 impl MockTui {
876 fn new() -> Self {
877 Self {
878 last_action: String::new(),
879 mode: AppMode::Results,
880 status_message: String::new(),
881 }
882 }
883 }
884
885 impl ActionHandlerContext for MockTui {
886 fn previous_row(&mut self) {
887 self.last_action = "previous_row".to_string();
888 }
889 fn next_row(&mut self) {
890 self.last_action = "next_row".to_string();
891 }
892 fn move_column_left(&mut self) {
893 self.last_action = "move_column_left".to_string();
894 }
895 fn move_column_right(&mut self) {
896 self.last_action = "move_column_right".to_string();
897 }
898 fn page_up(&mut self) {
899 self.last_action = "page_up".to_string();
900 }
901 fn page_down(&mut self) {
902 self.last_action = "page_down".to_string();
903 }
904 fn goto_first_row(&mut self) {
905 self.last_action = "goto_first_row".to_string();
906 }
907 fn goto_last_row(&mut self) {
908 self.last_action = "goto_last_row".to_string();
909 }
910 fn goto_first_column(&mut self) {
911 self.last_action = "goto_first_column".to_string();
912 }
913 fn goto_last_column(&mut self) {
914 self.last_action = "goto_last_column".to_string();
915 }
916 fn goto_row(&mut self, row: usize) {
917 self.last_action = format!("goto_row_{row}");
918 }
919 fn goto_column(&mut self, col: usize) {
920 self.last_action = format!("goto_column_{col}");
921 }
922
923 fn set_mode(&mut self, mode: AppMode) {
924 self.mode = mode;
925 }
926 fn get_mode(&self) -> AppMode {
927 self.mode.clone()
928 }
929 fn set_status_message(&mut self, message: String) {
930 self.status_message = message;
931 }
932
933 fn toggle_column_pin(&mut self) {
934 self.last_action = "toggle_column_pin".to_string();
935 }
936 fn hide_current_column(&mut self) {
937 self.last_action = "hide_current_column".to_string();
938 }
939 fn unhide_all_columns(&mut self) {
940 self.last_action = "unhide_all_columns".to_string();
941 }
942 fn clear_all_pinned_columns(&mut self) {
943 self.last_action = "clear_all_pinned_columns".to_string();
944 }
945
946 fn export_to_csv(&mut self) {
947 self.last_action = "export_to_csv".to_string();
948 }
949 fn export_to_json(&mut self) {
950 self.last_action = "export_to_json".to_string();
951 }
952
953 fn yank_cell(&mut self) {
954 self.last_action = "yank_cell".to_string();
955 }
956 fn yank_row(&mut self) {
957 self.last_action = "yank_row".to_string();
958 }
959 fn yank_column(&mut self) {
960 self.last_action = "yank_column".to_string();
961 }
962 fn yank_all(&mut self) {
963 self.last_action = "yank_all".to_string();
964 }
965 fn yank_query(&mut self) {
966 self.last_action = "yank_query".to_string();
967 }
968
969 fn toggle_selection_mode(&mut self) {
971 self.last_action = "toggle_selection_mode".to_string();
972 }
973 fn toggle_row_numbers(&mut self) {
974 self.last_action = "toggle_row_numbers".to_string();
975 }
976 fn toggle_compact_mode(&mut self) {
977 self.last_action = "toggle_compact_mode".to_string();
978 }
979 fn toggle_case_insensitive(&mut self) {
980 self.last_action = "toggle_case_insensitive".to_string();
981 }
982 fn toggle_key_indicator(&mut self) {
983 self.last_action = "toggle_key_indicator".to_string();
984 }
985
986 fn clear_filter(&mut self) {
988 self.last_action = "clear_filter".to_string();
989 }
990 fn clear_line(&mut self) {
991 self.last_action = "clear_line".to_string();
992 }
993
994 fn start_search(&mut self) {
996 self.last_action = "start_search".to_string();
997 }
998 fn start_column_search(&mut self) {
999 self.last_action = "start_column_search".to_string();
1000 }
1001 fn start_filter(&mut self) {
1002 self.last_action = "start_filter".to_string();
1003 }
1004 fn start_fuzzy_filter(&mut self) {
1005 self.last_action = "start_fuzzy_filter".to_string();
1006 }
1007 fn exit_current_mode(&mut self) {
1008 self.last_action = "exit_current_mode".to_string();
1009 }
1010 fn toggle_debug_mode(&mut self) {
1011 self.last_action = "toggle_debug_mode".to_string();
1012 }
1013
1014 fn move_current_column_left(&mut self) {
1016 self.last_action = "move_current_column_left".to_string();
1017 }
1018 fn move_current_column_right(&mut self) {
1019 self.last_action = "move_current_column_right".to_string();
1020 }
1021
1022 fn next_search_match(&mut self) {
1024 self.last_action = "next_search_match".to_string();
1025 }
1026 fn previous_search_match(&mut self) {
1027 self.last_action = "previous_search_match".to_string();
1028 }
1029
1030 fn show_column_statistics(&mut self) {
1032 self.last_action = "show_column_statistics".to_string();
1033 }
1034 fn cycle_column_packing(&mut self) {
1035 self.last_action = "cycle_column_packing".to_string();
1036 }
1037
1038 fn navigate_to_viewport_top(&mut self) {
1039 self.last_action = "navigate_to_viewport_top".to_string();
1040 }
1041
1042 fn navigate_to_viewport_middle(&mut self) {
1043 self.last_action = "navigate_to_viewport_middle".to_string();
1044 }
1045
1046 fn navigate_to_viewport_bottom(&mut self) {
1047 self.last_action = "navigate_to_viewport_bottom".to_string();
1048 }
1049
1050 fn start_jump_to_row(&mut self) {
1052 self.last_action = "start_jump_to_row".to_string();
1053 }
1054 fn clear_jump_to_row_input(&mut self) {
1055 self.last_action = "clear_jump_to_row_input".to_string();
1056 }
1057
1058 fn toggle_cursor_lock(&mut self) {
1060 self.last_action = "toggle_cursor_lock".to_string();
1061 }
1062 fn toggle_viewport_lock(&mut self) {
1063 self.last_action = "toggle_viewport_lock".to_string();
1064 }
1065
1066 fn move_input_cursor_left(&mut self) {
1068 self.last_action = "move_input_cursor_left".to_string();
1069 }
1070 fn move_input_cursor_right(&mut self) {
1071 self.last_action = "move_input_cursor_right".to_string();
1072 }
1073 fn move_input_cursor_home(&mut self) {
1074 self.last_action = "move_input_cursor_home".to_string();
1075 }
1076 fn move_input_cursor_end(&mut self) {
1077 self.last_action = "move_input_cursor_end".to_string();
1078 }
1079 fn backspace(&mut self) {
1080 self.last_action = "backspace".to_string();
1081 }
1082 fn delete(&mut self) {
1083 self.last_action = "delete".to_string();
1084 }
1085 fn undo(&mut self) {
1086 self.last_action = "undo".to_string();
1087 }
1088 fn redo(&mut self) {
1089 self.last_action = "redo".to_string();
1090 }
1091
1092 fn show_debug_info(&mut self) {
1094 self.last_action = "show_debug_info".to_string();
1095 }
1096 fn show_pretty_query(&mut self) {
1097 self.last_action = "show_pretty_query".to_string();
1098 }
1099 fn show_help(&mut self) {
1100 self.last_action = "show_help".to_string();
1101 }
1102
1103 fn kill_line(&mut self) {
1105 self.last_action = "kill_line".to_string();
1106 }
1107 fn kill_line_backward(&mut self) {
1108 self.last_action = "kill_line_backward".to_string();
1109 }
1110 fn delete_word_backward(&mut self) {
1111 self.last_action = "delete_word_backward".to_string();
1112 }
1113 fn delete_word_forward(&mut self) {
1114 self.last_action = "delete_word_forward".to_string();
1115 }
1116 fn expand_asterisk(&mut self) {
1117 self.last_action = "expand_asterisk".to_string();
1118 }
1119 fn expand_asterisk_visible(&mut self) {
1120 self.last_action = "expand_asterisk_visible".to_string();
1121 }
1122
1123 fn previous_history_command(&mut self) {
1125 self.last_action = "previous_history_command".to_string();
1126 }
1127 fn next_history_command(&mut self) {
1128 self.last_action = "next_history_command".to_string();
1129 }
1130 }
1131
1132 #[test]
1133 fn test_debug_viewport_handler() {
1134 let mut tui = MockTui::new();
1135 let handler = DebugViewportActionHandler;
1136 let context = ActionContext {
1137 mode: AppMode::Results,
1138 selection_mode: crate::app_state_container::SelectionMode::Cell,
1139 has_results: true,
1140 has_filter: false,
1141 has_search: false,
1142 row_count: 100,
1143 column_count: 10,
1144 current_row: 0,
1145 current_column: 0,
1146 };
1147
1148 let result = handler
1150 .handle_action(&Action::ShowDebugInfo, &context, &mut tui)
1151 .unwrap();
1152 assert!(matches!(result, Ok(ActionResult::Handled)));
1153 assert_eq!(tui.last_action, "toggle_debug_mode");
1154
1155 let result = handler
1157 .handle_action(&Action::StartJumpToRow, &context, &mut tui)
1158 .unwrap();
1159 assert!(matches!(result, Ok(ActionResult::Handled)));
1160 assert_eq!(tui.last_action, "start_jump_to_row");
1161
1162 let result = handler
1164 .handle_action(&Action::ToggleCursorLock, &context, &mut tui)
1165 .unwrap();
1166 assert!(matches!(result, Ok(ActionResult::Handled)));
1167 assert_eq!(tui.last_action, "toggle_cursor_lock");
1168
1169 let result = handler
1171 .handle_action(&Action::ToggleViewportLock, &context, &mut tui)
1172 .unwrap();
1173 assert!(matches!(result, Ok(ActionResult::Handled)));
1174 assert_eq!(tui.last_action, "toggle_viewport_lock");
1175
1176 let result = handler.handle_action(&Action::Quit, &context, &mut tui);
1178 assert!(result.is_none());
1179 }
1180
1181 #[test]
1182 fn test_navigation_handler() {
1183 let handler = NavigationActionHandler;
1184 let mut mock_tui = MockTui::new();
1185 let context = ActionContext {
1186 mode: AppMode::Results,
1187 selection_mode: crate::app_state_container::SelectionMode::Row,
1188 has_results: true,
1189 has_filter: false,
1190 has_search: false,
1191 row_count: 10,
1192 column_count: 5,
1193 current_row: 0,
1194 current_column: 0,
1195 };
1196
1197 let action = Action::Navigate(NavigateAction::Up(1));
1198 let result = handler.handle_action(&action, &context, &mut mock_tui);
1199
1200 assert!(result.is_some());
1201 assert_eq!(mock_tui.last_action, "previous_row");
1202
1203 match result.unwrap() {
1204 Ok(ActionResult::Handled) => {}
1205 _ => panic!("Expected Handled result"),
1206 }
1207 }
1208
1209 #[test]
1210 fn test_action_dispatcher() {
1211 let dispatcher = ActionDispatcher::new();
1212 let mut mock_tui = MockTui::new();
1213 let context = ActionContext {
1214 mode: AppMode::Results,
1215 selection_mode: crate::app_state_container::SelectionMode::Row,
1216 has_results: true,
1217 has_filter: false,
1218 has_search: false,
1219 row_count: 10,
1220 column_count: 5,
1221 current_row: 0,
1222 current_column: 0,
1223 };
1224
1225 let action = Action::Navigate(NavigateAction::Down(2));
1227 let result = dispatcher
1228 .dispatch(&action, &context, &mut mock_tui)
1229 .unwrap();
1230
1231 assert_eq!(result, ActionResult::Handled);
1232 assert_eq!(mock_tui.last_action, "next_row"); let action = Action::ExportToCsv;
1236 let result = dispatcher
1237 .dispatch(&action, &context, &mut mock_tui)
1238 .unwrap();
1239
1240 assert_eq!(result, ActionResult::Handled);
1241 assert_eq!(mock_tui.last_action, "export_to_csv");
1242 }
1243
1244 #[test]
1245 fn test_toggle_handler() {
1246 let handler = ToggleActionHandler;
1247 let mut mock_tui = MockTui::new();
1248 let context = ActionContext {
1249 mode: AppMode::Results,
1250 selection_mode: crate::app_state_container::SelectionMode::Row,
1251 has_results: true,
1252 has_filter: false,
1253 has_search: false,
1254 row_count: 10,
1255 column_count: 5,
1256 current_row: 0,
1257 current_column: 0,
1258 };
1259
1260 let action = Action::ToggleSelectionMode;
1262 let result = handler
1263 .handle_action(&action, &context, &mut mock_tui)
1264 .unwrap()
1265 .unwrap();
1266 assert_eq!(result, ActionResult::Handled);
1267 assert_eq!(mock_tui.last_action, "toggle_selection_mode");
1268
1269 let action = Action::ToggleRowNumbers;
1271 let result = handler
1272 .handle_action(&action, &context, &mut mock_tui)
1273 .unwrap()
1274 .unwrap();
1275 assert_eq!(result, ActionResult::Handled);
1276 assert_eq!(mock_tui.last_action, "toggle_row_numbers");
1277 }
1278
1279 #[test]
1280 fn test_clear_handler() {
1281 let handler = ClearActionHandler;
1282 let mut mock_tui = MockTui::new();
1283
1284 let context = ActionContext {
1286 mode: AppMode::Results,
1287 selection_mode: crate::app_state_container::SelectionMode::Row,
1288 has_results: true,
1289 has_filter: true,
1290 has_search: false,
1291 row_count: 10,
1292 column_count: 5,
1293 current_row: 0,
1294 current_column: 0,
1295 };
1296
1297 let action = Action::ClearFilter;
1298 let result = handler
1299 .handle_action(&action, &context, &mut mock_tui)
1300 .unwrap()
1301 .unwrap();
1302 assert_eq!(result, ActionResult::Handled);
1303 assert_eq!(mock_tui.last_action, "clear_filter");
1304
1305 let context = ActionContext {
1307 mode: AppMode::Command,
1308 selection_mode: crate::app_state_container::SelectionMode::Row,
1309 has_results: true,
1310 has_filter: false,
1311 has_search: false,
1312 row_count: 10,
1313 column_count: 5,
1314 current_row: 0,
1315 current_column: 0,
1316 };
1317
1318 let action = Action::ClearLine;
1319 let result = handler
1320 .handle_action(&action, &context, &mut mock_tui)
1321 .unwrap()
1322 .unwrap();
1323 assert_eq!(result, ActionResult::Handled);
1324 assert_eq!(mock_tui.last_action, "clear_line");
1325
1326 let context = ActionContext {
1328 mode: AppMode::Results,
1329 selection_mode: crate::app_state_container::SelectionMode::Row,
1330 has_results: true,
1331 has_filter: false,
1332 has_search: false,
1333 row_count: 10,
1334 column_count: 5,
1335 current_row: 0,
1336 current_column: 0,
1337 };
1338
1339 let action = Action::ClearLine;
1340 let result = handler.handle_action(&action, &context, &mut mock_tui);
1341 assert!(result.is_none());
1342 }
1343
1344 #[test]
1345 fn test_exit_handler() {
1346 let handler = ExitActionHandler;
1347 let mut mock_tui = MockTui::new();
1348 let context = ActionContext {
1349 mode: AppMode::Results,
1350 selection_mode: crate::app_state_container::SelectionMode::Row,
1351 has_results: true,
1352 has_filter: false,
1353 has_search: false,
1354 row_count: 10,
1355 column_count: 5,
1356 current_row: 0,
1357 current_column: 0,
1358 };
1359
1360 let action = Action::Quit;
1362 let result = handler
1363 .handle_action(&action, &context, &mut mock_tui)
1364 .unwrap()
1365 .unwrap();
1366 assert_eq!(result, ActionResult::Exit);
1367
1368 let action = Action::ForceQuit;
1370 let result = handler
1371 .handle_action(&action, &context, &mut mock_tui)
1372 .unwrap()
1373 .unwrap();
1374 assert_eq!(result, ActionResult::Exit);
1375 }
1376}