Skip to main content

fresh/app/
keybinding_editor_actions.rs

1//! Keybinding editor action handling
2//!
3//! This module provides the action handlers for the keybinding editor modal.
4
5use super::keybinding_editor::KeybindingEditor;
6use super::Editor;
7use crate::input::handler::InputResult;
8use crate::view::keybinding_editor::{handle_keybinding_editor_input, KeybindingEditorAction};
9use crate::view::ui::point_in_rect;
10use crossterm::event::{KeyEvent, MouseButton, MouseEvent, MouseEventKind};
11
12impl Editor {
13    /// Open the keybinding editor modal
14    pub fn open_keybinding_editor(&mut self) {
15        let config_path = self.dir_context.config_path().display().to_string();
16        let cmd_registry = self.command_registry.read().unwrap();
17        let keybindings = self.keybindings.read().unwrap();
18        self.keybinding_editor = Some(KeybindingEditor::new(
19            &self.config,
20            &keybindings,
21            &self.mode_registry,
22            &cmd_registry,
23            config_path,
24        ));
25    }
26
27    /// Handle input when keybinding editor is active
28    pub fn handle_keybinding_editor_input(&mut self, event: &KeyEvent) -> InputResult {
29        let mut editor = match self.keybinding_editor.take() {
30            Some(e) => e,
31            None => return InputResult::Ignored,
32        };
33
34        let action = handle_keybinding_editor_input(&mut editor, event);
35
36        match action {
37            KeybindingEditorAction::Consumed => {
38                self.keybinding_editor = Some(editor);
39                InputResult::Consumed
40            }
41            KeybindingEditorAction::Close => {
42                // Close without saving
43                self.set_status_message("Keybinding editor closed".to_string());
44                InputResult::Consumed
45            }
46            KeybindingEditorAction::SaveAndClose => {
47                // Save custom bindings to config
48                self.save_keybinding_editor_changes(&editor);
49                InputResult::Consumed
50            }
51            KeybindingEditorAction::StatusMessage(msg) => {
52                self.set_status_message(msg);
53                self.keybinding_editor = Some(editor);
54                InputResult::Consumed
55            }
56        }
57    }
58
59    /// Save keybinding editor changes to config
60    fn save_keybinding_editor_changes(&mut self, editor: &KeybindingEditor) {
61        if !editor.has_changes {
62            return;
63        }
64
65        // Remove deleted custom bindings from config
66        for remove in editor.get_pending_removes() {
67            self.config.keybindings.retain(|kb| {
68                !(kb.action == remove.action
69                    && kb.key == remove.key
70                    && kb.modifiers == remove.modifiers
71                    && kb.when == remove.when)
72            });
73        }
74
75        // Add new custom bindings
76        let new_bindings = editor.get_custom_bindings();
77        for binding in new_bindings {
78            self.config.keybindings.push(binding);
79        }
80
81        // Rebuild the keybinding resolver
82        *self.keybindings.write().unwrap() =
83            crate::input::keybindings::KeybindingResolver::new(&self.config);
84
85        // Save to config file via the pending changes mechanism
86        let config_value = match serde_json::to_value(&self.config.keybindings) {
87            Ok(v) => v,
88            Err(e) => {
89                self.set_status_message(format!("Failed to serialize keybindings: {}", e));
90                return;
91            }
92        };
93
94        let mut changes = std::collections::HashMap::new();
95        changes.insert("/keybindings".to_string(), config_value);
96
97        let resolver = crate::config_io::ConfigResolver::new(
98            self.dir_context.clone(),
99            self.working_dir.clone(),
100        );
101
102        match resolver.save_changes_to_layer(
103            &changes,
104            &std::collections::HashSet::new(),
105            crate::config_io::ConfigLayer::User,
106        ) {
107            Ok(()) => {
108                self.set_status_message("Keybinding changes saved".to_string());
109            }
110            Err(e) => {
111                self.set_status_message(format!("Failed to save keybindings: {}", e));
112            }
113        }
114    }
115
116    /// Check if keybinding editor is active
117    pub fn is_keybinding_editor_active(&self) -> bool {
118        self.keybinding_editor.is_some()
119    }
120
121    /// Handle mouse events when keybinding editor is active
122    /// Returns Ok(true) if a re-render is needed
123    pub fn handle_keybinding_editor_mouse(
124        &mut self,
125        mouse_event: MouseEvent,
126    ) -> anyhow::Result<bool> {
127        let mut editor = match self.keybinding_editor.take() {
128            Some(e) => e,
129            None => return Ok(false),
130        };
131
132        let col = mouse_event.column;
133        let row = mouse_event.row;
134        let layout = &editor.layout;
135
136        // All mouse events inside modal are consumed (masked from reaching underlying editor)
137        // Events outside the modal are ignored (but still consumed to prevent leaking)
138        if !point_in_rect(layout.modal_area, col, row) {
139            self.keybinding_editor = Some(editor);
140            return Ok(false);
141        }
142
143        match mouse_event.kind {
144            MouseEventKind::ScrollUp => {
145                // Scroll the table
146                if editor.edit_dialog.is_none() && !editor.showing_confirm_dialog {
147                    editor.select_prev();
148                }
149            }
150            MouseEventKind::ScrollDown => {
151                if editor.edit_dialog.is_none() && !editor.showing_confirm_dialog {
152                    editor.select_next();
153                }
154            }
155            MouseEventKind::Down(MouseButton::Left) => {
156                // Handle confirm dialog clicks first
157                if editor.showing_confirm_dialog {
158                    if let Some((save_r, discard_r, cancel_r)) = layout.confirm_buttons {
159                        if point_in_rect(save_r, col, row) {
160                            self.save_keybinding_editor_changes(&editor);
161                            return Ok(true);
162                        } else if point_in_rect(discard_r, col, row) {
163                            self.set_status_message("Keybinding editor closed".to_string());
164                            return Ok(true);
165                        } else if point_in_rect(cancel_r, col, row) {
166                            editor.showing_confirm_dialog = false;
167                        }
168                    }
169                    self.keybinding_editor = Some(editor);
170                    return Ok(true);
171                }
172
173                // Handle edit dialog clicks
174                if editor.edit_dialog.is_some() {
175                    // Button clicks
176                    if let Some((save_r, cancel_r)) = layout.dialog_buttons {
177                        if point_in_rect(save_r, col, row) {
178                            // Save button
179                            if let Some(err) = editor.apply_edit_dialog() {
180                                self.set_status_message(err);
181                            }
182                            self.keybinding_editor = Some(editor);
183                            return Ok(true);
184                        } else if point_in_rect(cancel_r, col, row) {
185                            // Cancel button - close dialog
186                            editor.edit_dialog = None;
187                            self.keybinding_editor = Some(editor);
188                            return Ok(true);
189                        }
190                    }
191                    // Field clicks
192                    if let Some(r) = layout.dialog_key_field {
193                        if point_in_rect(r, col, row) {
194                            if let Some(ref mut dialog) = editor.edit_dialog {
195                                dialog.focus_area = 0;
196                                dialog.mode = crate::app::keybinding_editor::EditMode::RecordingKey;
197                            }
198                        }
199                    }
200                    if let Some(r) = layout.dialog_action_field {
201                        if point_in_rect(r, col, row) {
202                            if let Some(ref mut dialog) = editor.edit_dialog {
203                                dialog.focus_area = 1;
204                                dialog.mode =
205                                    crate::app::keybinding_editor::EditMode::EditingAction;
206                            }
207                        }
208                    }
209                    if let Some(r) = layout.dialog_context_field {
210                        if point_in_rect(r, col, row) {
211                            if let Some(ref mut dialog) = editor.edit_dialog {
212                                dialog.focus_area = 2;
213                                dialog.mode =
214                                    crate::app::keybinding_editor::EditMode::EditingContext;
215                            }
216                        }
217                    }
218                    self.keybinding_editor = Some(editor);
219                    return Ok(true);
220                }
221
222                // Click on search bar to focus it
223                if let Some(search_r) = layout.search_bar {
224                    if point_in_rect(search_r, col, row) {
225                        editor.start_search();
226                        self.keybinding_editor = Some(editor);
227                        return Ok(true);
228                    }
229                }
230
231                // Click on table row to select (or toggle section header)
232                let table_area = layout.table_area;
233                let first_row_y = layout.table_first_row_y;
234                if point_in_rect(table_area, col, row) && row >= first_row_y {
235                    let clicked_row = (row - first_row_y) as usize;
236                    let new_selected = editor.scroll.offset as usize + clicked_row;
237                    if new_selected < editor.display_rows.len() {
238                        editor.selected = new_selected;
239                        if editor.selected_is_section_header() {
240                            editor.toggle_section_at_selected();
241                        }
242                    }
243                }
244            }
245            _ => {}
246        }
247
248        self.keybinding_editor = Some(editor);
249        Ok(true)
250    }
251}