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