sql_cli/config/
key_bindings.rs

1use crossterm::event::{KeyCode, KeyEvent, KeyModifiers};
2use std::collections::HashMap;
3
4/// Type alias for action callbacks
5pub type ActionCallback = Box<dyn Fn(&mut dyn ActionHandler) -> bool>;
6
7/// Trait that the TUI must implement to handle actions
8pub trait ActionHandler {
9    // Command mode actions
10    fn execute_query(&mut self) -> bool;
11    fn toggle_multiline(&mut self) -> bool;
12    fn expand_select_star(&mut self) -> bool;
13    fn search_history(&mut self) -> bool;
14    fn previous_history(&mut self) -> bool;
15    fn next_history(&mut self) -> bool;
16    fn autocomplete(&mut self) -> bool;
17    fn toggle_help(&mut self) -> bool;
18    fn toggle_debug(&mut self) -> bool;
19    fn toggle_case_insensitive(&mut self) -> bool;
20    fn enter_results_mode(&mut self) -> bool;
21    fn exit_app(&mut self) -> bool;
22
23    // Buffer management
24    fn next_buffer(&mut self) -> bool;
25    fn previous_buffer(&mut self) -> bool;
26    fn quick_switch_buffer(&mut self) -> bool;
27    fn new_buffer(&mut self) -> bool;
28    fn close_buffer(&mut self) -> bool;
29    fn list_buffers(&mut self) -> bool;
30
31    // Text editing
32    fn move_cursor_left(&mut self) -> bool;
33    fn move_cursor_right(&mut self) -> bool;
34    fn move_cursor_up(&mut self) -> bool;
35    fn move_cursor_down(&mut self) -> bool;
36    fn move_to_line_start(&mut self) -> bool;
37    fn move_to_line_end(&mut self) -> bool;
38    fn move_word_backward(&mut self) -> bool;
39    fn move_word_forward(&mut self) -> bool;
40    fn delete_char_backward(&mut self) -> bool;
41    fn delete_char_forward(&mut self) -> bool;
42    fn delete_word_backward(&mut self) -> bool;
43    fn delete_word_forward(&mut self) -> bool;
44    fn kill_line_forward(&mut self) -> bool;
45    fn kill_line_backward(&mut self) -> bool;
46    fn yank(&mut self) -> bool;
47    fn undo(&mut self) -> bool;
48    fn redo(&mut self) -> bool;
49    fn insert_char(&mut self, c: char) -> bool;
50
51    // Results mode actions
52    fn move_row_up(&mut self) -> bool;
53    fn move_row_down(&mut self) -> bool;
54    fn move_column_left(&mut self) -> bool;
55    fn move_column_right(&mut self) -> bool;
56    fn page_up(&mut self) -> bool;
57    fn page_down(&mut self) -> bool;
58    fn go_to_first_row(&mut self) -> bool;
59    fn go_to_last_row(&mut self) -> bool;
60    fn go_to_first_column(&mut self) -> bool;
61    fn go_to_last_column(&mut self) -> bool;
62    fn toggle_compact_mode(&mut self) -> bool;
63    fn toggle_row_numbers(&mut self) -> bool;
64    fn jump_to_row(&mut self) -> bool;
65    fn pin_column(&mut self) -> bool;
66    fn clear_pins(&mut self) -> bool;
67    fn start_search(&mut self) -> bool;
68    fn start_column_search(&mut self) -> bool;
69    fn start_filter(&mut self) -> bool;
70    fn start_fuzzy_filter(&mut self) -> bool;
71    fn next_search_result(&mut self) -> bool;
72    fn previous_search_result(&mut self) -> bool;
73    fn sort_by_column(&mut self) -> bool;
74    fn show_column_stats(&mut self) -> bool;
75    fn toggle_selection_mode(&mut self) -> bool;
76    fn yank_selection(&mut self) -> bool;
77    fn export_csv(&mut self) -> bool;
78    fn export_json(&mut self) -> bool;
79    fn back_to_command(&mut self) -> bool;
80
81    // Search/Filter mode actions
82    fn apply_search(&mut self) -> bool;
83    fn cancel_search(&mut self) -> bool;
84}
85
86/// Represents a key binding
87#[derive(Debug, Clone, PartialEq, Eq, Hash)]
88pub struct KeyBinding {
89    pub code: KeyCode,
90    pub modifiers: KeyModifiers,
91}
92
93impl KeyBinding {
94    #[must_use]
95    pub fn new(code: KeyCode) -> Self {
96        Self {
97            code,
98            modifiers: KeyModifiers::empty(),
99        }
100    }
101
102    #[must_use]
103    pub fn with_ctrl(code: KeyCode) -> Self {
104        Self {
105            code,
106            modifiers: KeyModifiers::CONTROL,
107        }
108    }
109
110    #[must_use]
111    pub fn with_alt(code: KeyCode) -> Self {
112        Self {
113            code,
114            modifiers: KeyModifiers::ALT,
115        }
116    }
117
118    #[must_use]
119    pub fn with_shift(code: KeyCode) -> Self {
120        Self {
121            code,
122            modifiers: KeyModifiers::SHIFT,
123        }
124    }
125
126    #[must_use]
127    pub fn with_modifiers(code: KeyCode, modifiers: KeyModifiers) -> Self {
128        Self { code, modifiers }
129    }
130
131    #[must_use]
132    pub fn from_event(event: &KeyEvent) -> Self {
133        Self {
134            code: event.code,
135            modifiers: event.modifiers,
136        }
137    }
138}
139
140/// Manages key bindings for different modes
141pub struct KeyBindingManager {
142    command_bindings: HashMap<KeyBinding, String>,
143    results_bindings: HashMap<KeyBinding, String>,
144    search_bindings: HashMap<KeyBinding, String>,
145}
146
147impl Default for KeyBindingManager {
148    fn default() -> Self {
149        Self::new()
150    }
151}
152
153impl KeyBindingManager {
154    #[must_use]
155    pub fn new() -> Self {
156        let mut manager = Self {
157            command_bindings: HashMap::new(),
158            results_bindings: HashMap::new(),
159            search_bindings: HashMap::new(),
160        };
161        manager.setup_default_bindings();
162        manager
163    }
164
165    fn setup_default_bindings(&mut self) {
166        // Command mode bindings
167        self.command_bindings
168            .insert(KeyBinding::new(KeyCode::Enter), "execute_query".to_string());
169        self.command_bindings
170            .insert(KeyBinding::new(KeyCode::Tab), "autocomplete".to_string());
171        self.command_bindings
172            .insert(KeyBinding::new(KeyCode::F(1)), "toggle_help".to_string());
173        self.command_bindings.insert(
174            KeyBinding::new(KeyCode::Char('?')),
175            "toggle_help".to_string(),
176        );
177        self.command_bindings.insert(
178            KeyBinding::new(KeyCode::F(3)),
179            "toggle_multiline".to_string(),
180        );
181        self.command_bindings
182            .insert(KeyBinding::new(KeyCode::F(5)), "toggle_debug".to_string());
183        self.command_bindings.insert(
184            KeyBinding::new(KeyCode::F(8)),
185            "toggle_case_insensitive".to_string(),
186        );
187        self.command_bindings.insert(
188            KeyBinding::new(KeyCode::Down),
189            "enter_results_mode".to_string(),
190        );
191
192        // Ctrl combinations
193        self.command_bindings.insert(
194            KeyBinding::with_ctrl(KeyCode::Char('c')),
195            "exit_app".to_string(),
196        );
197        self.command_bindings.insert(
198            KeyBinding::with_ctrl(KeyCode::Char('d')),
199            "exit_app".to_string(),
200        );
201        self.command_bindings.insert(
202            KeyBinding::with_ctrl(KeyCode::Char('x')),
203            "expand_select_star".to_string(),
204        );
205        self.command_bindings.insert(
206            KeyBinding::with_ctrl(KeyCode::Char('r')),
207            "search_history".to_string(),
208        );
209        self.command_bindings.insert(
210            KeyBinding::with_ctrl(KeyCode::Char('p')),
211            "previous_history".to_string(),
212        );
213        self.command_bindings.insert(
214            KeyBinding::with_ctrl(KeyCode::Char('n')),
215            "next_history".to_string(),
216        );
217        self.command_bindings.insert(
218            KeyBinding::with_ctrl(KeyCode::Char('a')),
219            "move_to_line_start".to_string(),
220        );
221        self.command_bindings.insert(
222            KeyBinding::with_ctrl(KeyCode::Char('e')),
223            "move_to_line_end".to_string(),
224        );
225        self.command_bindings.insert(
226            KeyBinding::with_ctrl(KeyCode::Char('w')),
227            "delete_word_backward".to_string(),
228        );
229        self.command_bindings.insert(
230            KeyBinding::with_ctrl(KeyCode::Char('y')),
231            "yank".to_string(),
232        );
233        self.command_bindings.insert(
234            KeyBinding::with_ctrl(KeyCode::Char('z')),
235            "undo".to_string(),
236        );
237        self.command_bindings.insert(
238            KeyBinding::with_ctrl(KeyCode::Char('6')),
239            "quick_switch_buffer".to_string(),
240        );
241
242        // Alt combinations
243        self.command_bindings.insert(
244            KeyBinding::with_alt(KeyCode::Char('d')),
245            "delete_word_forward".to_string(),
246        );
247        self.command_bindings.insert(
248            KeyBinding::with_alt(KeyCode::Char('n')),
249            "new_buffer".to_string(),
250        );
251        self.command_bindings.insert(
252            KeyBinding::with_alt(KeyCode::Char('w')),
253            "close_buffer".to_string(),
254        );
255        self.command_bindings.insert(
256            KeyBinding::with_alt(KeyCode::Char('b')),
257            "list_buffers".to_string(),
258        );
259        self.command_bindings.insert(
260            KeyBinding::with_alt(KeyCode::Up),
261            "previous_history".to_string(),
262        );
263        self.command_bindings.insert(
264            KeyBinding::with_alt(KeyCode::Down),
265            "next_history".to_string(),
266        );
267
268        // Function keys for buffers
269        self.command_bindings.insert(
270            KeyBinding::new(KeyCode::F(11)),
271            "previous_buffer".to_string(),
272        );
273        self.command_bindings
274            .insert(KeyBinding::new(KeyCode::F(12)), "next_buffer".to_string());
275        self.command_bindings.insert(
276            KeyBinding::with_ctrl(KeyCode::PageUp),
277            "previous_buffer".to_string(),
278        );
279        self.command_bindings.insert(
280            KeyBinding::with_ctrl(KeyCode::PageDown),
281            "next_buffer".to_string(),
282        );
283
284        // Results mode bindings
285        self.results_bindings.insert(
286            KeyBinding::new(KeyCode::Char('j')),
287            "move_row_down".to_string(),
288        );
289        self.results_bindings.insert(
290            KeyBinding::new(KeyCode::Char('k')),
291            "move_row_up".to_string(),
292        );
293        self.results_bindings.insert(
294            KeyBinding::new(KeyCode::Char('h')),
295            "move_column_left".to_string(),
296        );
297        self.results_bindings.insert(
298            KeyBinding::new(KeyCode::Char('l')),
299            "move_column_right".to_string(),
300        );
301        self.results_bindings
302            .insert(KeyBinding::new(KeyCode::Down), "move_row_down".to_string());
303        self.results_bindings
304            .insert(KeyBinding::new(KeyCode::Up), "move_row_up".to_string());
305        self.results_bindings.insert(
306            KeyBinding::new(KeyCode::Left),
307            "move_column_left".to_string(),
308        );
309        self.results_bindings.insert(
310            KeyBinding::new(KeyCode::Right),
311            "move_column_right".to_string(),
312        );
313        self.results_bindings
314            .insert(KeyBinding::new(KeyCode::PageDown), "page_down".to_string());
315        self.results_bindings
316            .insert(KeyBinding::new(KeyCode::PageUp), "page_up".to_string());
317        self.results_bindings.insert(
318            KeyBinding::new(KeyCode::Char('g')),
319            "go_to_first_row".to_string(),
320        );
321        self.results_bindings.insert(
322            KeyBinding::new(KeyCode::Char('G')),
323            "go_to_last_row".to_string(),
324        );
325        self.results_bindings.insert(
326            KeyBinding::new(KeyCode::Char('0')),
327            "go_to_first_column".to_string(),
328        );
329        self.results_bindings.insert(
330            KeyBinding::new(KeyCode::Char('^')),
331            "go_to_first_column".to_string(),
332        );
333        self.results_bindings.insert(
334            KeyBinding::new(KeyCode::Char('$')),
335            "go_to_last_column".to_string(),
336        );
337        self.results_bindings.insert(
338            KeyBinding::new(KeyCode::Char('C')),
339            "toggle_compact_mode".to_string(),
340        );
341        self.results_bindings.insert(
342            KeyBinding::new(KeyCode::Char('N')),
343            "toggle_row_numbers".to_string(),
344        );
345        self.results_bindings.insert(
346            KeyBinding::new(KeyCode::Char(':')),
347            "jump_to_row".to_string(),
348        );
349        self.results_bindings.insert(
350            KeyBinding::new(KeyCode::Char('p')),
351            "pin_column".to_string(),
352        );
353        self.results_bindings.insert(
354            KeyBinding::new(KeyCode::Char('P')),
355            "clear_pins".to_string(),
356        );
357        self.results_bindings.insert(
358            KeyBinding::new(KeyCode::Char('/')),
359            "start_search".to_string(),
360        );
361        self.results_bindings.insert(
362            KeyBinding::new(KeyCode::Char('\\')),
363            "start_column_search".to_string(),
364        );
365        self.results_bindings.insert(
366            KeyBinding::new(KeyCode::Char('F')),
367            "start_filter".to_string(),
368        );
369        self.results_bindings.insert(
370            KeyBinding::new(KeyCode::Char('f')),
371            "start_fuzzy_filter".to_string(),
372        );
373        self.results_bindings.insert(
374            KeyBinding::new(KeyCode::Char('n')),
375            "next_search_result".to_string(),
376        );
377        self.results_bindings.insert(
378            KeyBinding::new(KeyCode::Char('N')),
379            "previous_search_result".to_string(),
380        );
381        self.results_bindings.insert(
382            KeyBinding::new(KeyCode::Char('s')),
383            "sort_by_column".to_string(),
384        );
385        self.results_bindings.insert(
386            KeyBinding::new(KeyCode::Char('S')),
387            "show_column_stats".to_string(),
388        );
389        self.results_bindings.insert(
390            KeyBinding::new(KeyCode::Char('v')),
391            "toggle_selection_mode".to_string(),
392        );
393        self.results_bindings.insert(
394            KeyBinding::new(KeyCode::Char('y')),
395            "yank_selection".to_string(),
396        );
397        self.results_bindings
398            .insert(KeyBinding::new(KeyCode::Char('q')), "exit_app".to_string());
399        self.results_bindings
400            .insert(KeyBinding::new(KeyCode::Esc), "back_to_command".to_string());
401        self.results_bindings.insert(
402            KeyBinding::with_ctrl(KeyCode::Char('e')),
403            "export_csv".to_string(),
404        );
405        self.results_bindings.insert(
406            KeyBinding::with_ctrl(KeyCode::Char('j')),
407            "export_json".to_string(),
408        );
409
410        // Search/Filter mode bindings
411        self.search_bindings
412            .insert(KeyBinding::new(KeyCode::Enter), "apply_search".to_string());
413        self.search_bindings
414            .insert(KeyBinding::new(KeyCode::Esc), "cancel_search".to_string());
415    }
416
417    /// Get the action for a key in command mode
418    #[must_use]
419    pub fn get_command_action(&self, key: &KeyEvent) -> Option<&String> {
420        let binding = KeyBinding::from_event(key);
421        self.command_bindings.get(&binding)
422    }
423
424    /// Get the action for a key in results mode
425    #[must_use]
426    pub fn get_results_action(&self, key: &KeyEvent) -> Option<&String> {
427        let binding = KeyBinding::from_event(key);
428        self.results_bindings.get(&binding)
429    }
430
431    /// Get the action for a key in search mode
432    #[must_use]
433    pub fn get_search_action(&self, key: &KeyEvent) -> Option<&String> {
434        let binding = KeyBinding::from_event(key);
435        self.search_bindings.get(&binding)
436    }
437
438    /// Customize a binding
439    pub fn set_binding(&mut self, mode: &str, binding: KeyBinding, action: String) {
440        match mode {
441            "command" => {
442                self.command_bindings.insert(binding, action);
443            }
444            "results" => {
445                self.results_bindings.insert(binding, action);
446            }
447            "search" => {
448                self.search_bindings.insert(binding, action);
449            }
450            _ => {}
451        }
452    }
453
454    /// Remove a binding
455    pub fn remove_binding(&mut self, mode: &str, binding: &KeyBinding) {
456        match mode {
457            "command" => {
458                self.command_bindings.remove(binding);
459            }
460            "results" => {
461                self.results_bindings.remove(binding);
462            }
463            "search" => {
464                self.search_bindings.remove(binding);
465            }
466            _ => {}
467        }
468    }
469
470    /// Get all bindings for a mode (for help display)
471    #[must_use]
472    pub fn get_bindings(&self, mode: &str) -> Vec<(KeyBinding, String)> {
473        let map = match mode {
474            "command" => &self.command_bindings,
475            "results" => &self.results_bindings,
476            "search" => &self.search_bindings,
477            _ => return Vec::new(),
478        };
479
480        let mut bindings: Vec<_> = map.iter().map(|(k, v)| (k.clone(), v.clone())).collect();
481        bindings.sort_by_key(|(k, _)| format!("{k:?}"));
482        bindings
483    }
484}