Skip to main content

sql_cli/ui/input/
action_handlers.rs

1//! Action handlers using visitor pattern
2//!
3//! This module implements a visitor pattern for handling different groups of actions,
4//! allowing us to break down the massive `try_handle_action` function into manageable chunks.
5
6use crate::buffer::AppMode;
7use crate::ui::input::actions::{Action, ActionContext, ActionResult, NavigateAction, YankTarget};
8use anyhow::Result;
9
10/// Trait for handling groups of related actions
11pub trait ActionHandler {
12    /// Handle an action if this handler is responsible for it
13    fn handle_action(
14        &self,
15        action: &Action,
16        context: &ActionContext,
17        tui: &mut dyn ActionHandlerContext,
18    ) -> Option<Result<ActionResult>>;
19
20    /// Get the name of this handler (for debugging/logging)
21    fn name(&self) -> &'static str;
22}
23
24/// Context interface for action handlers to interact with the TUI
25/// This abstracts the TUI methods that action handlers need
26pub trait ActionHandlerContext {
27    // Navigation methods
28    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    // Mode and UI state
42    fn set_mode(&mut self, mode: AppMode);
43    fn get_mode(&self) -> AppMode;
44    fn set_status_message(&mut self, message: String);
45
46    // Column operations
47    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    // Export operations
53    fn export_to_csv(&mut self);
54    fn export_to_json(&mut self);
55
56    // Yank operations
57    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    // Toggle operations
64    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    // Clear operations
71    fn clear_filter(&mut self);
72    fn clear_line(&mut self);
73
74    // Mode operations
75    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    // Column arrangement operations
83    fn move_current_column_left(&mut self);
84    fn move_current_column_right(&mut self);
85
86    // Search navigation
87    fn next_search_match(&mut self);
88    fn previous_search_match(&mut self);
89
90    // Statistics and display
91    fn show_column_statistics(&mut self);
92    fn cycle_column_packing(&mut self);
93
94    // Viewport navigation
95    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    // Input and text editing
100    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    // Jump-to-row operations
110    fn start_jump_to_row(&mut self);
111    fn clear_jump_to_row_input(&mut self);
112
113    // Debug and development
114    fn show_debug_info(&mut self);
115    fn show_pretty_query(&mut self);
116    fn show_help(&mut self);
117
118    // Text editing
119    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    // History navigation
129    fn previous_history_command(&mut self);
130    fn next_history_command(&mut self);
131
132    // Viewport lock operations
133    fn toggle_cursor_lock(&mut self);
134    fn toggle_viewport_lock(&mut self);
135}
136
137/// Handler for navigation actions (Up, Down, Left, Right, `PageUp`, etc.)
138pub 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, // Not handled by this handler
215        }
216    }
217
218    fn name(&self) -> &'static str {
219        "Navigation"
220    }
221}
222
223/// Handler for column operations (pin, hide, sort, etc.)
224pub 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
259/// Handler for export operations (CSV, JSON)
260pub 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
287/// Handler for yank operations (cell, row, column, etc.)
288pub 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
317/// Handler for UI mode and display operations
318pub 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            // ShowDebugInfo needs to stay in the legacy switch for now
334            // because it calls toggle_debug_mode() which generates debug data
335            _ => None,
336        }
337    }
338
339    fn name(&self) -> &'static str {
340        "UI"
341    }
342}
343
344/// Handler for toggle operations (selection mode, row numbers, etc.)
345pub 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
384/// Handler for clear/reset operations
385pub 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                // Only handle in Command mode
401                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
417/// Handler for exit/quit actions
418pub 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
438/// Handler for mode transition actions (search modes, debug, etc.)
439pub 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
482/// Handler for viewport navigation actions (H, M, L vim commands)
483pub 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
514/// Handler for column arrangement actions (move left/right)
515pub 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
542/// Handler for search navigation actions
543pub 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                // When Shift+N is pressed and there's no active search,
559                // toggle row numbers instead of navigating search
560                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
576/// Handler for statistics and display actions
577pub 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
604/// Handler for cursor movement actions in Command mode
605pub 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        // Only handle in Command mode
615        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
645/// Handler for debug and viewport control operations
646pub 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
681/// Handler for function key actions (F1-F12)
682pub 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
737/// Handler for text editing actions in Command mode
738pub 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        // Only handle in Command mode
748        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
818/// Main action dispatcher using visitor pattern
819pub 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    /// Dispatch an action to the appropriate handler
850    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        // No handler found for this action
863        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    // Mock implementation for testing
879    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        // Toggle operations
980        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        // Clear operations
997        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        // Mode operations
1005        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        // Column arrangement operations
1025        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        // Search navigation
1033        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        // Statistics and display
1041        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        // Jump-to-row operations
1061        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        // Viewport lock operations
1069        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        // Input and text editing
1077        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        // Debug and development methods
1103        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        // Text editing operations
1114        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        // History navigation
1140        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        // Test ShowDebugInfo
1165        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        // Test StartJumpToRow
1172        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        // Test ToggleCursorLock
1179        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        // Test ToggleViewportLock
1186        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        // Test unhandled action
1193        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        // Test navigation action
1242        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"); // Called twice for count=2
1249
1250        // Test export action
1251        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        // Test toggle selection mode
1277        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        // Test toggle row numbers
1286        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        // Test clear filter
1301        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        // Test clear line in Command mode
1322        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        // Test clear line not handled in Results mode
1343        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        // Test quit
1377        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        // Test force quit
1385        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}