1use super::{Event, KeyCode, KeyModifiers};
2use crate::{key_bindings::KeyBindings, InputOptions, KeyEvent, StandardEvent};
3
4#[derive(Debug)]
6pub struct EventHandler<CustomKeybinding: crate::CustomKeybinding, CustomEvent: crate::CustomEvent> {
7 key_bindings: KeyBindings<CustomKeybinding, CustomEvent>,
8}
9
10impl<CustomKeybinding: crate::CustomKeybinding, CustomEvent: crate::CustomEvent>
11 EventHandler<CustomKeybinding, CustomEvent>
12{
13 #[inline]
15 #[must_use]
16 pub const fn new(key_bindings: KeyBindings<CustomKeybinding, CustomEvent>) -> Self {
17 Self { key_bindings }
18 }
19
20 #[inline]
22 pub fn read_event<F>(
23 &self,
24 event: Event<CustomEvent>,
25 input_options: &InputOptions,
26 callback: F,
27 ) -> Event<CustomEvent>
28 where
29 F: FnOnce(Event<CustomEvent>, &KeyBindings<CustomKeybinding, CustomEvent>) -> Event<CustomEvent>,
30 {
31 if event == Event::None {
32 return event;
33 }
34
35 if let Some(e) = Self::handle_standard_inputs(event) {
36 return e;
37 }
38
39 if input_options.contains(InputOptions::RESIZE) {
40 if let Event::Resize(..) = event {
41 return event;
42 }
43 }
44
45 if input_options.contains(InputOptions::MOVEMENT) {
46 if let Some(evt) = Self::handle_movement_inputs(&self.key_bindings, event) {
47 return evt;
48 }
49 }
50
51 if input_options.contains(InputOptions::SEARCH) {
52 if let Some(evt) = Self::handle_search(&self.key_bindings, event) {
53 return evt;
54 }
55 }
56
57 if input_options.contains(InputOptions::HELP) && self.key_bindings.help.contains(&event) {
58 return Event::from(StandardEvent::Help);
59 }
60
61 if input_options.contains(InputOptions::UNDO_REDO) {
62 if let Some(evt) = Self::handle_undo_redo(&self.key_bindings, event) {
63 return evt;
64 }
65 }
66
67 callback(event, &self.key_bindings)
68 }
69
70 #[allow(clippy::wildcard_enum_match_arm)]
71 fn handle_standard_inputs(event: Event<CustomEvent>) -> Option<Event<CustomEvent>> {
72 match event {
73 Event::Key(KeyEvent {
74 code: KeyCode::Char('c'),
75 modifiers: KeyModifiers::CONTROL,
76 }) => Some(Event::from(StandardEvent::Kill)),
77 _ => None,
78 }
79 }
80
81 #[allow(clippy::wildcard_enum_match_arm)]
82 fn handle_movement_inputs(
83 key_bindings: &KeyBindings<CustomKeybinding, CustomEvent>,
84 event: Event<CustomEvent>,
85 ) -> Option<Event<CustomEvent>> {
86 Some(match event {
87 e if key_bindings.scroll_down.contains(&e) => Event::from(StandardEvent::ScrollDown),
88 e if key_bindings.scroll_end.contains(&e) => Event::from(StandardEvent::ScrollBottom),
89 e if key_bindings.scroll_home.contains(&e) => Event::from(StandardEvent::ScrollTop),
90 e if key_bindings.scroll_left.contains(&e) => Event::from(StandardEvent::ScrollLeft),
91 e if key_bindings.scroll_right.contains(&e) => Event::from(StandardEvent::ScrollRight),
92 e if key_bindings.scroll_up.contains(&e) => Event::from(StandardEvent::ScrollUp),
93 e if key_bindings.scroll_step_down.contains(&e) => Event::from(StandardEvent::ScrollJumpDown),
94 e if key_bindings.scroll_step_up.contains(&e) => Event::from(StandardEvent::ScrollJumpUp),
95 Event::Key(KeyEvent {
97 code: KeyCode::Up,
98 modifiers: KeyModifiers::NONE,
99 }) => Event::from(StandardEvent::ScrollUp),
100 Event::Key(KeyEvent {
101 code: KeyCode::Down,
102 modifiers: KeyModifiers::NONE,
103 }) => Event::from(StandardEvent::ScrollDown),
104 Event::Key(KeyEvent {
105 code: KeyCode::Left,
106 modifiers: KeyModifiers::NONE,
107 }) => Event::from(StandardEvent::ScrollLeft),
108 Event::Key(KeyEvent {
109 code: KeyCode::Right,
110 modifiers: KeyModifiers::NONE,
111 }) => Event::from(StandardEvent::ScrollRight),
112 Event::Key(KeyEvent {
113 code: KeyCode::PageUp,
114 modifiers: KeyModifiers::NONE,
115 }) => Event::from(StandardEvent::ScrollJumpUp),
116 Event::Key(KeyEvent {
117 code: KeyCode::PageDown,
118 modifiers: KeyModifiers::NONE,
119 }) => Event::from(StandardEvent::ScrollJumpDown),
120 Event::Key(KeyEvent {
121 code: KeyCode::Home,
122 modifiers: KeyModifiers::NONE,
123 }) => Event::from(StandardEvent::ScrollTop),
124 Event::Key(KeyEvent {
125 code: KeyCode::End,
126 modifiers: KeyModifiers::NONE,
127 }) => Event::from(StandardEvent::ScrollBottom),
128 _ => return None,
129 })
130 }
131
132 fn handle_search(
133 key_bindings: &KeyBindings<CustomKeybinding, CustomEvent>,
134 event: Event<CustomEvent>,
135 ) -> Option<Event<CustomEvent>> {
136 match event {
137 e if key_bindings.search_next.contains(&e) => Some(Event::from(StandardEvent::SearchNext)),
138 e if key_bindings.search_previous.contains(&e) => Some(Event::from(StandardEvent::SearchPrevious)),
139 e if key_bindings.search_start.contains(&e) => Some(Event::from(StandardEvent::SearchStart)),
140 _ => None,
141 }
142 }
143
144 fn handle_undo_redo(
145 key_bindings: &KeyBindings<CustomKeybinding, CustomEvent>,
146 event: Event<CustomEvent>,
147 ) -> Option<Event<CustomEvent>> {
148 if key_bindings.undo.contains(&event) {
149 Some(Event::from(StandardEvent::Undo))
150 }
151 else if key_bindings.redo.contains(&event) {
152 Some(Event::from(StandardEvent::Redo))
153 }
154 else {
155 None
156 }
157 }
158}
159
160#[cfg(test)]
161mod tests {
162 use rstest::rstest;
163
164 use super::*;
165 use crate::{
166 map_keybindings,
167 testutil::local::{create_test_keybindings, Event, EventHandler},
168 };
169
170 #[rstest]
171 #[case::standard(Event::Key(KeyEvent {
172 code: KeyCode::Char('c'),
173 modifiers: KeyModifiers::CONTROL,
174 }), true)]
175 #[case::resize(Event::Resize(100, 100), false)]
176 #[case::movement(Event::from(KeyCode::Up), false)]
177 #[case::undo_redo(Event::Key(KeyEvent {
178 code: KeyCode::Char('z'),
179 modifiers: KeyModifiers::CONTROL,
180 }), false)]
181 #[case::other(Event::from('a'), false)]
182 fn read_event_options_disabled(#[case] event: Event, #[case] handled: bool) {
183 let event_handler = EventHandler::new(create_test_keybindings());
184 let result = event_handler.read_event(event, &InputOptions::empty(), |_, _| Event::from(KeyCode::Null));
185
186 if handled {
187 assert_ne!(result, Event::from(KeyCode::Null));
188 }
189 else {
190 assert_eq!(result, Event::from(KeyCode::Null));
191 }
192 }
193
194 #[rstest]
195 #[case::standard(Event::Key(KeyEvent {
196 code: KeyCode::Char('c'),
197 modifiers: KeyModifiers::CONTROL,
198 }), true)]
199 #[case::resize(Event::Resize(100, 100), true)]
200 #[case::movement(Event::from(KeyCode::Up), true)]
201 #[case::undo_redo(Event::Key(KeyEvent {
202 code: KeyCode::Char('z'),
203 modifiers: KeyModifiers::CONTROL,
204 }), true)]
205 #[case::other(Event::from('a'), false)]
206 fn read_event_enabled(#[case] event: Event, #[case] handled: bool) {
207 let event_handler = EventHandler::new(create_test_keybindings());
208 let result = event_handler.read_event(event, &InputOptions::all(), |_, _| Event::from(KeyCode::Null));
209
210 if handled {
211 assert_ne!(result, Event::from(KeyCode::Null));
212 }
213 else {
214 assert_eq!(result, Event::from(KeyCode::Null));
215 }
216 }
217
218 #[test]
219 fn none_event() {
220 let event_handler = EventHandler::new(create_test_keybindings());
221 let result = event_handler.read_event(Event::None, &InputOptions::empty(), |_, _| Event::from(KeyCode::Null));
222 assert_eq!(result, Event::None);
223 }
224
225 #[rstest]
226 #[case::standard(Event::Key(KeyEvent {
227 code: KeyCode::Char('c'),
228 modifiers: KeyModifiers::CONTROL,
229 }), Event::from(StandardEvent::Kill))]
230 #[case::other(Event::from('a'), Event::from(KeyCode::Null))]
231 fn standard_inputs(#[case] event: Event, #[case] expected: Event) {
232 let event_handler = EventHandler::new(create_test_keybindings());
233 let result = event_handler.read_event(event, &InputOptions::empty(), |_, _| Event::from(KeyCode::Null));
234 assert_eq!(result, expected);
235 }
236
237 #[rstest]
238 #[case::standard(Event::from(KeyCode::Up), Event::from(StandardEvent::ScrollUp))]
239 #[case::standard(Event::from(KeyCode::Down), Event::from(StandardEvent::ScrollDown))]
240 #[case::standard(Event::from(KeyCode::Left), Event::from(StandardEvent::ScrollLeft))]
241 #[case::standard(Event::from(KeyCode::Right), Event::from(StandardEvent::ScrollRight))]
242 #[case::standard(Event::from(KeyCode::PageUp), Event::from(StandardEvent::ScrollJumpUp))]
243 #[case::standard(Event::from(KeyCode::PageDown), Event::from(StandardEvent::ScrollJumpDown))]
244 #[case::standard(Event::from(KeyCode::Home), Event::from(StandardEvent::ScrollTop))]
245 #[case::standard(Event::from(KeyCode::End), Event::from(StandardEvent::ScrollBottom))]
246 #[case::other(Event::from('a'), Event::from(KeyCode::Null))]
247 fn movement_inputs(#[case] event: Event, #[case] expected: Event) {
248 let event_handler = EventHandler::new(create_test_keybindings());
249 let result = event_handler.read_event(event, &InputOptions::MOVEMENT, |_, _| Event::from(KeyCode::Null));
250 assert_eq!(result, expected);
251 }
252
253 #[rstest]
254 #[case::standard(Event::from(KeyCode::Up), Event::from(StandardEvent::ScrollUp))]
255 #[case::standard(Event::from(KeyCode::Down), Event::from(StandardEvent::ScrollDown))]
256 #[case::standard(Event::from(KeyCode::Left), Event::from(StandardEvent::ScrollLeft))]
257 #[case::standard(Event::from(KeyCode::Right), Event::from(StandardEvent::ScrollRight))]
258 #[case::standard(Event::from(KeyCode::PageUp), Event::from(StandardEvent::ScrollJumpUp))]
259 #[case::standard(Event::from(KeyCode::PageDown), Event::from(StandardEvent::ScrollJumpDown))]
260 #[case::standard(Event::from(KeyCode::Home), Event::from(StandardEvent::ScrollTop))]
261 #[case::standard(Event::from(KeyCode::End), Event::from(StandardEvent::ScrollBottom))]
262 #[case::other(Event::from('a'), Event::from(KeyCode::Null))]
263 fn default_movement_inputs(#[case] event: Event, #[case] expected: Event) {
264 let mut bindings = create_test_keybindings();
265 bindings.scroll_down = map_keybindings(&[String::from("x")]);
266 bindings.scroll_end = map_keybindings(&[String::from("x")]);
267 bindings.scroll_home = map_keybindings(&[String::from("x")]);
268 bindings.scroll_left = map_keybindings(&[String::from("x")]);
269 bindings.scroll_right = map_keybindings(&[String::from("x")]);
270 bindings.scroll_up = map_keybindings(&[String::from("x")]);
271 bindings.scroll_step_down = map_keybindings(&[String::from("x")]);
272 bindings.scroll_step_up = map_keybindings(&[String::from("x")]);
273 let event_handler = EventHandler::new(bindings);
274 let result = event_handler.read_event(event, &InputOptions::MOVEMENT, |_, _| Event::from(KeyCode::Null));
275 assert_eq!(result, expected);
276 }
277
278 #[rstest]
279 #[case::search_next(Event::from('n'), Event::from(StandardEvent::SearchNext))]
280 #[case::search_previous(Event::from('N'), Event::from(StandardEvent::SearchPrevious))]
281 #[case::search_start(Event::from('/'), Event::from(StandardEvent::SearchStart))]
282 #[case::other(Event::from('a'), Event::from(KeyCode::Null))]
283 fn search_inputs(#[case] event: Event, #[case] expected: Event) {
284 let event_handler = EventHandler::new(create_test_keybindings());
285 let result = event_handler.read_event(event, &InputOptions::SEARCH, |_, _| Event::from(KeyCode::Null));
286 assert_eq!(result, expected);
287 }
288
289 #[test]
290 fn help_event() {
291 let event_handler = EventHandler::new(create_test_keybindings());
292 let result = event_handler.read_event(Event::from('?'), &InputOptions::HELP, |_, _| Event::from(KeyCode::Null));
293 assert_eq!(result, Event::from(StandardEvent::Help));
294 }
295
296 #[rstest]
297 #[case::standard(Event::Key(KeyEvent {
298 code: KeyCode::Char('z'),
299 modifiers: KeyModifiers::CONTROL,
300 }), Event::from(StandardEvent::Undo))]
301 #[case::standard(Event::Key(KeyEvent {
302 code: KeyCode::Char('y'),
303 modifiers: KeyModifiers::CONTROL,
304 }), Event::from(StandardEvent::Redo))]
305 #[case::other(Event::from('a'), Event::from(KeyCode::Null))]
306 fn undo_redo_inputs(#[case] event: Event, #[case] expected: Event) {
307 let event_handler = EventHandler::new(create_test_keybindings());
308 let result = event_handler.read_event(event, &InputOptions::UNDO_REDO, |_, _| Event::from(KeyCode::Null));
309 assert_eq!(result, expected);
310 }
311}