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 expand_asterisk(&mut self);
124    fn expand_asterisk_visible(&mut self);
125
126    // History navigation
127    fn previous_history_command(&mut self);
128    fn next_history_command(&mut self);
129
130    // Viewport lock operations
131    fn toggle_cursor_lock(&mut self);
132    fn toggle_viewport_lock(&mut self);
133}
134
135/// Handler for navigation actions (Up, Down, Left, Right, `PageUp`, etc.)
136pub 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, // Not handled by this handler
213        }
214    }
215
216    fn name(&self) -> &'static str {
217        "Navigation"
218    }
219}
220
221/// Handler for column operations (pin, hide, sort, etc.)
222pub 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
257/// Handler for export operations (CSV, JSON)
258pub 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
285/// Handler for yank operations (cell, row, column, etc.)
286pub 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
315/// Handler for UI mode and display operations
316pub 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            // ShowDebugInfo needs to stay in the legacy switch for now
332            // because it calls toggle_debug_mode() which generates debug data
333            _ => None,
334        }
335    }
336
337    fn name(&self) -> &'static str {
338        "UI"
339    }
340}
341
342/// Handler for toggle operations (selection mode, row numbers, etc.)
343pub 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
382/// Handler for clear/reset operations
383pub 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                // Only handle in Command mode
399                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
415/// Handler for exit/quit actions
416pub 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
436/// Handler for mode transition actions (search modes, debug, etc.)
437pub 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
480/// Handler for viewport navigation actions (H, M, L vim commands)
481pub 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
512/// Handler for column arrangement actions (move left/right)
513pub 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
540/// Handler for search navigation actions
541pub 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                // When Shift+N is pressed and there's no active search,
557                // toggle row numbers instead of navigating search
558                if context.has_search {
559                    tui.previous_search_match();
560                } else {
561                    tui.toggle_row_numbers();
562                }
563                Some(Ok(ActionResult::Handled))
564            }
565            _ => None,
566        }
567    }
568
569    fn name(&self) -> &'static str {
570        "SearchNavigation"
571    }
572}
573
574/// Handler for statistics and display actions
575pub 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
602/// Handler for cursor movement actions in Command mode
603pub 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        // Only handle in Command mode
613        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
643/// Handler for debug and viewport control operations
644pub 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
679/// Handler for function key actions (F1-F12)
680pub 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
735/// Handler for text editing actions in Command mode
736pub 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        // Only handle in Command mode
746        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
808/// Main action dispatcher using visitor pattern
809pub struct ActionDispatcher {
810    handlers: Vec<Box<dyn ActionHandler>>,
811}
812
813impl ActionDispatcher {
814    #[must_use]
815    pub fn new() -> Self {
816        let handlers: Vec<Box<dyn ActionHandler>> = vec![
817            Box::new(NavigationActionHandler),
818            Box::new(ColumnActionHandler),
819            Box::new(ExportActionHandler),
820            Box::new(YankActionHandler),
821            Box::new(UIActionHandler),
822            Box::new(ToggleActionHandler),
823            Box::new(ClearActionHandler),
824            Box::new(ExitActionHandler),
825            Box::new(FunctionKeyActionHandler),
826            Box::new(ModeActionHandler),
827            Box::new(ColumnArrangementActionHandler),
828            Box::new(SearchNavigationActionHandler),
829            Box::new(StatisticsActionHandler),
830            Box::new(InputCursorActionHandler),
831            Box::new(TextEditActionHandler),
832            Box::new(ViewportNavigationHandler),
833            Box::new(DebugViewportActionHandler),
834        ];
835
836        Self { handlers }
837    }
838
839    /// Dispatch an action to the appropriate handler
840    pub fn dispatch(
841        &self,
842        action: &Action,
843        context: &ActionContext,
844        tui: &mut dyn ActionHandlerContext,
845    ) -> Result<ActionResult> {
846        for handler in &self.handlers {
847            if let Some(result) = handler.handle_action(action, context, tui) {
848                return result;
849            }
850        }
851
852        // No handler found for this action
853        Ok(ActionResult::NotHandled)
854    }
855}
856
857impl Default for ActionDispatcher {
858    fn default() -> Self {
859        Self::new()
860    }
861}
862
863#[cfg(test)]
864mod tests {
865    use super::*;
866    use crate::ui::input::actions::{Action, NavigateAction};
867
868    // Mock implementation for testing
869    struct MockTui {
870        pub last_action: String,
871        pub mode: AppMode,
872        pub status_message: String,
873    }
874
875    impl MockTui {
876        fn new() -> Self {
877            Self {
878                last_action: String::new(),
879                mode: AppMode::Results,
880                status_message: String::new(),
881            }
882        }
883    }
884
885    impl ActionHandlerContext for MockTui {
886        fn previous_row(&mut self) {
887            self.last_action = "previous_row".to_string();
888        }
889        fn next_row(&mut self) {
890            self.last_action = "next_row".to_string();
891        }
892        fn move_column_left(&mut self) {
893            self.last_action = "move_column_left".to_string();
894        }
895        fn move_column_right(&mut self) {
896            self.last_action = "move_column_right".to_string();
897        }
898        fn page_up(&mut self) {
899            self.last_action = "page_up".to_string();
900        }
901        fn page_down(&mut self) {
902            self.last_action = "page_down".to_string();
903        }
904        fn goto_first_row(&mut self) {
905            self.last_action = "goto_first_row".to_string();
906        }
907        fn goto_last_row(&mut self) {
908            self.last_action = "goto_last_row".to_string();
909        }
910        fn goto_first_column(&mut self) {
911            self.last_action = "goto_first_column".to_string();
912        }
913        fn goto_last_column(&mut self) {
914            self.last_action = "goto_last_column".to_string();
915        }
916        fn goto_row(&mut self, row: usize) {
917            self.last_action = format!("goto_row_{row}");
918        }
919        fn goto_column(&mut self, col: usize) {
920            self.last_action = format!("goto_column_{col}");
921        }
922
923        fn set_mode(&mut self, mode: AppMode) {
924            self.mode = mode;
925        }
926        fn get_mode(&self) -> AppMode {
927            self.mode.clone()
928        }
929        fn set_status_message(&mut self, message: String) {
930            self.status_message = message;
931        }
932
933        fn toggle_column_pin(&mut self) {
934            self.last_action = "toggle_column_pin".to_string();
935        }
936        fn hide_current_column(&mut self) {
937            self.last_action = "hide_current_column".to_string();
938        }
939        fn unhide_all_columns(&mut self) {
940            self.last_action = "unhide_all_columns".to_string();
941        }
942        fn clear_all_pinned_columns(&mut self) {
943            self.last_action = "clear_all_pinned_columns".to_string();
944        }
945
946        fn export_to_csv(&mut self) {
947            self.last_action = "export_to_csv".to_string();
948        }
949        fn export_to_json(&mut self) {
950            self.last_action = "export_to_json".to_string();
951        }
952
953        fn yank_cell(&mut self) {
954            self.last_action = "yank_cell".to_string();
955        }
956        fn yank_row(&mut self) {
957            self.last_action = "yank_row".to_string();
958        }
959        fn yank_column(&mut self) {
960            self.last_action = "yank_column".to_string();
961        }
962        fn yank_all(&mut self) {
963            self.last_action = "yank_all".to_string();
964        }
965        fn yank_query(&mut self) {
966            self.last_action = "yank_query".to_string();
967        }
968
969        // Toggle operations
970        fn toggle_selection_mode(&mut self) {
971            self.last_action = "toggle_selection_mode".to_string();
972        }
973        fn toggle_row_numbers(&mut self) {
974            self.last_action = "toggle_row_numbers".to_string();
975        }
976        fn toggle_compact_mode(&mut self) {
977            self.last_action = "toggle_compact_mode".to_string();
978        }
979        fn toggle_case_insensitive(&mut self) {
980            self.last_action = "toggle_case_insensitive".to_string();
981        }
982        fn toggle_key_indicator(&mut self) {
983            self.last_action = "toggle_key_indicator".to_string();
984        }
985
986        // Clear operations
987        fn clear_filter(&mut self) {
988            self.last_action = "clear_filter".to_string();
989        }
990        fn clear_line(&mut self) {
991            self.last_action = "clear_line".to_string();
992        }
993
994        // Mode operations
995        fn start_search(&mut self) {
996            self.last_action = "start_search".to_string();
997        }
998        fn start_column_search(&mut self) {
999            self.last_action = "start_column_search".to_string();
1000        }
1001        fn start_filter(&mut self) {
1002            self.last_action = "start_filter".to_string();
1003        }
1004        fn start_fuzzy_filter(&mut self) {
1005            self.last_action = "start_fuzzy_filter".to_string();
1006        }
1007        fn exit_current_mode(&mut self) {
1008            self.last_action = "exit_current_mode".to_string();
1009        }
1010        fn toggle_debug_mode(&mut self) {
1011            self.last_action = "toggle_debug_mode".to_string();
1012        }
1013
1014        // Column arrangement operations
1015        fn move_current_column_left(&mut self) {
1016            self.last_action = "move_current_column_left".to_string();
1017        }
1018        fn move_current_column_right(&mut self) {
1019            self.last_action = "move_current_column_right".to_string();
1020        }
1021
1022        // Search navigation
1023        fn next_search_match(&mut self) {
1024            self.last_action = "next_search_match".to_string();
1025        }
1026        fn previous_search_match(&mut self) {
1027            self.last_action = "previous_search_match".to_string();
1028        }
1029
1030        // Statistics and display
1031        fn show_column_statistics(&mut self) {
1032            self.last_action = "show_column_statistics".to_string();
1033        }
1034        fn cycle_column_packing(&mut self) {
1035            self.last_action = "cycle_column_packing".to_string();
1036        }
1037
1038        fn navigate_to_viewport_top(&mut self) {
1039            self.last_action = "navigate_to_viewport_top".to_string();
1040        }
1041
1042        fn navigate_to_viewport_middle(&mut self) {
1043            self.last_action = "navigate_to_viewport_middle".to_string();
1044        }
1045
1046        fn navigate_to_viewport_bottom(&mut self) {
1047            self.last_action = "navigate_to_viewport_bottom".to_string();
1048        }
1049
1050        // Jump-to-row operations
1051        fn start_jump_to_row(&mut self) {
1052            self.last_action = "start_jump_to_row".to_string();
1053        }
1054        fn clear_jump_to_row_input(&mut self) {
1055            self.last_action = "clear_jump_to_row_input".to_string();
1056        }
1057
1058        // Viewport lock operations
1059        fn toggle_cursor_lock(&mut self) {
1060            self.last_action = "toggle_cursor_lock".to_string();
1061        }
1062        fn toggle_viewport_lock(&mut self) {
1063            self.last_action = "toggle_viewport_lock".to_string();
1064        }
1065
1066        // Input and text editing
1067        fn move_input_cursor_left(&mut self) {
1068            self.last_action = "move_input_cursor_left".to_string();
1069        }
1070        fn move_input_cursor_right(&mut self) {
1071            self.last_action = "move_input_cursor_right".to_string();
1072        }
1073        fn move_input_cursor_home(&mut self) {
1074            self.last_action = "move_input_cursor_home".to_string();
1075        }
1076        fn move_input_cursor_end(&mut self) {
1077            self.last_action = "move_input_cursor_end".to_string();
1078        }
1079        fn backspace(&mut self) {
1080            self.last_action = "backspace".to_string();
1081        }
1082        fn delete(&mut self) {
1083            self.last_action = "delete".to_string();
1084        }
1085        fn undo(&mut self) {
1086            self.last_action = "undo".to_string();
1087        }
1088        fn redo(&mut self) {
1089            self.last_action = "redo".to_string();
1090        }
1091
1092        // Debug and development methods
1093        fn show_debug_info(&mut self) {
1094            self.last_action = "show_debug_info".to_string();
1095        }
1096        fn show_pretty_query(&mut self) {
1097            self.last_action = "show_pretty_query".to_string();
1098        }
1099        fn show_help(&mut self) {
1100            self.last_action = "show_help".to_string();
1101        }
1102
1103        // Text editing operations
1104        fn kill_line(&mut self) {
1105            self.last_action = "kill_line".to_string();
1106        }
1107        fn kill_line_backward(&mut self) {
1108            self.last_action = "kill_line_backward".to_string();
1109        }
1110        fn delete_word_backward(&mut self) {
1111            self.last_action = "delete_word_backward".to_string();
1112        }
1113        fn delete_word_forward(&mut self) {
1114            self.last_action = "delete_word_forward".to_string();
1115        }
1116        fn expand_asterisk(&mut self) {
1117            self.last_action = "expand_asterisk".to_string();
1118        }
1119        fn expand_asterisk_visible(&mut self) {
1120            self.last_action = "expand_asterisk_visible".to_string();
1121        }
1122
1123        // History navigation
1124        fn previous_history_command(&mut self) {
1125            self.last_action = "previous_history_command".to_string();
1126        }
1127        fn next_history_command(&mut self) {
1128            self.last_action = "next_history_command".to_string();
1129        }
1130    }
1131
1132    #[test]
1133    fn test_debug_viewport_handler() {
1134        let mut tui = MockTui::new();
1135        let handler = DebugViewportActionHandler;
1136        let context = ActionContext {
1137            mode: AppMode::Results,
1138            selection_mode: crate::app_state_container::SelectionMode::Cell,
1139            has_results: true,
1140            has_filter: false,
1141            has_search: false,
1142            row_count: 100,
1143            column_count: 10,
1144            current_row: 0,
1145            current_column: 0,
1146        };
1147
1148        // Test ShowDebugInfo
1149        let result = handler
1150            .handle_action(&Action::ShowDebugInfo, &context, &mut tui)
1151            .unwrap();
1152        assert!(matches!(result, Ok(ActionResult::Handled)));
1153        assert_eq!(tui.last_action, "toggle_debug_mode");
1154
1155        // Test StartJumpToRow
1156        let result = handler
1157            .handle_action(&Action::StartJumpToRow, &context, &mut tui)
1158            .unwrap();
1159        assert!(matches!(result, Ok(ActionResult::Handled)));
1160        assert_eq!(tui.last_action, "start_jump_to_row");
1161
1162        // Test ToggleCursorLock
1163        let result = handler
1164            .handle_action(&Action::ToggleCursorLock, &context, &mut tui)
1165            .unwrap();
1166        assert!(matches!(result, Ok(ActionResult::Handled)));
1167        assert_eq!(tui.last_action, "toggle_cursor_lock");
1168
1169        // Test ToggleViewportLock
1170        let result = handler
1171            .handle_action(&Action::ToggleViewportLock, &context, &mut tui)
1172            .unwrap();
1173        assert!(matches!(result, Ok(ActionResult::Handled)));
1174        assert_eq!(tui.last_action, "toggle_viewport_lock");
1175
1176        // Test unhandled action
1177        let result = handler.handle_action(&Action::Quit, &context, &mut tui);
1178        assert!(result.is_none());
1179    }
1180
1181    #[test]
1182    fn test_navigation_handler() {
1183        let handler = NavigationActionHandler;
1184        let mut mock_tui = MockTui::new();
1185        let context = ActionContext {
1186            mode: AppMode::Results,
1187            selection_mode: crate::app_state_container::SelectionMode::Row,
1188            has_results: true,
1189            has_filter: false,
1190            has_search: false,
1191            row_count: 10,
1192            column_count: 5,
1193            current_row: 0,
1194            current_column: 0,
1195        };
1196
1197        let action = Action::Navigate(NavigateAction::Up(1));
1198        let result = handler.handle_action(&action, &context, &mut mock_tui);
1199
1200        assert!(result.is_some());
1201        assert_eq!(mock_tui.last_action, "previous_row");
1202
1203        match result.unwrap() {
1204            Ok(ActionResult::Handled) => {}
1205            _ => panic!("Expected Handled result"),
1206        }
1207    }
1208
1209    #[test]
1210    fn test_action_dispatcher() {
1211        let dispatcher = ActionDispatcher::new();
1212        let mut mock_tui = MockTui::new();
1213        let context = ActionContext {
1214            mode: AppMode::Results,
1215            selection_mode: crate::app_state_container::SelectionMode::Row,
1216            has_results: true,
1217            has_filter: false,
1218            has_search: false,
1219            row_count: 10,
1220            column_count: 5,
1221            current_row: 0,
1222            current_column: 0,
1223        };
1224
1225        // Test navigation action
1226        let action = Action::Navigate(NavigateAction::Down(2));
1227        let result = dispatcher
1228            .dispatch(&action, &context, &mut mock_tui)
1229            .unwrap();
1230
1231        assert_eq!(result, ActionResult::Handled);
1232        assert_eq!(mock_tui.last_action, "next_row"); // Called twice for count=2
1233
1234        // Test export action
1235        let action = Action::ExportToCsv;
1236        let result = dispatcher
1237            .dispatch(&action, &context, &mut mock_tui)
1238            .unwrap();
1239
1240        assert_eq!(result, ActionResult::Handled);
1241        assert_eq!(mock_tui.last_action, "export_to_csv");
1242    }
1243
1244    #[test]
1245    fn test_toggle_handler() {
1246        let handler = ToggleActionHandler;
1247        let mut mock_tui = MockTui::new();
1248        let context = ActionContext {
1249            mode: AppMode::Results,
1250            selection_mode: crate::app_state_container::SelectionMode::Row,
1251            has_results: true,
1252            has_filter: false,
1253            has_search: false,
1254            row_count: 10,
1255            column_count: 5,
1256            current_row: 0,
1257            current_column: 0,
1258        };
1259
1260        // Test toggle selection mode
1261        let action = Action::ToggleSelectionMode;
1262        let result = handler
1263            .handle_action(&action, &context, &mut mock_tui)
1264            .unwrap()
1265            .unwrap();
1266        assert_eq!(result, ActionResult::Handled);
1267        assert_eq!(mock_tui.last_action, "toggle_selection_mode");
1268
1269        // Test toggle row numbers
1270        let action = Action::ToggleRowNumbers;
1271        let result = handler
1272            .handle_action(&action, &context, &mut mock_tui)
1273            .unwrap()
1274            .unwrap();
1275        assert_eq!(result, ActionResult::Handled);
1276        assert_eq!(mock_tui.last_action, "toggle_row_numbers");
1277    }
1278
1279    #[test]
1280    fn test_clear_handler() {
1281        let handler = ClearActionHandler;
1282        let mut mock_tui = MockTui::new();
1283
1284        // Test clear filter
1285        let context = ActionContext {
1286            mode: AppMode::Results,
1287            selection_mode: crate::app_state_container::SelectionMode::Row,
1288            has_results: true,
1289            has_filter: true,
1290            has_search: false,
1291            row_count: 10,
1292            column_count: 5,
1293            current_row: 0,
1294            current_column: 0,
1295        };
1296
1297        let action = Action::ClearFilter;
1298        let result = handler
1299            .handle_action(&action, &context, &mut mock_tui)
1300            .unwrap()
1301            .unwrap();
1302        assert_eq!(result, ActionResult::Handled);
1303        assert_eq!(mock_tui.last_action, "clear_filter");
1304
1305        // Test clear line in Command mode
1306        let context = ActionContext {
1307            mode: AppMode::Command,
1308            selection_mode: crate::app_state_container::SelectionMode::Row,
1309            has_results: true,
1310            has_filter: false,
1311            has_search: false,
1312            row_count: 10,
1313            column_count: 5,
1314            current_row: 0,
1315            current_column: 0,
1316        };
1317
1318        let action = Action::ClearLine;
1319        let result = handler
1320            .handle_action(&action, &context, &mut mock_tui)
1321            .unwrap()
1322            .unwrap();
1323        assert_eq!(result, ActionResult::Handled);
1324        assert_eq!(mock_tui.last_action, "clear_line");
1325
1326        // Test clear line not handled in Results mode
1327        let context = ActionContext {
1328            mode: AppMode::Results,
1329            selection_mode: crate::app_state_container::SelectionMode::Row,
1330            has_results: true,
1331            has_filter: false,
1332            has_search: false,
1333            row_count: 10,
1334            column_count: 5,
1335            current_row: 0,
1336            current_column: 0,
1337        };
1338
1339        let action = Action::ClearLine;
1340        let result = handler.handle_action(&action, &context, &mut mock_tui);
1341        assert!(result.is_none());
1342    }
1343
1344    #[test]
1345    fn test_exit_handler() {
1346        let handler = ExitActionHandler;
1347        let mut mock_tui = MockTui::new();
1348        let context = ActionContext {
1349            mode: AppMode::Results,
1350            selection_mode: crate::app_state_container::SelectionMode::Row,
1351            has_results: true,
1352            has_filter: false,
1353            has_search: false,
1354            row_count: 10,
1355            column_count: 5,
1356            current_row: 0,
1357            current_column: 0,
1358        };
1359
1360        // Test quit
1361        let action = Action::Quit;
1362        let result = handler
1363            .handle_action(&action, &context, &mut mock_tui)
1364            .unwrap()
1365            .unwrap();
1366        assert_eq!(result, ActionResult::Exit);
1367
1368        // Test force quit
1369        let action = Action::ForceQuit;
1370        let result = handler
1371            .handle_action(&action, &context, &mut mock_tui)
1372            .unwrap()
1373            .unwrap();
1374        assert_eq!(result, ActionResult::Exit);
1375    }
1376}