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::Entry(index) => {
43 self.focus_entry(index);
44 return Some(KeybindingListEvent::FocusChanged(Some(index)));
45 }
46 KeybindingListHit::AddRow => {
47 self.focus_add_row();
48 return Some(KeybindingListEvent::FocusChanged(None));
49 }
50 }
51 }
52 }
53 None
54 }
55
56 pub fn handle_key(&mut self, key: KeyEvent) -> Option<KeybindingListEvent> {
62 if !self.is_enabled() {
63 return None;
64 }
65
66 match key.code {
67 KeyCode::Enter => match self.focused_index {
68 None => Some(KeybindingListEvent::AddRequested),
70 Some(index) => Some(KeybindingListEvent::EditRequested(index)),
72 },
73 KeyCode::Delete | KeyCode::Backspace => {
74 if let Some(index) = self.focused_index {
75 self.remove_binding(index);
76 Some(KeybindingListEvent::BindingRemoved(index))
77 } else {
78 None
79 }
80 }
81 KeyCode::Up | KeyCode::Char('k') => {
82 self.focus_prev();
83 Some(KeybindingListEvent::FocusChanged(self.focused_index))
84 }
85 KeyCode::Down | KeyCode::Char('j') => {
86 self.focus_next();
87 Some(KeybindingListEvent::FocusChanged(self.focused_index))
88 }
89 _ => None,
90 }
91 }
92}
93
94#[cfg(test)]
95mod tests {
96 use super::*;
97 use crossterm::event::KeyModifiers;
98 use ratatui::layout::Rect;
99
100 fn make_layout() -> KeybindingListLayout {
101 KeybindingListLayout {
102 entry_rects: vec![(0, Rect::new(2, 1, 40, 1)), (1, Rect::new(2, 2, 40, 1))],
103 add_rect: Some(Rect::new(2, 3, 40, 1)),
104 }
105 }
106
107 fn mouse_down(x: u16, y: u16) -> MouseEvent {
108 MouseEvent {
109 kind: MouseEventKind::Down(MouseButton::Left),
110 column: x,
111 row: y,
112 modifiers: KeyModifiers::empty(),
113 }
114 }
115
116 #[test]
117 fn test_click_entry() {
118 let mut state = KeybindingListState::new("Test");
119 state.add_binding(serde_json::json!({"key": "a", "action": "test"}));
120 let layout = make_layout();
121
122 let result = state.handle_mouse(mouse_down(10, 1), &layout);
123 assert_eq!(result, Some(KeybindingListEvent::FocusChanged(Some(0))));
124 assert_eq!(state.focused_index, Some(0));
125 }
126
127 #[test]
128 fn test_click_add_row() {
129 let mut state = KeybindingListState::new("Test");
130 state.add_binding(serde_json::json!({"key": "a", "action": "test"}));
131 state.focus_entry(0);
132 let layout = make_layout();
133
134 let result = state.handle_mouse(mouse_down(10, 3), &layout);
135 assert_eq!(result, Some(KeybindingListEvent::FocusChanged(None)));
136 assert!(state.focused_index.is_none());
137 }
138
139 #[test]
140 fn test_keyboard_navigation() {
141 let mut state = KeybindingListState::new("Test");
142 state.add_binding(serde_json::json!({"key": "a", "action": "test1"}));
143 state.add_binding(serde_json::json!({"key": "b", "action": "test2"}));
144
145 let down = KeyEvent::new(KeyCode::Down, KeyModifiers::empty());
146 let result = state.handle_key(down);
147 assert_eq!(result, Some(KeybindingListEvent::FocusChanged(Some(0))));
148
149 let result = state.handle_key(down);
150 assert_eq!(result, Some(KeybindingListEvent::FocusChanged(Some(1))));
151
152 let up = KeyEvent::new(KeyCode::Up, KeyModifiers::empty());
153 let result = state.handle_key(up);
154 assert_eq!(result, Some(KeybindingListEvent::FocusChanged(Some(0))));
155 }
156
157 #[test]
158 fn test_enter_on_add_row() {
159 let mut state = KeybindingListState::new("Test");
160
161 let enter = KeyEvent::new(KeyCode::Enter, KeyModifiers::empty());
162 let result = state.handle_key(enter);
163 assert_eq!(result, Some(KeybindingListEvent::AddRequested));
164 }
165
166 #[test]
167 fn test_enter_on_entry() {
168 let mut state = KeybindingListState::new("Test");
169 state.add_binding(serde_json::json!({"key": "a", "action": "test"}));
170 state.focus_entry(0);
171
172 let enter = KeyEvent::new(KeyCode::Enter, KeyModifiers::empty());
173 let result = state.handle_key(enter);
174 assert_eq!(result, Some(KeybindingListEvent::EditRequested(0)));
175 }
176
177 #[test]
178 fn test_delete_removes_focused() {
179 let mut state = KeybindingListState::new("Test");
180 state.add_binding(serde_json::json!({"key": "a", "action": "test"}));
181 state.focus_entry(0);
182
183 let delete = KeyEvent::new(KeyCode::Delete, KeyModifiers::empty());
184 let result = state.handle_key(delete);
185 assert_eq!(result, Some(KeybindingListEvent::BindingRemoved(0)));
186 assert!(state.bindings.is_empty());
187 }
188}