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