fresh/view/controls/keybinding_list/
input.rs1use crossterm::event::{KeyCode, KeyEvent, MouseButton, MouseEvent, MouseEventKind};
4
5use super::{KeybindingListHit, KeybindingListLayout, KeybindingListState};
6
7#[derive(Debug, Clone, PartialEq, Eq)]
9pub enum KeybindingListEvent {
10 BindingRemoved(usize),
12 FocusChanged(Option<usize>),
14 AddRequested,
16 EditRequested(usize),
18}
19
20impl KeybindingListState {
21 pub fn handle_mouse(
31 &mut self,
32 event: MouseEvent,
33 layout: &KeybindingListLayout,
34 ) -> Option<KeybindingListEvent> {
35 if !self.is_enabled() {
36 return None;
37 }
38
39 if let MouseEventKind::Down(MouseButton::Left) = event.kind {
40 if let Some(hit) = layout.hit_test(event.column, event.row) {
41 match hit {
42 KeybindingListHit::DeleteButton(index) => {
43 self.remove_binding(index);
44 return Some(KeybindingListEvent::BindingRemoved(index));
45 }
46 KeybindingListHit::Entry(index) => {
47 self.focus_entry(index);
48 return Some(KeybindingListEvent::FocusChanged(Some(index)));
49 }
50 KeybindingListHit::AddRow => {
51 self.focus_add_row();
52 return Some(KeybindingListEvent::FocusChanged(None));
53 }
54 }
55 }
56 }
57 None
58 }
59
60 pub fn handle_key(&mut self, key: KeyEvent) -> Option<KeybindingListEvent> {
66 if !self.is_enabled() {
67 return None;
68 }
69
70 match key.code {
71 KeyCode::Enter => match self.focused_index {
72 None => Some(KeybindingListEvent::AddRequested),
74 Some(index) => Some(KeybindingListEvent::EditRequested(index)),
76 },
77 KeyCode::Delete | KeyCode::Backspace => {
78 if let Some(index) = self.focused_index {
79 self.remove_binding(index);
80 Some(KeybindingListEvent::BindingRemoved(index))
81 } else {
82 None
83 }
84 }
85 KeyCode::Up | KeyCode::Char('k') => {
86 self.focus_prev();
87 Some(KeybindingListEvent::FocusChanged(self.focused_index))
88 }
89 KeyCode::Down | KeyCode::Char('j') => {
90 self.focus_next();
91 Some(KeybindingListEvent::FocusChanged(self.focused_index))
92 }
93 _ => None,
94 }
95 }
96}
97
98#[cfg(test)]
99mod tests {
100 use super::*;
101 use crossterm::event::KeyModifiers;
102 use ratatui::layout::Rect;
103
104 fn make_layout() -> KeybindingListLayout {
105 KeybindingListLayout {
106 entry_rects: vec![(0, Rect::new(2, 1, 40, 1)), (1, Rect::new(2, 2, 40, 1))],
107 delete_rects: vec![Rect::new(38, 1, 3, 1), Rect::new(38, 2, 3, 1)],
108 add_rect: Some(Rect::new(2, 3, 40, 1)),
109 }
110 }
111
112 fn mouse_down(x: u16, y: u16) -> MouseEvent {
113 MouseEvent {
114 kind: MouseEventKind::Down(MouseButton::Left),
115 column: x,
116 row: y,
117 modifiers: KeyModifiers::empty(),
118 }
119 }
120
121 #[test]
122 fn test_click_delete_button() {
123 let mut state = KeybindingListState::new("Test");
124 state.add_binding(serde_json::json!({"key": "a", "action": "test"}));
125 let layout = make_layout();
126
127 let result = state.handle_mouse(mouse_down(38, 1), &layout);
128 assert_eq!(result, Some(KeybindingListEvent::BindingRemoved(0)));
129 assert!(state.bindings.is_empty());
130 }
131
132 #[test]
133 fn test_click_entry() {
134 let mut state = KeybindingListState::new("Test");
135 state.add_binding(serde_json::json!({"key": "a", "action": "test"}));
136 let layout = make_layout();
137
138 let result = state.handle_mouse(mouse_down(10, 1), &layout);
139 assert_eq!(result, Some(KeybindingListEvent::FocusChanged(Some(0))));
140 assert_eq!(state.focused_index, Some(0));
141 }
142
143 #[test]
144 fn test_click_add_row() {
145 let mut state = KeybindingListState::new("Test");
146 state.add_binding(serde_json::json!({"key": "a", "action": "test"}));
147 state.focus_entry(0);
148 let layout = make_layout();
149
150 let result = state.handle_mouse(mouse_down(10, 3), &layout);
151 assert_eq!(result, Some(KeybindingListEvent::FocusChanged(None)));
152 assert!(state.focused_index.is_none());
153 }
154
155 #[test]
156 fn test_keyboard_navigation() {
157 let mut state = KeybindingListState::new("Test");
158 state.add_binding(serde_json::json!({"key": "a", "action": "test1"}));
159 state.add_binding(serde_json::json!({"key": "b", "action": "test2"}));
160
161 let down = KeyEvent::new(KeyCode::Down, KeyModifiers::empty());
162 let result = state.handle_key(down);
163 assert_eq!(result, Some(KeybindingListEvent::FocusChanged(Some(0))));
164
165 let result = state.handle_key(down);
166 assert_eq!(result, Some(KeybindingListEvent::FocusChanged(Some(1))));
167
168 let up = KeyEvent::new(KeyCode::Up, KeyModifiers::empty());
169 let result = state.handle_key(up);
170 assert_eq!(result, Some(KeybindingListEvent::FocusChanged(Some(0))));
171 }
172
173 #[test]
174 fn test_enter_on_add_row() {
175 let mut state = KeybindingListState::new("Test");
176
177 let enter = KeyEvent::new(KeyCode::Enter, KeyModifiers::empty());
178 let result = state.handle_key(enter);
179 assert_eq!(result, Some(KeybindingListEvent::AddRequested));
180 }
181
182 #[test]
183 fn test_enter_on_entry() {
184 let mut state = KeybindingListState::new("Test");
185 state.add_binding(serde_json::json!({"key": "a", "action": "test"}));
186 state.focus_entry(0);
187
188 let enter = KeyEvent::new(KeyCode::Enter, KeyModifiers::empty());
189 let result = state.handle_key(enter);
190 assert_eq!(result, Some(KeybindingListEvent::EditRequested(0)));
191 }
192
193 #[test]
194 fn test_delete_removes_focused() {
195 let mut state = KeybindingListState::new("Test");
196 state.add_binding(serde_json::json!({"key": "a", "action": "test"}));
197 state.focus_entry(0);
198
199 let delete = KeyEvent::new(KeyCode::Delete, KeyModifiers::empty());
200 let result = state.handle_key(delete);
201 assert_eq!(result, Some(KeybindingListEvent::BindingRemoved(0)));
202 assert!(state.bindings.is_empty());
203 }
204}