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    pub fn new(code: KeyCode) -> Self {
95        Self {
96            code,
97            modifiers: KeyModifiers::empty(),
98        }
99    }
100
101    pub fn with_ctrl(code: KeyCode) -> Self {
102        Self {
103            code,
104            modifiers: KeyModifiers::CONTROL,
105        }
106    }
107
108    pub fn with_alt(code: KeyCode) -> Self {
109        Self {
110            code,
111            modifiers: KeyModifiers::ALT,
112        }
113    }
114
115    pub fn with_shift(code: KeyCode) -> Self {
116        Self {
117            code,
118            modifiers: KeyModifiers::SHIFT,
119        }
120    }
121
122    pub fn with_modifiers(code: KeyCode, modifiers: KeyModifiers) -> Self {
123        Self { code, modifiers }
124    }
125
126    pub fn from_event(event: &KeyEvent) -> Self {
127        Self {
128            code: event.code,
129            modifiers: event.modifiers,
130        }
131    }
132}
133
134/// Manages key bindings for different modes
135pub struct KeyBindingManager {
136    command_bindings: HashMap<KeyBinding, String>,
137    results_bindings: HashMap<KeyBinding, String>,
138    search_bindings: HashMap<KeyBinding, String>,
139}
140
141impl KeyBindingManager {
142    pub fn new() -> Self {
143        let mut manager = Self {
144            command_bindings: HashMap::new(),
145            results_bindings: HashMap::new(),
146            search_bindings: HashMap::new(),
147        };
148        manager.setup_default_bindings();
149        manager
150    }
151
152    fn setup_default_bindings(&mut self) {
153        // Command mode bindings
154        self.command_bindings
155            .insert(KeyBinding::new(KeyCode::Enter), "execute_query".to_string());
156        self.command_bindings
157            .insert(KeyBinding::new(KeyCode::Tab), "autocomplete".to_string());
158        self.command_bindings
159            .insert(KeyBinding::new(KeyCode::F(1)), "toggle_help".to_string());
160        self.command_bindings.insert(
161            KeyBinding::new(KeyCode::Char('?')),
162            "toggle_help".to_string(),
163        );
164        self.command_bindings.insert(
165            KeyBinding::new(KeyCode::F(3)),
166            "toggle_multiline".to_string(),
167        );
168        self.command_bindings
169            .insert(KeyBinding::new(KeyCode::F(5)), "toggle_debug".to_string());
170        self.command_bindings.insert(
171            KeyBinding::new(KeyCode::F(8)),
172            "toggle_case_insensitive".to_string(),
173        );
174        self.command_bindings.insert(
175            KeyBinding::new(KeyCode::Down),
176            "enter_results_mode".to_string(),
177        );
178
179        // Ctrl combinations
180        self.command_bindings.insert(
181            KeyBinding::with_ctrl(KeyCode::Char('c')),
182            "exit_app".to_string(),
183        );
184        self.command_bindings.insert(
185            KeyBinding::with_ctrl(KeyCode::Char('d')),
186            "exit_app".to_string(),
187        );
188        self.command_bindings.insert(
189            KeyBinding::with_ctrl(KeyCode::Char('x')),
190            "expand_select_star".to_string(),
191        );
192        self.command_bindings.insert(
193            KeyBinding::with_ctrl(KeyCode::Char('r')),
194            "search_history".to_string(),
195        );
196        self.command_bindings.insert(
197            KeyBinding::with_ctrl(KeyCode::Char('p')),
198            "previous_history".to_string(),
199        );
200        self.command_bindings.insert(
201            KeyBinding::with_ctrl(KeyCode::Char('n')),
202            "next_history".to_string(),
203        );
204        self.command_bindings.insert(
205            KeyBinding::with_ctrl(KeyCode::Char('a')),
206            "move_to_line_start".to_string(),
207        );
208        self.command_bindings.insert(
209            KeyBinding::with_ctrl(KeyCode::Char('e')),
210            "move_to_line_end".to_string(),
211        );
212        self.command_bindings.insert(
213            KeyBinding::with_ctrl(KeyCode::Char('w')),
214            "delete_word_backward".to_string(),
215        );
216        self.command_bindings.insert(
217            KeyBinding::with_ctrl(KeyCode::Char('y')),
218            "yank".to_string(),
219        );
220        self.command_bindings.insert(
221            KeyBinding::with_ctrl(KeyCode::Char('z')),
222            "undo".to_string(),
223        );
224        self.command_bindings.insert(
225            KeyBinding::with_ctrl(KeyCode::Char('6')),
226            "quick_switch_buffer".to_string(),
227        );
228
229        // Alt combinations
230        self.command_bindings.insert(
231            KeyBinding::with_alt(KeyCode::Char('d')),
232            "delete_word_forward".to_string(),
233        );
234        self.command_bindings.insert(
235            KeyBinding::with_alt(KeyCode::Char('n')),
236            "new_buffer".to_string(),
237        );
238        self.command_bindings.insert(
239            KeyBinding::with_alt(KeyCode::Char('w')),
240            "close_buffer".to_string(),
241        );
242        self.command_bindings.insert(
243            KeyBinding::with_alt(KeyCode::Char('b')),
244            "list_buffers".to_string(),
245        );
246        self.command_bindings.insert(
247            KeyBinding::with_alt(KeyCode::Up),
248            "previous_history".to_string(),
249        );
250        self.command_bindings.insert(
251            KeyBinding::with_alt(KeyCode::Down),
252            "next_history".to_string(),
253        );
254
255        // Function keys for buffers
256        self.command_bindings.insert(
257            KeyBinding::new(KeyCode::F(11)),
258            "previous_buffer".to_string(),
259        );
260        self.command_bindings
261            .insert(KeyBinding::new(KeyCode::F(12)), "next_buffer".to_string());
262        self.command_bindings.insert(
263            KeyBinding::with_ctrl(KeyCode::PageUp),
264            "previous_buffer".to_string(),
265        );
266        self.command_bindings.insert(
267            KeyBinding::with_ctrl(KeyCode::PageDown),
268            "next_buffer".to_string(),
269        );
270
271        // Results mode bindings
272        self.results_bindings.insert(
273            KeyBinding::new(KeyCode::Char('j')),
274            "move_row_down".to_string(),
275        );
276        self.results_bindings.insert(
277            KeyBinding::new(KeyCode::Char('k')),
278            "move_row_up".to_string(),
279        );
280        self.results_bindings.insert(
281            KeyBinding::new(KeyCode::Char('h')),
282            "move_column_left".to_string(),
283        );
284        self.results_bindings.insert(
285            KeyBinding::new(KeyCode::Char('l')),
286            "move_column_right".to_string(),
287        );
288        self.results_bindings
289            .insert(KeyBinding::new(KeyCode::Down), "move_row_down".to_string());
290        self.results_bindings
291            .insert(KeyBinding::new(KeyCode::Up), "move_row_up".to_string());
292        self.results_bindings.insert(
293            KeyBinding::new(KeyCode::Left),
294            "move_column_left".to_string(),
295        );
296        self.results_bindings.insert(
297            KeyBinding::new(KeyCode::Right),
298            "move_column_right".to_string(),
299        );
300        self.results_bindings
301            .insert(KeyBinding::new(KeyCode::PageDown), "page_down".to_string());
302        self.results_bindings
303            .insert(KeyBinding::new(KeyCode::PageUp), "page_up".to_string());
304        self.results_bindings.insert(
305            KeyBinding::new(KeyCode::Char('g')),
306            "go_to_first_row".to_string(),
307        );
308        self.results_bindings.insert(
309            KeyBinding::new(KeyCode::Char('G')),
310            "go_to_last_row".to_string(),
311        );
312        self.results_bindings.insert(
313            KeyBinding::new(KeyCode::Char('0')),
314            "go_to_first_column".to_string(),
315        );
316        self.results_bindings.insert(
317            KeyBinding::new(KeyCode::Char('^')),
318            "go_to_first_column".to_string(),
319        );
320        self.results_bindings.insert(
321            KeyBinding::new(KeyCode::Char('$')),
322            "go_to_last_column".to_string(),
323        );
324        self.results_bindings.insert(
325            KeyBinding::new(KeyCode::Char('C')),
326            "toggle_compact_mode".to_string(),
327        );
328        self.results_bindings.insert(
329            KeyBinding::new(KeyCode::Char('N')),
330            "toggle_row_numbers".to_string(),
331        );
332        self.results_bindings.insert(
333            KeyBinding::new(KeyCode::Char(':')),
334            "jump_to_row".to_string(),
335        );
336        self.results_bindings.insert(
337            KeyBinding::new(KeyCode::Char('p')),
338            "pin_column".to_string(),
339        );
340        self.results_bindings.insert(
341            KeyBinding::new(KeyCode::Char('P')),
342            "clear_pins".to_string(),
343        );
344        self.results_bindings.insert(
345            KeyBinding::new(KeyCode::Char('/')),
346            "start_search".to_string(),
347        );
348        self.results_bindings.insert(
349            KeyBinding::new(KeyCode::Char('\\')),
350            "start_column_search".to_string(),
351        );
352        self.results_bindings.insert(
353            KeyBinding::new(KeyCode::Char('F')),
354            "start_filter".to_string(),
355        );
356        self.results_bindings.insert(
357            KeyBinding::new(KeyCode::Char('f')),
358            "start_fuzzy_filter".to_string(),
359        );
360        self.results_bindings.insert(
361            KeyBinding::new(KeyCode::Char('n')),
362            "next_search_result".to_string(),
363        );
364        self.results_bindings.insert(
365            KeyBinding::new(KeyCode::Char('N')),
366            "previous_search_result".to_string(),
367        );
368        self.results_bindings.insert(
369            KeyBinding::new(KeyCode::Char('s')),
370            "sort_by_column".to_string(),
371        );
372        self.results_bindings.insert(
373            KeyBinding::new(KeyCode::Char('S')),
374            "show_column_stats".to_string(),
375        );
376        self.results_bindings.insert(
377            KeyBinding::new(KeyCode::Char('v')),
378            "toggle_selection_mode".to_string(),
379        );
380        self.results_bindings.insert(
381            KeyBinding::new(KeyCode::Char('y')),
382            "yank_selection".to_string(),
383        );
384        self.results_bindings
385            .insert(KeyBinding::new(KeyCode::Char('q')), "exit_app".to_string());
386        self.results_bindings
387            .insert(KeyBinding::new(KeyCode::Esc), "back_to_command".to_string());
388        self.results_bindings.insert(
389            KeyBinding::with_ctrl(KeyCode::Char('e')),
390            "export_csv".to_string(),
391        );
392        self.results_bindings.insert(
393            KeyBinding::with_ctrl(KeyCode::Char('j')),
394            "export_json".to_string(),
395        );
396
397        // Search/Filter mode bindings
398        self.search_bindings
399            .insert(KeyBinding::new(KeyCode::Enter), "apply_search".to_string());
400        self.search_bindings
401            .insert(KeyBinding::new(KeyCode::Esc), "cancel_search".to_string());
402    }
403
404    /// Get the action for a key in command mode
405    pub fn get_command_action(&self, key: &KeyEvent) -> Option<&String> {
406        let binding = KeyBinding::from_event(key);
407        self.command_bindings.get(&binding)
408    }
409
410    /// Get the action for a key in results mode
411    pub fn get_results_action(&self, key: &KeyEvent) -> Option<&String> {
412        let binding = KeyBinding::from_event(key);
413        self.results_bindings.get(&binding)
414    }
415
416    /// Get the action for a key in search mode
417    pub fn get_search_action(&self, key: &KeyEvent) -> Option<&String> {
418        let binding = KeyBinding::from_event(key);
419        self.search_bindings.get(&binding)
420    }
421
422    /// Customize a binding
423    pub fn set_binding(&mut self, mode: &str, binding: KeyBinding, action: String) {
424        match mode {
425            "command" => {
426                self.command_bindings.insert(binding, action);
427            }
428            "results" => {
429                self.results_bindings.insert(binding, action);
430            }
431            "search" => {
432                self.search_bindings.insert(binding, action);
433            }
434            _ => {}
435        }
436    }
437
438    /// Remove a binding
439    pub fn remove_binding(&mut self, mode: &str, binding: &KeyBinding) {
440        match mode {
441            "command" => {
442                self.command_bindings.remove(binding);
443            }
444            "results" => {
445                self.results_bindings.remove(binding);
446            }
447            "search" => {
448                self.search_bindings.remove(binding);
449            }
450            _ => {}
451        }
452    }
453
454    /// Get all bindings for a mode (for help display)
455    pub fn get_bindings(&self, mode: &str) -> Vec<(KeyBinding, String)> {
456        let map = match mode {
457            "command" => &self.command_bindings,
458            "results" => &self.results_bindings,
459            "search" => &self.search_bindings,
460            _ => return Vec::new(),
461        };
462
463        let mut bindings: Vec<_> = map.iter().map(|(k, v)| (k.clone(), v.clone())).collect();
464        bindings.sort_by_key(|(k, _)| format!("{:?}", k));
465        bindings
466    }
467}