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.toggle_row_numbers();
560 } else {
561 tui.previous_search_match();
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 pub fn new() -> Self {
815 let handlers: Vec<Box<dyn ActionHandler>> = vec![
816 Box::new(NavigationActionHandler),
817 Box::new(ColumnActionHandler),
818 Box::new(ExportActionHandler),
819 Box::new(YankActionHandler),
820 Box::new(UIActionHandler),
821 Box::new(ToggleActionHandler),
822 Box::new(ClearActionHandler),
823 Box::new(ExitActionHandler),
824 Box::new(FunctionKeyActionHandler),
825 Box::new(ModeActionHandler),
826 Box::new(ColumnArrangementActionHandler),
827 Box::new(SearchNavigationActionHandler),
828 Box::new(StatisticsActionHandler),
829 Box::new(InputCursorActionHandler),
830 Box::new(TextEditActionHandler),
831 Box::new(ViewportNavigationHandler),
832 Box::new(DebugViewportActionHandler),
833 ];
834
835 Self { handlers }
836 }
837
838 pub fn dispatch(
840 &self,
841 action: &Action,
842 context: &ActionContext,
843 tui: &mut dyn ActionHandlerContext,
844 ) -> Result<ActionResult> {
845 for handler in &self.handlers {
846 if let Some(result) = handler.handle_action(action, context, tui) {
847 return result;
848 }
849 }
850
851 Ok(ActionResult::NotHandled)
853 }
854}
855
856impl Default for ActionDispatcher {
857 fn default() -> Self {
858 Self::new()
859 }
860}
861
862#[cfg(test)]
863mod tests {
864 use super::*;
865 use crate::ui::input::actions::{Action, NavigateAction};
866
867 struct MockTui {
869 pub last_action: String,
870 pub mode: AppMode,
871 pub status_message: String,
872 }
873
874 impl MockTui {
875 fn new() -> Self {
876 Self {
877 last_action: String::new(),
878 mode: AppMode::Results,
879 status_message: String::new(),
880 }
881 }
882 }
883
884 impl ActionHandlerContext for MockTui {
885 fn previous_row(&mut self) {
886 self.last_action = "previous_row".to_string();
887 }
888 fn next_row(&mut self) {
889 self.last_action = "next_row".to_string();
890 }
891 fn move_column_left(&mut self) {
892 self.last_action = "move_column_left".to_string();
893 }
894 fn move_column_right(&mut self) {
895 self.last_action = "move_column_right".to_string();
896 }
897 fn page_up(&mut self) {
898 self.last_action = "page_up".to_string();
899 }
900 fn page_down(&mut self) {
901 self.last_action = "page_down".to_string();
902 }
903 fn goto_first_row(&mut self) {
904 self.last_action = "goto_first_row".to_string();
905 }
906 fn goto_last_row(&mut self) {
907 self.last_action = "goto_last_row".to_string();
908 }
909 fn goto_first_column(&mut self) {
910 self.last_action = "goto_first_column".to_string();
911 }
912 fn goto_last_column(&mut self) {
913 self.last_action = "goto_last_column".to_string();
914 }
915 fn goto_row(&mut self, row: usize) {
916 self.last_action = format!("goto_row_{}", row);
917 }
918 fn goto_column(&mut self, col: usize) {
919 self.last_action = format!("goto_column_{}", col);
920 }
921
922 fn set_mode(&mut self, mode: AppMode) {
923 self.mode = mode;
924 }
925 fn get_mode(&self) -> AppMode {
926 self.mode.clone()
927 }
928 fn set_status_message(&mut self, message: String) {
929 self.status_message = message;
930 }
931
932 fn toggle_column_pin(&mut self) {
933 self.last_action = "toggle_column_pin".to_string();
934 }
935 fn hide_current_column(&mut self) {
936 self.last_action = "hide_current_column".to_string();
937 }
938 fn unhide_all_columns(&mut self) {
939 self.last_action = "unhide_all_columns".to_string();
940 }
941 fn clear_all_pinned_columns(&mut self) {
942 self.last_action = "clear_all_pinned_columns".to_string();
943 }
944
945 fn export_to_csv(&mut self) {
946 self.last_action = "export_to_csv".to_string();
947 }
948 fn export_to_json(&mut self) {
949 self.last_action = "export_to_json".to_string();
950 }
951
952 fn yank_cell(&mut self) {
953 self.last_action = "yank_cell".to_string();
954 }
955 fn yank_row(&mut self) {
956 self.last_action = "yank_row".to_string();
957 }
958 fn yank_column(&mut self) {
959 self.last_action = "yank_column".to_string();
960 }
961 fn yank_all(&mut self) {
962 self.last_action = "yank_all".to_string();
963 }
964 fn yank_query(&mut self) {
965 self.last_action = "yank_query".to_string();
966 }
967
968 fn toggle_selection_mode(&mut self) {
970 self.last_action = "toggle_selection_mode".to_string();
971 }
972 fn toggle_row_numbers(&mut self) {
973 self.last_action = "toggle_row_numbers".to_string();
974 }
975 fn toggle_compact_mode(&mut self) {
976 self.last_action = "toggle_compact_mode".to_string();
977 }
978 fn toggle_case_insensitive(&mut self) {
979 self.last_action = "toggle_case_insensitive".to_string();
980 }
981 fn toggle_key_indicator(&mut self) {
982 self.last_action = "toggle_key_indicator".to_string();
983 }
984
985 fn clear_filter(&mut self) {
987 self.last_action = "clear_filter".to_string();
988 }
989 fn clear_line(&mut self) {
990 self.last_action = "clear_line".to_string();
991 }
992
993 fn start_search(&mut self) {
995 self.last_action = "start_search".to_string();
996 }
997 fn start_column_search(&mut self) {
998 self.last_action = "start_column_search".to_string();
999 }
1000 fn start_filter(&mut self) {
1001 self.last_action = "start_filter".to_string();
1002 }
1003 fn start_fuzzy_filter(&mut self) {
1004 self.last_action = "start_fuzzy_filter".to_string();
1005 }
1006 fn exit_current_mode(&mut self) {
1007 self.last_action = "exit_current_mode".to_string();
1008 }
1009 fn toggle_debug_mode(&mut self) {
1010 self.last_action = "toggle_debug_mode".to_string();
1011 }
1012
1013 fn move_current_column_left(&mut self) {
1015 self.last_action = "move_current_column_left".to_string();
1016 }
1017 fn move_current_column_right(&mut self) {
1018 self.last_action = "move_current_column_right".to_string();
1019 }
1020
1021 fn next_search_match(&mut self) {
1023 self.last_action = "next_search_match".to_string();
1024 }
1025 fn previous_search_match(&mut self) {
1026 self.last_action = "previous_search_match".to_string();
1027 }
1028
1029 fn show_column_statistics(&mut self) {
1031 self.last_action = "show_column_statistics".to_string();
1032 }
1033 fn cycle_column_packing(&mut self) {
1034 self.last_action = "cycle_column_packing".to_string();
1035 }
1036
1037 fn navigate_to_viewport_top(&mut self) {
1038 self.last_action = "navigate_to_viewport_top".to_string();
1039 }
1040
1041 fn navigate_to_viewport_middle(&mut self) {
1042 self.last_action = "navigate_to_viewport_middle".to_string();
1043 }
1044
1045 fn navigate_to_viewport_bottom(&mut self) {
1046 self.last_action = "navigate_to_viewport_bottom".to_string();
1047 }
1048
1049 fn start_jump_to_row(&mut self) {
1051 self.last_action = "start_jump_to_row".to_string();
1052 }
1053 fn clear_jump_to_row_input(&mut self) {
1054 self.last_action = "clear_jump_to_row_input".to_string();
1055 }
1056
1057 fn toggle_cursor_lock(&mut self) {
1059 self.last_action = "toggle_cursor_lock".to_string();
1060 }
1061 fn toggle_viewport_lock(&mut self) {
1062 self.last_action = "toggle_viewport_lock".to_string();
1063 }
1064
1065 fn move_input_cursor_left(&mut self) {
1067 self.last_action = "move_input_cursor_left".to_string();
1068 }
1069 fn move_input_cursor_right(&mut self) {
1070 self.last_action = "move_input_cursor_right".to_string();
1071 }
1072 fn move_input_cursor_home(&mut self) {
1073 self.last_action = "move_input_cursor_home".to_string();
1074 }
1075 fn move_input_cursor_end(&mut self) {
1076 self.last_action = "move_input_cursor_end".to_string();
1077 }
1078 fn backspace(&mut self) {
1079 self.last_action = "backspace".to_string();
1080 }
1081 fn delete(&mut self) {
1082 self.last_action = "delete".to_string();
1083 }
1084 fn undo(&mut self) {
1085 self.last_action = "undo".to_string();
1086 }
1087 fn redo(&mut self) {
1088 self.last_action = "redo".to_string();
1089 }
1090
1091 fn show_debug_info(&mut self) {
1093 self.last_action = "show_debug_info".to_string();
1094 }
1095 fn show_pretty_query(&mut self) {
1096 self.last_action = "show_pretty_query".to_string();
1097 }
1098 fn show_help(&mut self) {
1099 self.last_action = "show_help".to_string();
1100 }
1101
1102 fn kill_line(&mut self) {
1104 self.last_action = "kill_line".to_string();
1105 }
1106 fn kill_line_backward(&mut self) {
1107 self.last_action = "kill_line_backward".to_string();
1108 }
1109 fn delete_word_backward(&mut self) {
1110 self.last_action = "delete_word_backward".to_string();
1111 }
1112 fn delete_word_forward(&mut self) {
1113 self.last_action = "delete_word_forward".to_string();
1114 }
1115 fn expand_asterisk(&mut self) {
1116 self.last_action = "expand_asterisk".to_string();
1117 }
1118 fn expand_asterisk_visible(&mut self) {
1119 self.last_action = "expand_asterisk_visible".to_string();
1120 }
1121
1122 fn previous_history_command(&mut self) {
1124 self.last_action = "previous_history_command".to_string();
1125 }
1126 fn next_history_command(&mut self) {
1127 self.last_action = "next_history_command".to_string();
1128 }
1129 }
1130
1131 #[test]
1132 fn test_debug_viewport_handler() {
1133 let mut tui = MockTui::new();
1134 let handler = DebugViewportActionHandler;
1135 let context = ActionContext {
1136 mode: AppMode::Results,
1137 selection_mode: crate::app_state_container::SelectionMode::Cell,
1138 has_results: true,
1139 has_filter: false,
1140 has_search: false,
1141 row_count: 100,
1142 column_count: 10,
1143 current_row: 0,
1144 current_column: 0,
1145 };
1146
1147 let result = handler
1149 .handle_action(&Action::ShowDebugInfo, &context, &mut tui)
1150 .unwrap();
1151 assert!(matches!(result, Ok(ActionResult::Handled)));
1152 assert_eq!(tui.last_action, "toggle_debug_mode");
1153
1154 let result = handler
1156 .handle_action(&Action::StartJumpToRow, &context, &mut tui)
1157 .unwrap();
1158 assert!(matches!(result, Ok(ActionResult::Handled)));
1159 assert_eq!(tui.last_action, "start_jump_to_row");
1160
1161 let result = handler
1163 .handle_action(&Action::ToggleCursorLock, &context, &mut tui)
1164 .unwrap();
1165 assert!(matches!(result, Ok(ActionResult::Handled)));
1166 assert_eq!(tui.last_action, "toggle_cursor_lock");
1167
1168 let result = handler
1170 .handle_action(&Action::ToggleViewportLock, &context, &mut tui)
1171 .unwrap();
1172 assert!(matches!(result, Ok(ActionResult::Handled)));
1173 assert_eq!(tui.last_action, "toggle_viewport_lock");
1174
1175 let result = handler.handle_action(&Action::Quit, &context, &mut tui);
1177 assert!(result.is_none());
1178 }
1179
1180 #[test]
1181 fn test_navigation_handler() {
1182 let handler = NavigationActionHandler;
1183 let mut mock_tui = MockTui::new();
1184 let context = ActionContext {
1185 mode: AppMode::Results,
1186 selection_mode: crate::app_state_container::SelectionMode::Row,
1187 has_results: true,
1188 has_filter: false,
1189 has_search: false,
1190 row_count: 10,
1191 column_count: 5,
1192 current_row: 0,
1193 current_column: 0,
1194 };
1195
1196 let action = Action::Navigate(NavigateAction::Up(1));
1197 let result = handler.handle_action(&action, &context, &mut mock_tui);
1198
1199 assert!(result.is_some());
1200 assert_eq!(mock_tui.last_action, "previous_row");
1201
1202 match result.unwrap() {
1203 Ok(ActionResult::Handled) => {}
1204 _ => panic!("Expected Handled result"),
1205 }
1206 }
1207
1208 #[test]
1209 fn test_action_dispatcher() {
1210 let dispatcher = ActionDispatcher::new();
1211 let mut mock_tui = MockTui::new();
1212 let context = ActionContext {
1213 mode: AppMode::Results,
1214 selection_mode: crate::app_state_container::SelectionMode::Row,
1215 has_results: true,
1216 has_filter: false,
1217 has_search: false,
1218 row_count: 10,
1219 column_count: 5,
1220 current_row: 0,
1221 current_column: 0,
1222 };
1223
1224 let action = Action::Navigate(NavigateAction::Down(2));
1226 let result = dispatcher
1227 .dispatch(&action, &context, &mut mock_tui)
1228 .unwrap();
1229
1230 assert_eq!(result, ActionResult::Handled);
1231 assert_eq!(mock_tui.last_action, "next_row"); let action = Action::ExportToCsv;
1235 let result = dispatcher
1236 .dispatch(&action, &context, &mut mock_tui)
1237 .unwrap();
1238
1239 assert_eq!(result, ActionResult::Handled);
1240 assert_eq!(mock_tui.last_action, "export_to_csv");
1241 }
1242
1243 #[test]
1244 fn test_toggle_handler() {
1245 let handler = ToggleActionHandler;
1246 let mut mock_tui = MockTui::new();
1247 let context = ActionContext {
1248 mode: AppMode::Results,
1249 selection_mode: crate::app_state_container::SelectionMode::Row,
1250 has_results: true,
1251 has_filter: false,
1252 has_search: false,
1253 row_count: 10,
1254 column_count: 5,
1255 current_row: 0,
1256 current_column: 0,
1257 };
1258
1259 let action = Action::ToggleSelectionMode;
1261 let result = handler
1262 .handle_action(&action, &context, &mut mock_tui)
1263 .unwrap()
1264 .unwrap();
1265 assert_eq!(result, ActionResult::Handled);
1266 assert_eq!(mock_tui.last_action, "toggle_selection_mode");
1267
1268 let action = Action::ToggleRowNumbers;
1270 let result = handler
1271 .handle_action(&action, &context, &mut mock_tui)
1272 .unwrap()
1273 .unwrap();
1274 assert_eq!(result, ActionResult::Handled);
1275 assert_eq!(mock_tui.last_action, "toggle_row_numbers");
1276 }
1277
1278 #[test]
1279 fn test_clear_handler() {
1280 let handler = ClearActionHandler;
1281 let mut mock_tui = MockTui::new();
1282
1283 let context = ActionContext {
1285 mode: AppMode::Results,
1286 selection_mode: crate::app_state_container::SelectionMode::Row,
1287 has_results: true,
1288 has_filter: true,
1289 has_search: false,
1290 row_count: 10,
1291 column_count: 5,
1292 current_row: 0,
1293 current_column: 0,
1294 };
1295
1296 let action = Action::ClearFilter;
1297 let result = handler
1298 .handle_action(&action, &context, &mut mock_tui)
1299 .unwrap()
1300 .unwrap();
1301 assert_eq!(result, ActionResult::Handled);
1302 assert_eq!(mock_tui.last_action, "clear_filter");
1303
1304 let context = ActionContext {
1306 mode: AppMode::Command,
1307 selection_mode: crate::app_state_container::SelectionMode::Row,
1308 has_results: true,
1309 has_filter: false,
1310 has_search: false,
1311 row_count: 10,
1312 column_count: 5,
1313 current_row: 0,
1314 current_column: 0,
1315 };
1316
1317 let action = Action::ClearLine;
1318 let result = handler
1319 .handle_action(&action, &context, &mut mock_tui)
1320 .unwrap()
1321 .unwrap();
1322 assert_eq!(result, ActionResult::Handled);
1323 assert_eq!(mock_tui.last_action, "clear_line");
1324
1325 let context = ActionContext {
1327 mode: AppMode::Results,
1328 selection_mode: crate::app_state_container::SelectionMode::Row,
1329 has_results: true,
1330 has_filter: false,
1331 has_search: false,
1332 row_count: 10,
1333 column_count: 5,
1334 current_row: 0,
1335 current_column: 0,
1336 };
1337
1338 let action = Action::ClearLine;
1339 let result = handler.handle_action(&action, &context, &mut mock_tui);
1340 assert!(result.is_none());
1341 }
1342
1343 #[test]
1344 fn test_exit_handler() {
1345 let handler = ExitActionHandler;
1346 let mut mock_tui = MockTui::new();
1347 let context = ActionContext {
1348 mode: AppMode::Results,
1349 selection_mode: crate::app_state_container::SelectionMode::Row,
1350 has_results: true,
1351 has_filter: false,
1352 has_search: false,
1353 row_count: 10,
1354 column_count: 5,
1355 current_row: 0,
1356 current_column: 0,
1357 };
1358
1359 let action = Action::Quit;
1361 let result = handler
1362 .handle_action(&action, &context, &mut mock_tui)
1363 .unwrap()
1364 .unwrap();
1365 assert_eq!(result, ActionResult::Exit);
1366
1367 let action = Action::ForceQuit;
1369 let result = handler
1370 .handle_action(&action, &context, &mut mock_tui)
1371 .unwrap()
1372 .unwrap();
1373 assert_eq!(result, ActionResult::Exit);
1374 }
1375}