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