1use crossterm::event::{KeyCode, KeyEvent};
4
5use super::types::KeyCombo;
6
7pub const DEFAULT_EXIT_TIMEOUT_SECS: u64 = 2;
9
10#[derive(Debug, Clone)]
18pub struct KeyBindings {
19 pub move_up: Vec<KeyCombo>,
22 pub move_down: Vec<KeyCombo>,
24 pub move_left: Vec<KeyCombo>,
26 pub move_right: Vec<KeyCombo>,
28 pub move_line_start: Vec<KeyCombo>,
30 pub move_line_end: Vec<KeyCombo>,
32
33 pub delete_char_before: Vec<KeyCombo>,
36 pub delete_char_at: Vec<KeyCombo>,
38 pub kill_line: Vec<KeyCombo>,
40 pub insert_newline: Vec<KeyCombo>,
42
43 pub submit: Vec<KeyCombo>,
46 pub interrupt: Vec<KeyCombo>,
48 pub quit: Vec<KeyCombo>,
50 pub force_quit: Vec<KeyCombo>,
52 pub enter_exit_mode: Vec<KeyCombo>,
54 pub exit_timeout_secs: u64,
56
57 pub select: Vec<KeyCombo>,
60 pub cancel: Vec<KeyCombo>,
62}
63
64impl Default for KeyBindings {
65 fn default() -> Self {
66 Self::minimal()
67 }
68}
69
70impl KeyBindings {
71 pub fn emacs() -> Self {
80 Self {
81 move_up: vec![KeyCombo::key(KeyCode::Up), KeyCombo::ctrl('p')],
82 move_down: vec![KeyCombo::key(KeyCode::Down), KeyCombo::ctrl('n')],
83 move_left: vec![KeyCombo::key(KeyCode::Left), KeyCombo::ctrl('b')],
84 move_right: vec![KeyCombo::key(KeyCode::Right), KeyCombo::ctrl('f')],
85 move_line_start: vec![KeyCombo::key(KeyCode::Home), KeyCombo::ctrl('a')],
86 move_line_end: vec![KeyCombo::key(KeyCode::End), KeyCombo::ctrl('e')],
87
88 delete_char_before: vec![KeyCombo::key(KeyCode::Backspace)],
89 delete_char_at: vec![KeyCombo::key(KeyCode::Delete)],
90 kill_line: vec![KeyCombo::ctrl('k')],
91 insert_newline: vec![KeyCombo::ctrl('j')],
92
93 submit: vec![KeyCombo::key(KeyCode::Enter)],
94 interrupt: vec![KeyCombo::key(KeyCode::Esc)],
95 quit: vec![], force_quit: vec![KeyCombo::ctrl('q')],
97 enter_exit_mode: vec![KeyCombo::ctrl('d')],
98 exit_timeout_secs: DEFAULT_EXIT_TIMEOUT_SECS,
99
100 select: vec![
101 KeyCombo::key(KeyCode::Enter),
102 KeyCombo::key(KeyCode::Char(' ')),
103 ],
104 cancel: vec![KeyCombo::key(KeyCode::Esc)],
105 }
106 }
107
108 pub fn minimal() -> Self {
115 Self {
116 move_up: vec![KeyCombo::key(KeyCode::Up)],
117 move_down: vec![KeyCombo::key(KeyCode::Down)],
118 move_left: vec![KeyCombo::key(KeyCode::Left)],
119 move_right: vec![KeyCombo::key(KeyCode::Right)],
120 move_line_start: vec![KeyCombo::key(KeyCode::Home)],
121 move_line_end: vec![KeyCombo::key(KeyCode::End)],
122
123 delete_char_before: vec![KeyCombo::key(KeyCode::Backspace)],
124 delete_char_at: vec![KeyCombo::key(KeyCode::Delete)],
125 kill_line: vec![],
126 insert_newline: vec![KeyCombo::ctrl('j')],
127
128 submit: vec![KeyCombo::key(KeyCode::Enter)],
129 interrupt: vec![],
130 quit: vec![KeyCombo::key(KeyCode::Esc)], force_quit: vec![KeyCombo::ctrl('q')], enter_exit_mode: vec![],
133 exit_timeout_secs: DEFAULT_EXIT_TIMEOUT_SECS,
134
135 select: vec![
136 KeyCombo::key(KeyCode::Enter),
137 KeyCombo::key(KeyCode::Char(' ')),
138 ],
139 cancel: vec![KeyCombo::key(KeyCode::Esc)],
140 }
141 }
142
143 pub(crate) fn matches_any(combos: &[KeyCombo], event: &KeyEvent) -> bool {
145 combos.iter().any(|combo| combo.matches(event))
146 }
147
148 pub fn with_move_up(mut self, combos: Vec<KeyCombo>) -> Self {
154 self.move_up = combos;
155 self
156 }
157
158 pub fn with_move_down(mut self, combos: Vec<KeyCombo>) -> Self {
160 self.move_down = combos;
161 self
162 }
163
164 pub fn with_move_left(mut self, combos: Vec<KeyCombo>) -> Self {
166 self.move_left = combos;
167 self
168 }
169
170 pub fn with_move_right(mut self, combos: Vec<KeyCombo>) -> Self {
172 self.move_right = combos;
173 self
174 }
175
176 pub fn with_move_line_start(mut self, combos: Vec<KeyCombo>) -> Self {
178 self.move_line_start = combos;
179 self
180 }
181
182 pub fn with_move_line_end(mut self, combos: Vec<KeyCombo>) -> Self {
184 self.move_line_end = combos;
185 self
186 }
187
188 pub fn with_delete_char_before(mut self, combos: Vec<KeyCombo>) -> Self {
190 self.delete_char_before = combos;
191 self
192 }
193
194 pub fn with_delete_char_at(mut self, combos: Vec<KeyCombo>) -> Self {
196 self.delete_char_at = combos;
197 self
198 }
199
200 pub fn with_kill_line(mut self, combos: Vec<KeyCombo>) -> Self {
202 self.kill_line = combos;
203 self
204 }
205
206 pub fn with_insert_newline(mut self, combos: Vec<KeyCombo>) -> Self {
208 self.insert_newline = combos;
209 self
210 }
211
212 pub fn with_submit(mut self, combos: Vec<KeyCombo>) -> Self {
214 self.submit = combos;
215 self
216 }
217
218 pub fn with_interrupt(mut self, combos: Vec<KeyCombo>) -> Self {
220 self.interrupt = combos;
221 self
222 }
223
224 pub fn with_quit(mut self, combos: Vec<KeyCombo>) -> Self {
226 self.quit = combos;
227 self
228 }
229
230 pub fn with_force_quit(mut self, combos: Vec<KeyCombo>) -> Self {
232 self.force_quit = combos;
233 self
234 }
235
236 pub fn with_enter_exit_mode(mut self, combos: Vec<KeyCombo>) -> Self {
238 self.enter_exit_mode = combos;
239 self
240 }
241
242 pub fn with_exit_timeout_secs(mut self, secs: u64) -> Self {
244 self.exit_timeout_secs = secs;
245 self
246 }
247
248 pub fn with_select(mut self, combos: Vec<KeyCombo>) -> Self {
250 self.select = combos;
251 self
252 }
253
254 pub fn with_cancel(mut self, combos: Vec<KeyCombo>) -> Self {
256 self.cancel = combos;
257 self
258 }
259
260 pub fn without_exit_mode(mut self) -> Self {
266 self.enter_exit_mode = vec![];
267 self
268 }
269
270 pub fn without_quit(mut self) -> Self {
272 self.quit = vec![];
273 self
274 }
275
276 pub fn without_force_quit(mut self) -> Self {
278 self.force_quit = vec![];
279 self
280 }
281
282 pub fn without_interrupt(mut self) -> Self {
284 self.interrupt = vec![];
285 self
286 }
287
288 pub fn without_kill_line(mut self) -> Self {
290 self.kill_line = vec![];
291 self
292 }
293
294 pub fn without_insert_newline(mut self) -> Self {
296 self.insert_newline = vec![];
297 self
298 }
299
300 pub fn add_move_up(mut self, combo: KeyCombo) -> Self {
306 self.move_up.push(combo);
307 self
308 }
309
310 pub fn add_move_down(mut self, combo: KeyCombo) -> Self {
312 self.move_down.push(combo);
313 self
314 }
315
316 pub fn add_move_left(mut self, combo: KeyCombo) -> Self {
318 self.move_left.push(combo);
319 self
320 }
321
322 pub fn add_move_right(mut self, combo: KeyCombo) -> Self {
324 self.move_right.push(combo);
325 self
326 }
327
328 pub fn add_quit(mut self, combo: KeyCombo) -> Self {
330 self.quit.push(combo);
331 self
332 }
333
334 pub fn add_submit(mut self, combo: KeyCombo) -> Self {
336 self.submit.push(combo);
337 self
338 }
339
340 pub fn add_interrupt(mut self, combo: KeyCombo) -> Self {
342 self.interrupt.push(combo);
343 self
344 }
345
346 pub fn add_enter_exit_mode(mut self, combo: KeyCombo) -> Self {
348 self.enter_exit_mode.push(combo);
349 self
350 }
351
352 pub fn add_force_quit(mut self, combo: KeyCombo) -> Self {
354 self.force_quit.push(combo);
355 self
356 }
357
358 pub fn add_select(mut self, combo: KeyCombo) -> Self {
360 self.select.push(combo);
361 self
362 }
363
364 pub fn add_cancel(mut self, combo: KeyCombo) -> Self {
366 self.cancel.push(combo);
367 self
368 }
369}
370
371#[cfg(test)]
372mod tests {
373 use super::*;
374
375 #[test]
376 fn test_emacs_bindings() {
377 let bindings = KeyBindings::emacs();
378
379 let ctrl_p = KeyCombo::ctrl('p');
381 assert!(bindings.move_up.contains(&ctrl_p));
382
383 let up = KeyCombo::key(KeyCode::Up);
385 assert!(bindings.move_up.contains(&up));
386
387 assert!(bindings.quit.is_empty());
389 }
390
391 #[test]
392 fn test_minimal_bindings() {
393 let bindings = KeyBindings::minimal();
394
395 let esc = KeyCombo::key(KeyCode::Esc);
397 assert!(bindings.quit.contains(&esc));
398
399 let ctrl_p = KeyCombo::ctrl('p');
401 assert!(!bindings.move_up.contains(&ctrl_p));
402 }
403
404 #[test]
405 fn test_builder_with_methods() {
406 let bindings = KeyBindings::minimal()
408 .with_quit(vec![KeyCombo::ctrl('w')])
409 .with_submit(vec![KeyCombo::key(KeyCode::Enter), KeyCombo::ctrl('m')]);
410
411 assert_eq!(bindings.quit.len(), 1);
413 assert!(bindings.quit.contains(&KeyCombo::ctrl('w')));
414
415 assert_eq!(bindings.submit.len(), 2);
417 assert!(bindings.submit.contains(&KeyCombo::key(KeyCode::Enter)));
418 assert!(bindings.submit.contains(&KeyCombo::ctrl('m')));
419 }
420
421 #[test]
422 fn test_builder_without_methods() {
423 let bindings = KeyBindings::emacs().without_exit_mode().without_kill_line();
425
426 assert!(bindings.enter_exit_mode.is_empty());
428
429 assert!(bindings.kill_line.is_empty());
431
432 assert!(!bindings.move_up.is_empty());
434 assert!(!bindings.submit.is_empty());
435 }
436
437 #[test]
438 fn test_builder_add_methods() {
439 let bindings = KeyBindings::minimal()
441 .add_quit(KeyCombo::ctrl('c'))
442 .add_submit(KeyCombo::ctrl('s'));
443
444 assert!(bindings.quit.contains(&KeyCombo::key(KeyCode::Esc)));
446 assert!(bindings.quit.contains(&KeyCombo::ctrl('c')));
447
448 assert!(bindings.submit.contains(&KeyCombo::key(KeyCode::Enter)));
450 assert!(bindings.submit.contains(&KeyCombo::ctrl('s')));
451 }
452
453 #[test]
454 fn test_builder_chaining() {
455 let bindings = KeyBindings::minimal()
457 .with_move_up(vec![KeyCombo::key(KeyCode::Up), KeyCombo::ctrl('p')])
458 .with_move_down(vec![KeyCombo::key(KeyCode::Down), KeyCombo::ctrl('n')])
459 .without_quit()
460 .with_enter_exit_mode(vec![KeyCombo::ctrl('d')])
461 .with_exit_timeout_secs(5)
462 .add_force_quit(KeyCombo::ctrl('c'));
463
464 assert!(bindings.move_up.contains(&KeyCombo::ctrl('p')));
466 assert!(bindings.move_down.contains(&KeyCombo::ctrl('n')));
467 assert!(bindings.quit.is_empty());
468 assert!(bindings.enter_exit_mode.contains(&KeyCombo::ctrl('d')));
469 assert_eq!(bindings.exit_timeout_secs, 5);
470 assert!(bindings.force_quit.contains(&KeyCombo::ctrl('c')));
471 }
472}