1use crossterm::event::{KeyCode, KeyEvent};
4
5use super::types::KeyCombo;
6
7pub const DEFAULT_EXIT_TIMEOUT_SECS: u64 = 2;
9
10#[derive(Debug, Clone)]
20pub struct KeyBindings {
21 pub move_up: Vec<KeyCombo>,
24 pub move_down: Vec<KeyCombo>,
26 pub move_left: Vec<KeyCombo>,
28 pub move_right: Vec<KeyCombo>,
30 pub move_line_start: Vec<KeyCombo>,
32 pub move_line_end: Vec<KeyCombo>,
34
35 pub delete_char_before: Vec<KeyCombo>,
38 pub delete_char_at: Vec<KeyCombo>,
40 pub kill_line: Vec<KeyCombo>,
42 pub insert_newline: Vec<KeyCombo>,
44
45 pub submit: Vec<KeyCombo>,
48 pub interrupt: Vec<KeyCombo>,
50 pub quit: Vec<KeyCombo>,
52 pub force_quit: Vec<KeyCombo>,
54 pub enter_exit_mode: Vec<KeyCombo>,
56 pub exit_timeout_secs: u64,
58
59 pub select: Vec<KeyCombo>,
62 pub cancel: Vec<KeyCombo>,
64}
65
66impl Default for KeyBindings {
67 fn default() -> Self {
68 Self::bare_minimum()
69 }
70}
71
72impl KeyBindings {
73 pub fn bare_minimum() -> Self {
85 Self {
86 move_up: vec![KeyCombo::key(KeyCode::Up)],
87 move_down: vec![KeyCombo::key(KeyCode::Down)],
88 move_left: vec![KeyCombo::key(KeyCode::Left)],
89 move_right: vec![KeyCombo::key(KeyCode::Right)],
90 move_line_start: vec![KeyCombo::key(KeyCode::Home)],
91 move_line_end: vec![KeyCombo::key(KeyCode::End)],
92
93 delete_char_before: vec![KeyCombo::key(KeyCode::Backspace)],
94 delete_char_at: vec![KeyCombo::key(KeyCode::Delete)],
95 kill_line: vec![],
96 insert_newline: vec![],
97
98 submit: vec![KeyCombo::key(KeyCode::Enter)],
99 interrupt: vec![],
100 quit: vec![KeyCombo::key(KeyCode::Esc)], force_quit: vec![KeyCombo::ctrl('q')],
102 enter_exit_mode: vec![],
103 exit_timeout_secs: DEFAULT_EXIT_TIMEOUT_SECS,
104
105 select: vec![KeyCombo::key(KeyCode::Enter), KeyCombo::key(KeyCode::Char(' '))],
106 cancel: vec![KeyCombo::key(KeyCode::Esc)],
107 }
108 }
109
110 pub fn emacs() -> Self {
119 Self {
120 move_up: vec![KeyCombo::key(KeyCode::Up), KeyCombo::ctrl('p')],
121 move_down: vec![KeyCombo::key(KeyCode::Down), KeyCombo::ctrl('n')],
122 move_left: vec![KeyCombo::key(KeyCode::Left), KeyCombo::ctrl('b')],
123 move_right: vec![KeyCombo::key(KeyCode::Right), KeyCombo::ctrl('f')],
124 move_line_start: vec![KeyCombo::key(KeyCode::Home), KeyCombo::ctrl('a')],
125 move_line_end: vec![KeyCombo::key(KeyCode::End), KeyCombo::ctrl('e')],
126
127 delete_char_before: vec![KeyCombo::key(KeyCode::Backspace)],
128 delete_char_at: vec![KeyCombo::key(KeyCode::Delete)],
129 kill_line: vec![KeyCombo::ctrl('k')],
130 insert_newline: vec![KeyCombo::ctrl('j')],
131
132 submit: vec![KeyCombo::key(KeyCode::Enter)],
133 interrupt: vec![KeyCombo::key(KeyCode::Esc)],
134 quit: vec![], force_quit: vec![KeyCombo::ctrl('q')],
136 enter_exit_mode: vec![KeyCombo::ctrl('d')],
137 exit_timeout_secs: DEFAULT_EXIT_TIMEOUT_SECS,
138
139 select: vec![KeyCombo::key(KeyCode::Enter), KeyCombo::key(KeyCode::Char(' '))],
140 cancel: vec![KeyCombo::key(KeyCode::Esc)],
141 }
142 }
143
144 pub fn minimal() -> Self {
151 Self {
152 move_up: vec![KeyCombo::key(KeyCode::Up)],
153 move_down: vec![KeyCombo::key(KeyCode::Down)],
154 move_left: vec![KeyCombo::key(KeyCode::Left)],
155 move_right: vec![KeyCombo::key(KeyCode::Right)],
156 move_line_start: vec![KeyCombo::key(KeyCode::Home)],
157 move_line_end: vec![KeyCombo::key(KeyCode::End)],
158
159 delete_char_before: vec![KeyCombo::key(KeyCode::Backspace)],
160 delete_char_at: vec![KeyCombo::key(KeyCode::Delete)],
161 kill_line: vec![],
162 insert_newline: vec![KeyCombo::ctrl('j')],
163
164 submit: vec![KeyCombo::key(KeyCode::Enter)],
165 interrupt: vec![],
166 quit: vec![KeyCombo::key(KeyCode::Esc)], force_quit: vec![KeyCombo::ctrl('q')], enter_exit_mode: vec![],
169 exit_timeout_secs: DEFAULT_EXIT_TIMEOUT_SECS,
170
171 select: vec![KeyCombo::key(KeyCode::Enter), KeyCombo::key(KeyCode::Char(' '))],
172 cancel: vec![KeyCombo::key(KeyCode::Esc)],
173 }
174 }
175
176 pub(crate) fn matches_any(combos: &[KeyCombo], event: &KeyEvent) -> bool {
178 combos.iter().any(|combo| combo.matches(event))
179 }
180
181 pub fn with_move_up(mut self, combos: Vec<KeyCombo>) -> Self {
187 self.move_up = combos;
188 self
189 }
190
191 pub fn with_move_down(mut self, combos: Vec<KeyCombo>) -> Self {
193 self.move_down = combos;
194 self
195 }
196
197 pub fn with_move_left(mut self, combos: Vec<KeyCombo>) -> Self {
199 self.move_left = combos;
200 self
201 }
202
203 pub fn with_move_right(mut self, combos: Vec<KeyCombo>) -> Self {
205 self.move_right = combos;
206 self
207 }
208
209 pub fn with_move_line_start(mut self, combos: Vec<KeyCombo>) -> Self {
211 self.move_line_start = combos;
212 self
213 }
214
215 pub fn with_move_line_end(mut self, combos: Vec<KeyCombo>) -> Self {
217 self.move_line_end = combos;
218 self
219 }
220
221 pub fn with_delete_char_before(mut self, combos: Vec<KeyCombo>) -> Self {
223 self.delete_char_before = combos;
224 self
225 }
226
227 pub fn with_delete_char_at(mut self, combos: Vec<KeyCombo>) -> Self {
229 self.delete_char_at = combos;
230 self
231 }
232
233 pub fn with_kill_line(mut self, combos: Vec<KeyCombo>) -> Self {
235 self.kill_line = combos;
236 self
237 }
238
239 pub fn with_insert_newline(mut self, combos: Vec<KeyCombo>) -> Self {
241 self.insert_newline = combos;
242 self
243 }
244
245 pub fn with_submit(mut self, combos: Vec<KeyCombo>) -> Self {
247 self.submit = combos;
248 self
249 }
250
251 pub fn with_interrupt(mut self, combos: Vec<KeyCombo>) -> Self {
253 self.interrupt = combos;
254 self
255 }
256
257 pub fn with_quit(mut self, combos: Vec<KeyCombo>) -> Self {
259 self.quit = combos;
260 self
261 }
262
263 pub fn with_force_quit(mut self, combos: Vec<KeyCombo>) -> Self {
265 self.force_quit = combos;
266 self
267 }
268
269 pub fn with_enter_exit_mode(mut self, combos: Vec<KeyCombo>) -> Self {
271 self.enter_exit_mode = combos;
272 self
273 }
274
275 pub fn with_exit_timeout_secs(mut self, secs: u64) -> Self {
277 self.exit_timeout_secs = secs;
278 self
279 }
280
281 pub fn with_select(mut self, combos: Vec<KeyCombo>) -> Self {
283 self.select = combos;
284 self
285 }
286
287 pub fn with_cancel(mut self, combos: Vec<KeyCombo>) -> Self {
289 self.cancel = combos;
290 self
291 }
292
293 pub fn without_exit_mode(mut self) -> Self {
299 self.enter_exit_mode = vec![];
300 self
301 }
302
303 pub fn without_quit(mut self) -> Self {
305 self.quit = vec![];
306 self
307 }
308
309 pub fn without_force_quit(mut self) -> Self {
311 self.force_quit = vec![];
312 self
313 }
314
315 pub fn without_interrupt(mut self) -> Self {
317 self.interrupt = vec![];
318 self
319 }
320
321 pub fn without_kill_line(mut self) -> Self {
323 self.kill_line = vec![];
324 self
325 }
326
327 pub fn without_insert_newline(mut self) -> Self {
329 self.insert_newline = vec![];
330 self
331 }
332
333 pub fn add_move_up(mut self, combo: KeyCombo) -> Self {
339 self.move_up.push(combo);
340 self
341 }
342
343 pub fn add_move_down(mut self, combo: KeyCombo) -> Self {
345 self.move_down.push(combo);
346 self
347 }
348
349 pub fn add_move_left(mut self, combo: KeyCombo) -> Self {
351 self.move_left.push(combo);
352 self
353 }
354
355 pub fn add_move_right(mut self, combo: KeyCombo) -> Self {
357 self.move_right.push(combo);
358 self
359 }
360
361 pub fn add_quit(mut self, combo: KeyCombo) -> Self {
363 self.quit.push(combo);
364 self
365 }
366
367 pub fn add_submit(mut self, combo: KeyCombo) -> Self {
369 self.submit.push(combo);
370 self
371 }
372
373 pub fn add_interrupt(mut self, combo: KeyCombo) -> Self {
375 self.interrupt.push(combo);
376 self
377 }
378
379 pub fn add_enter_exit_mode(mut self, combo: KeyCombo) -> Self {
381 self.enter_exit_mode.push(combo);
382 self
383 }
384
385 pub fn add_force_quit(mut self, combo: KeyCombo) -> Self {
387 self.force_quit.push(combo);
388 self
389 }
390
391 pub fn add_select(mut self, combo: KeyCombo) -> Self {
393 self.select.push(combo);
394 self
395 }
396
397 pub fn add_cancel(mut self, combo: KeyCombo) -> Self {
399 self.cancel.push(combo);
400 self
401 }
402}
403
404#[cfg(test)]
405mod tests {
406 use super::*;
407
408 #[test]
409 fn test_emacs_bindings() {
410 let bindings = KeyBindings::emacs();
411
412 let ctrl_p = KeyCombo::ctrl('p');
414 assert!(bindings.move_up.contains(&ctrl_p));
415
416 let up = KeyCombo::key(KeyCode::Up);
418 assert!(bindings.move_up.contains(&up));
419
420 assert!(bindings.quit.is_empty());
422 }
423
424 #[test]
425 fn test_minimal_bindings() {
426 let bindings = KeyBindings::minimal();
427
428 let esc = KeyCombo::key(KeyCode::Esc);
430 assert!(bindings.quit.contains(&esc));
431
432 let ctrl_p = KeyCombo::ctrl('p');
434 assert!(!bindings.move_up.contains(&ctrl_p));
435 }
436
437 #[test]
438 fn test_bare_minimum_bindings() {
439 let bindings = KeyBindings::bare_minimum();
440
441 let esc = KeyCombo::key(KeyCode::Esc);
443 assert!(bindings.quit.contains(&esc));
444 assert!(bindings.interrupt.is_empty());
445
446 let ctrl_p = KeyCombo::ctrl('p');
448 assert!(!bindings.move_up.contains(&ctrl_p));
449
450 assert!(bindings.enter_exit_mode.is_empty());
452 }
453
454 #[test]
455 fn test_builder_with_methods() {
456 let bindings = KeyBindings::minimal()
458 .with_quit(vec![KeyCombo::ctrl('w')])
459 .with_submit(vec![KeyCombo::key(KeyCode::Enter), KeyCombo::ctrl('m')]);
460
461 assert_eq!(bindings.quit.len(), 1);
463 assert!(bindings.quit.contains(&KeyCombo::ctrl('w')));
464
465 assert_eq!(bindings.submit.len(), 2);
467 assert!(bindings.submit.contains(&KeyCombo::key(KeyCode::Enter)));
468 assert!(bindings.submit.contains(&KeyCombo::ctrl('m')));
469 }
470
471 #[test]
472 fn test_builder_without_methods() {
473 let bindings = KeyBindings::emacs()
475 .without_exit_mode()
476 .without_kill_line();
477
478 assert!(bindings.enter_exit_mode.is_empty());
480
481 assert!(bindings.kill_line.is_empty());
483
484 assert!(!bindings.move_up.is_empty());
486 assert!(!bindings.submit.is_empty());
487 }
488
489 #[test]
490 fn test_builder_add_methods() {
491 let bindings = KeyBindings::minimal()
493 .add_quit(KeyCombo::ctrl('c'))
494 .add_submit(KeyCombo::ctrl('s'));
495
496 assert!(bindings.quit.contains(&KeyCombo::key(KeyCode::Esc)));
498 assert!(bindings.quit.contains(&KeyCombo::ctrl('c')));
499
500 assert!(bindings.submit.contains(&KeyCombo::key(KeyCode::Enter)));
502 assert!(bindings.submit.contains(&KeyCombo::ctrl('s')));
503 }
504
505 #[test]
506 fn test_builder_chaining() {
507 let bindings = KeyBindings::bare_minimum()
509 .with_move_up(vec![KeyCombo::key(KeyCode::Up), KeyCombo::ctrl('p')])
510 .with_move_down(vec![KeyCombo::key(KeyCode::Down), KeyCombo::ctrl('n')])
511 .without_quit()
512 .with_enter_exit_mode(vec![KeyCombo::ctrl('d')])
513 .with_exit_timeout_secs(5)
514 .add_force_quit(KeyCombo::ctrl('c'));
515
516 assert!(bindings.move_up.contains(&KeyCombo::ctrl('p')));
518 assert!(bindings.move_down.contains(&KeyCombo::ctrl('n')));
519 assert!(bindings.quit.is_empty());
520 assert!(bindings.enter_exit_mode.contains(&KeyCombo::ctrl('d')));
521 assert_eq!(bindings.exit_timeout_secs, 5);
522 assert!(bindings.force_quit.contains(&KeyCombo::ctrl('c')));
523 }
524}