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