sql_cli/
action_handler.rs

1use crate::buffer::{AppMode, EditMode};
2use anyhow::Result;
3use tracing::debug;
4
5/// Handles the execution of actions triggered by key bindings
6pub struct ActionHandler;
7
8impl ActionHandler {
9    /// Execute a navigation action
10    pub fn handle_navigation(action: &str, app: &mut impl AppContext) -> Result<bool> {
11        match action {
12            "next_row" => {
13                app.next_row();
14                Ok(false)
15            }
16            "previous_row" => {
17                app.previous_row();
18                Ok(false)
19            }
20            "next_column" | "move_column_right" => {
21                app.move_column_right();
22                Ok(false)
23            }
24            "previous_column" | "move_column_left" => {
25                app.move_column_left();
26                Ok(false)
27            }
28            "page_down" => {
29                app.page_down();
30                Ok(false)
31            }
32            "page_up" => {
33                app.page_up();
34                Ok(false)
35            }
36            "goto_first_row" => {
37                app.goto_first_row();
38                Ok(false)
39            }
40            "goto_last_row" => {
41                app.goto_last_row();
42                Ok(false)
43            }
44            "goto_first_column" => {
45                app.goto_first_column();
46                Ok(false)
47            }
48            "goto_last_column" => {
49                app.goto_last_column();
50                Ok(false)
51            }
52            _ => Ok(false),
53        }
54    }
55
56    /// Execute a mode change action
57    pub fn handle_mode_change(action: &str, app: &mut impl AppContext) -> Result<bool> {
58        match action {
59            "enter_edit_mode" => {
60                app.set_mode(AppMode::Command);
61                app.set_edit_mode(EditMode::SingleLine);
62                Ok(false)
63            }
64            "enter_results_mode" => {
65                app.set_mode(AppMode::Results);
66                Ok(false)
67            }
68            "exit_results_mode" => {
69                app.set_mode(AppMode::Command);
70                Ok(false)
71            }
72            "toggle_help" => {
73                if app.get_mode() == AppMode::Help {
74                    app.set_mode(AppMode::Command);
75                } else {
76                    app.set_mode(AppMode::Help);
77                }
78                Ok(false)
79            }
80            "toggle_debug" => {
81                if app.get_mode() == AppMode::Debug {
82                    app.set_mode(AppMode::Command);
83                } else {
84                    app.set_mode(AppMode::Debug);
85                    app.generate_debug_context();
86                }
87                Ok(false)
88            }
89            "show_pretty_query" => {
90                app.set_mode(AppMode::PrettyQuery);
91                app.generate_pretty_query();
92                Ok(false)
93            }
94            "show_history" => {
95                app.set_mode(AppMode::History);
96                Ok(false)
97            }
98            "show_cache" => {
99                app.set_mode(AppMode::Command);
100                Ok(false)
101            }
102            _ => Ok(false),
103        }
104    }
105
106    /// Execute a yank/clipboard action
107    pub fn handle_yank(action: &str, app: &mut impl AppContext) -> Result<bool> {
108        match action {
109            "yank_cell" => {
110                app.yank_cell();
111                Ok(false)
112            }
113            "yank_row" => {
114                app.yank_row();
115                Ok(false)
116            }
117            "yank_column" => {
118                app.yank_column();
119                Ok(false)
120            }
121            "yank_all" => {
122                app.yank_all();
123                Ok(false)
124            }
125            "paste" => {
126                app.paste_from_clipboard();
127                Ok(false)
128            }
129            _ => Ok(false),
130        }
131    }
132
133    /// Execute an export action
134    pub fn handle_export(action: &str, app: &mut impl AppContext) -> Result<bool> {
135        match action {
136            "export_csv" => {
137                app.export_to_csv();
138                Ok(false)
139            }
140            "export_json" => {
141                app.export_to_json();
142                Ok(false)
143            }
144            _ => Ok(false),
145        }
146    }
147
148    /// Execute a buffer action
149    pub fn handle_buffer(action: &str, app: &mut impl AppContext) -> Result<bool> {
150        match action {
151            "next_buffer" => {
152                app.next_buffer();
153                Ok(false)
154            }
155            "previous_buffer" => {
156                app.previous_buffer();
157                Ok(false)
158            }
159            "close_buffer" => app.close_buffer(),
160            "new_buffer" => {
161                app.new_buffer();
162                Ok(false)
163            }
164            "list_buffers" => {
165                app.list_buffers();
166                Ok(false)
167            }
168            _ => Ok(false),
169        }
170    }
171
172    /// Execute a query action
173    pub fn handle_query(action: &str, app: &mut impl AppContext) -> Result<bool> {
174        match action {
175            "execute_query" => {
176                let query = app.get_input_text();
177                if !query.trim().is_empty() {
178                    app.execute_query(&query)?;
179                }
180                Ok(false)
181            }
182            "handle_completion" => {
183                app.handle_completion();
184                Ok(false)
185            }
186            _ => Ok(false),
187        }
188    }
189
190    /// Execute a filter/search action
191    pub fn handle_filter(action: &str, app: &mut impl AppContext) -> Result<bool> {
192        match action {
193            "start_filter" => {
194                app.set_mode(AppMode::Filter);
195                Ok(false)
196            }
197            "start_fuzzy_filter" => {
198                app.set_mode(AppMode::FuzzyFilter);
199                Ok(false)
200            }
201            "start_search" => {
202                app.set_mode(AppMode::Search);
203                Ok(false)
204            }
205            "clear_filter" => {
206                app.clear_filter();
207                Ok(false)
208            }
209            "next_match" => {
210                app.next_search_match();
211                Ok(false)
212            }
213            "previous_match" => {
214                app.previous_search_match();
215                Ok(false)
216            }
217            _ => Ok(false),
218        }
219    }
220
221    /// Execute a sort action
222    pub fn handle_sort(action: &str, app: &mut impl AppContext) -> Result<bool> {
223        match action {
224            "sort_column" | "sort_column_asc" => {
225                let column = app.get_current_column();
226                app.sort_by_column(column);
227                Ok(false)
228            }
229            "sort_column_desc" => {
230                let column = app.get_current_column();
231                app.sort_by_column_desc(column);
232                Ok(false)
233            }
234            _ => Ok(false),
235        }
236    }
237
238    /// Execute a column action
239    pub fn handle_column(action: &str, app: &mut impl AppContext) -> Result<bool> {
240        match action {
241            "pin_column" | "toggle_column_pin" => {
242                app.toggle_column_pin();
243                Ok(false)
244            }
245            "clear_pins" | "clear_all_pinned_columns" => {
246                app.clear_all_pinned_columns();
247                Ok(false)
248            }
249            "calculate_statistics" => {
250                app.calculate_column_statistics();
251                Ok(false)
252            }
253            _ => Ok(false),
254        }
255    }
256
257    /// Main action dispatcher
258    pub fn execute(action: &str, app: &mut impl AppContext) -> Result<bool> {
259        debug!("Executing action: {}", action);
260
261        // Check for quit action first
262        if action == "quit" {
263            return Ok(true);
264        }
265
266        // Try each category of actions
267        if Self::handle_navigation(action, app)? {
268            return Ok(true);
269        }
270        if Self::handle_mode_change(action, app)? {
271            return Ok(true);
272        }
273        if Self::handle_yank(action, app)? {
274            return Ok(true);
275        }
276        if Self::handle_export(action, app)? {
277            return Ok(true);
278        }
279        if Self::handle_buffer(action, app)? {
280            return Ok(true);
281        }
282        if Self::handle_query(action, app)? {
283            return Ok(true);
284        }
285        if Self::handle_filter(action, app)? {
286            return Ok(true);
287        }
288        if Self::handle_sort(action, app)? {
289            return Ok(true);
290        }
291        if Self::handle_column(action, app)? {
292            return Ok(true);
293        }
294
295        // Unknown action
296        debug!("Unknown action: {}", action);
297        Ok(false)
298    }
299}
300
301/// Trait for application context that actions can operate on
302pub trait AppContext {
303    // Navigation
304    fn next_row(&mut self);
305    fn previous_row(&mut self);
306    fn move_column_left(&mut self);
307    fn move_column_right(&mut self);
308    fn page_down(&mut self);
309    fn page_up(&mut self);
310    fn goto_first_row(&mut self);
311    fn goto_last_row(&mut self);
312    fn goto_first_column(&mut self);
313    fn goto_last_column(&mut self);
314    fn get_current_column(&self) -> usize;
315
316    // Mode management
317    fn get_mode(&self) -> AppMode;
318    fn set_mode(&mut self, mode: AppMode);
319    fn set_edit_mode(&mut self, mode: EditMode);
320
321    // Query operations
322    fn get_input_text(&self) -> String;
323    fn execute_query(&mut self, query: &str) -> Result<()>;
324    fn handle_completion(&mut self);
325
326    // Yank operations
327    fn yank_cell(&mut self);
328    fn yank_row(&mut self);
329    fn yank_column(&mut self);
330    fn yank_all(&mut self);
331    fn paste_from_clipboard(&mut self);
332
333    // Export operations
334    fn export_to_csv(&mut self);
335    fn export_to_json(&mut self);
336
337    // Buffer operations
338    fn next_buffer(&mut self);
339    fn previous_buffer(&mut self);
340    fn close_buffer(&mut self) -> Result<bool>;
341    fn new_buffer(&mut self);
342    fn list_buffers(&mut self);
343
344    // Filter/search operations
345    fn clear_filter(&mut self);
346    fn next_search_match(&mut self);
347    fn previous_search_match(&mut self);
348
349    // Sort operations
350    fn sort_by_column(&mut self, column: usize);
351    fn sort_by_column_desc(&mut self, column: usize);
352
353    // Column operations
354    fn toggle_column_pin(&mut self);
355    fn clear_all_pinned_columns(&mut self);
356    fn calculate_column_statistics(&mut self);
357
358    // Debug operations
359    fn generate_debug_context(&mut self);
360    fn generate_pretty_query(&mut self);
361}