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![KeyCombo::key(KeyCode::Enter), KeyCombo::key(KeyCode::Char(' '))],
101 cancel: vec![KeyCombo::key(KeyCode::Esc)],
102 }
103 }
104
105 pub fn minimal() -> Self {
112 Self {
113 move_up: vec![KeyCombo::key(KeyCode::Up)],
114 move_down: vec![KeyCombo::key(KeyCode::Down)],
115 move_left: vec![KeyCombo::key(KeyCode::Left)],
116 move_right: vec![KeyCombo::key(KeyCode::Right)],
117 move_line_start: vec![KeyCombo::key(KeyCode::Home)],
118 move_line_end: vec![KeyCombo::key(KeyCode::End)],
119
120 delete_char_before: vec![KeyCombo::key(KeyCode::Backspace)],
121 delete_char_at: vec![KeyCombo::key(KeyCode::Delete)],
122 kill_line: vec![],
123 insert_newline: vec![KeyCombo::ctrl('j')],
124
125 submit: vec![KeyCombo::key(KeyCode::Enter)],
126 interrupt: vec![],
127 quit: vec![KeyCombo::key(KeyCode::Esc)], force_quit: vec![KeyCombo::ctrl('q')], enter_exit_mode: vec![],
130 exit_timeout_secs: DEFAULT_EXIT_TIMEOUT_SECS,
131
132 select: vec![KeyCombo::key(KeyCode::Enter), KeyCombo::key(KeyCode::Char(' '))],
133 cancel: vec![KeyCombo::key(KeyCode::Esc)],
134 }
135 }
136
137 pub(crate) fn matches_any(combos: &[KeyCombo], event: &KeyEvent) -> bool {
139 combos.iter().any(|combo| combo.matches(event))
140 }
141
142 pub fn with_move_up(mut self, combos: Vec<KeyCombo>) -> Self {
148 self.move_up = combos;
149 self
150 }
151
152 pub fn with_move_down(mut self, combos: Vec<KeyCombo>) -> Self {
154 self.move_down = combos;
155 self
156 }
157
158 pub fn with_move_left(mut self, combos: Vec<KeyCombo>) -> Self {
160 self.move_left = combos;
161 self
162 }
163
164 pub fn with_move_right(mut self, combos: Vec<KeyCombo>) -> Self {
166 self.move_right = combos;
167 self
168 }
169
170 pub fn with_move_line_start(mut self, combos: Vec<KeyCombo>) -> Self {
172 self.move_line_start = combos;
173 self
174 }
175
176 pub fn with_move_line_end(mut self, combos: Vec<KeyCombo>) -> Self {
178 self.move_line_end = combos;
179 self
180 }
181
182 pub fn with_delete_char_before(mut self, combos: Vec<KeyCombo>) -> Self {
184 self.delete_char_before = combos;
185 self
186 }
187
188 pub fn with_delete_char_at(mut self, combos: Vec<KeyCombo>) -> Self {
190 self.delete_char_at = combos;
191 self
192 }
193
194 pub fn with_kill_line(mut self, combos: Vec<KeyCombo>) -> Self {
196 self.kill_line = combos;
197 self
198 }
199
200 pub fn with_insert_newline(mut self, combos: Vec<KeyCombo>) -> Self {
202 self.insert_newline = combos;
203 self
204 }
205
206 pub fn with_submit(mut self, combos: Vec<KeyCombo>) -> Self {
208 self.submit = combos;
209 self
210 }
211
212 pub fn with_interrupt(mut self, combos: Vec<KeyCombo>) -> Self {
214 self.interrupt = combos;
215 self
216 }
217
218 pub fn with_quit(mut self, combos: Vec<KeyCombo>) -> Self {
220 self.quit = combos;
221 self
222 }
223
224 pub fn with_force_quit(mut self, combos: Vec<KeyCombo>) -> Self {
226 self.force_quit = combos;
227 self
228 }
229
230 pub fn with_enter_exit_mode(mut self, combos: Vec<KeyCombo>) -> Self {
232 self.enter_exit_mode = combos;
233 self
234 }
235
236 pub fn with_exit_timeout_secs(mut self, secs: u64) -> Self {
238 self.exit_timeout_secs = secs;
239 self
240 }
241
242 pub fn with_select(mut self, combos: Vec<KeyCombo>) -> Self {
244 self.select = combos;
245 self
246 }
247
248 pub fn with_cancel(mut self, combos: Vec<KeyCombo>) -> Self {
250 self.cancel = combos;
251 self
252 }
253
254 pub fn without_exit_mode(mut self) -> Self {
260 self.enter_exit_mode = vec![];
261 self
262 }
263
264 pub fn without_quit(mut self) -> Self {
266 self.quit = vec![];
267 self
268 }
269
270 pub fn without_force_quit(mut self) -> Self {
272 self.force_quit = vec![];
273 self
274 }
275
276 pub fn without_interrupt(mut self) -> Self {
278 self.interrupt = vec![];
279 self
280 }
281
282 pub fn without_kill_line(mut self) -> Self {
284 self.kill_line = vec![];
285 self
286 }
287
288 pub fn without_insert_newline(mut self) -> Self {
290 self.insert_newline = vec![];
291 self
292 }
293
294 pub fn add_move_up(mut self, combo: KeyCombo) -> Self {
300 self.move_up.push(combo);
301 self
302 }
303
304 pub fn add_move_down(mut self, combo: KeyCombo) -> Self {
306 self.move_down.push(combo);
307 self
308 }
309
310 pub fn add_move_left(mut self, combo: KeyCombo) -> Self {
312 self.move_left.push(combo);
313 self
314 }
315
316 pub fn add_move_right(mut self, combo: KeyCombo) -> Self {
318 self.move_right.push(combo);
319 self
320 }
321
322 pub fn add_quit(mut self, combo: KeyCombo) -> Self {
324 self.quit.push(combo);
325 self
326 }
327
328 pub fn add_submit(mut self, combo: KeyCombo) -> Self {
330 self.submit.push(combo);
331 self
332 }
333
334 pub fn add_interrupt(mut self, combo: KeyCombo) -> Self {
336 self.interrupt.push(combo);
337 self
338 }
339
340 pub fn add_enter_exit_mode(mut self, combo: KeyCombo) -> Self {
342 self.enter_exit_mode.push(combo);
343 self
344 }
345
346 pub fn add_force_quit(mut self, combo: KeyCombo) -> Self {
348 self.force_quit.push(combo);
349 self
350 }
351
352 pub fn add_select(mut self, combo: KeyCombo) -> Self {
354 self.select.push(combo);
355 self
356 }
357
358 pub fn add_cancel(mut self, combo: KeyCombo) -> Self {
360 self.cancel.push(combo);
361 self
362 }
363}
364
365#[cfg(test)]
366mod tests {
367 use super::*;
368
369 #[test]
370 fn test_emacs_bindings() {
371 let bindings = KeyBindings::emacs();
372
373 let ctrl_p = KeyCombo::ctrl('p');
375 assert!(bindings.move_up.contains(&ctrl_p));
376
377 let up = KeyCombo::key(KeyCode::Up);
379 assert!(bindings.move_up.contains(&up));
380
381 assert!(bindings.quit.is_empty());
383 }
384
385 #[test]
386 fn test_minimal_bindings() {
387 let bindings = KeyBindings::minimal();
388
389 let esc = KeyCombo::key(KeyCode::Esc);
391 assert!(bindings.quit.contains(&esc));
392
393 let ctrl_p = KeyCombo::ctrl('p');
395 assert!(!bindings.move_up.contains(&ctrl_p));
396 }
397
398 #[test]
399 fn test_builder_with_methods() {
400 let bindings = KeyBindings::minimal()
402 .with_quit(vec![KeyCombo::ctrl('w')])
403 .with_submit(vec![KeyCombo::key(KeyCode::Enter), KeyCombo::ctrl('m')]);
404
405 assert_eq!(bindings.quit.len(), 1);
407 assert!(bindings.quit.contains(&KeyCombo::ctrl('w')));
408
409 assert_eq!(bindings.submit.len(), 2);
411 assert!(bindings.submit.contains(&KeyCombo::key(KeyCode::Enter)));
412 assert!(bindings.submit.contains(&KeyCombo::ctrl('m')));
413 }
414
415 #[test]
416 fn test_builder_without_methods() {
417 let bindings = KeyBindings::emacs()
419 .without_exit_mode()
420 .without_kill_line();
421
422 assert!(bindings.enter_exit_mode.is_empty());
424
425 assert!(bindings.kill_line.is_empty());
427
428 assert!(!bindings.move_up.is_empty());
430 assert!(!bindings.submit.is_empty());
431 }
432
433 #[test]
434 fn test_builder_add_methods() {
435 let bindings = KeyBindings::minimal()
437 .add_quit(KeyCombo::ctrl('c'))
438 .add_submit(KeyCombo::ctrl('s'));
439
440 assert!(bindings.quit.contains(&KeyCombo::key(KeyCode::Esc)));
442 assert!(bindings.quit.contains(&KeyCombo::ctrl('c')));
443
444 assert!(bindings.submit.contains(&KeyCombo::key(KeyCode::Enter)));
446 assert!(bindings.submit.contains(&KeyCombo::ctrl('s')));
447 }
448
449 #[test]
450 fn test_builder_chaining() {
451 let bindings = KeyBindings::minimal()
453 .with_move_up(vec![KeyCombo::key(KeyCode::Up), KeyCombo::ctrl('p')])
454 .with_move_down(vec![KeyCombo::key(KeyCode::Down), KeyCombo::ctrl('n')])
455 .without_quit()
456 .with_enter_exit_mode(vec![KeyCombo::ctrl('d')])
457 .with_exit_timeout_secs(5)
458 .add_force_quit(KeyCombo::ctrl('c'));
459
460 assert!(bindings.move_up.contains(&KeyCombo::ctrl('p')));
462 assert!(bindings.move_down.contains(&KeyCombo::ctrl('n')));
463 assert!(bindings.quit.is_empty());
464 assert!(bindings.enter_exit_mode.contains(&KeyCombo::ctrl('d')));
465 assert_eq!(bindings.exit_timeout_secs, 5);
466 assert!(bindings.force_quit.contains(&KeyCombo::ctrl('c')));
467 }
468}