1use crossterm::event::{KeyCode, KeyEvent, KeyModifiers};
4
5use super::bindings::KeyBindings;
6use super::exit::ExitState;
7use super::types::{AppKeyAction, AppKeyResult, KeyCombo, KeyContext};
8
9pub type KeyHookFn = Box<dyn Fn(&KeyEvent, &KeyContext) -> Option<AppKeyResult> + Send>;
11
12pub trait KeyHandler: Send + 'static {
29 fn handle_key(&mut self, key: KeyEvent, context: &KeyContext) -> AppKeyResult;
40
41 fn status_hint(&self) -> Option<String> {
46 None
47 }
48
49 fn bindings(&self) -> &KeyBindings;
54}
55
56pub struct DefaultKeyHandler {
78 bindings: KeyBindings,
79 exit_state: ExitState,
80 custom_bindings: Vec<(KeyCombo, Box<dyn Fn() -> AppKeyAction + Send + Sync>)>,
81}
82
83impl DefaultKeyHandler {
84 pub fn new(bindings: KeyBindings) -> Self {
86 Self {
87 bindings,
88 exit_state: ExitState::default(),
89 custom_bindings: Vec::new(),
90 }
91 }
92
93 pub fn bindings(&self) -> &KeyBindings {
95 &self.bindings
96 }
97
98 pub fn with_custom_binding<F>(mut self, combo: KeyCombo, action_fn: F) -> Self
120 where
121 F: Fn() -> AppKeyAction + Send + Sync + 'static,
122 {
123 self.custom_bindings.push((combo, Box::new(action_fn)));
124 self
125 }
126
127 fn is_exit_key(&self, key: &KeyEvent) -> bool {
129 KeyBindings::matches_any(&self.bindings.enter_exit_mode, key)
130 }
131
132 fn check_custom_binding(&self, key: &KeyEvent) -> Option<AppKeyAction> {
134 for (combo, action_fn) in &self.custom_bindings {
135 if combo.matches(key) {
136 return Some(action_fn());
137 }
138 }
139 None
140 }
141}
142
143impl Default for DefaultKeyHandler {
144 fn default() -> Self {
145 Self::new(KeyBindings::default())
146 }
147}
148
149impl KeyHandler for DefaultKeyHandler {
150 fn handle_key(&mut self, key: KeyEvent, context: &KeyContext) -> AppKeyResult {
151 if self.exit_state.is_expired() {
153 self.exit_state.reset();
154 }
155
156 if context.widget_blocking {
159 if KeyBindings::matches_any(&self.bindings.force_quit, &key) {
161 return AppKeyResult::Action(AppKeyAction::Quit);
162 }
163 return AppKeyResult::NotHandled;
165 }
166
167 if context.is_processing {
169 if KeyBindings::matches_any(&self.bindings.interrupt, &key) {
170 return AppKeyResult::Action(AppKeyAction::Interrupt);
171 }
172 if KeyBindings::matches_any(&self.bindings.force_quit, &key) {
173 return AppKeyResult::Action(AppKeyAction::Quit);
174 }
175 if self.is_exit_key(&key) {
177 if self.exit_state.is_awaiting() {
178 self.exit_state.reset();
179 return AppKeyResult::Action(AppKeyAction::RequestExit);
180 } else if context.input_empty {
181 self.exit_state =
182 ExitState::awaiting_confirmation(self.bindings.exit_timeout_secs);
183 return AppKeyResult::Handled;
184 }
185 }
186 return AppKeyResult::Handled;
188 }
189
190 if self.exit_state.is_awaiting() {
192 if self.is_exit_key(&key) {
193 self.exit_state.reset();
194 return AppKeyResult::Action(AppKeyAction::RequestExit);
195 }
196 self.exit_state.reset();
198 }
200
201 if let Some(action) = self.check_custom_binding(&key) {
203 return AppKeyResult::Action(action);
204 }
205
206 if KeyBindings::matches_any(&self.bindings.force_quit, &key) {
208 return AppKeyResult::Action(AppKeyAction::Quit);
209 }
210 if KeyBindings::matches_any(&self.bindings.quit, &key) && context.input_empty {
211 return AppKeyResult::Action(AppKeyAction::Quit);
212 }
213 if self.is_exit_key(&key) {
214 if context.input_empty {
215 self.exit_state = ExitState::awaiting_confirmation(self.bindings.exit_timeout_secs);
217 return AppKeyResult::Handled;
218 }
219 return AppKeyResult::Action(AppKeyAction::DeleteCharAt);
221 }
222 if KeyBindings::matches_any(&self.bindings.submit, &key) {
223 return AppKeyResult::Action(AppKeyAction::Submit);
224 }
225 if KeyBindings::matches_any(&self.bindings.interrupt, &key) {
226 return AppKeyResult::Action(AppKeyAction::Interrupt);
227 }
228
229 if KeyBindings::matches_any(&self.bindings.move_up, &key) {
231 return AppKeyResult::Action(AppKeyAction::MoveUp);
232 }
233 if KeyBindings::matches_any(&self.bindings.move_down, &key) {
234 return AppKeyResult::Action(AppKeyAction::MoveDown);
235 }
236 if KeyBindings::matches_any(&self.bindings.move_left, &key) {
237 return AppKeyResult::Action(AppKeyAction::MoveLeft);
238 }
239 if KeyBindings::matches_any(&self.bindings.move_right, &key) {
240 return AppKeyResult::Action(AppKeyAction::MoveRight);
241 }
242 if KeyBindings::matches_any(&self.bindings.move_line_start, &key) {
243 return AppKeyResult::Action(AppKeyAction::MoveLineStart);
244 }
245 if KeyBindings::matches_any(&self.bindings.move_line_end, &key) {
246 return AppKeyResult::Action(AppKeyAction::MoveLineEnd);
247 }
248
249 if KeyBindings::matches_any(&self.bindings.delete_char_before, &key) {
251 return AppKeyResult::Action(AppKeyAction::DeleteCharBefore);
252 }
253 if KeyBindings::matches_any(&self.bindings.delete_char_at, &key) {
254 return AppKeyResult::Action(AppKeyAction::DeleteCharAt);
255 }
256 if KeyBindings::matches_any(&self.bindings.kill_line, &key) {
257 return AppKeyResult::Action(AppKeyAction::KillLine);
258 }
259 if KeyBindings::matches_any(&self.bindings.insert_newline, &key) {
260 return AppKeyResult::Action(AppKeyAction::InsertNewline);
261 }
262
263 if let KeyCode::Char(c) = key.code
265 && (key.modifiers.is_empty() || key.modifiers == KeyModifiers::SHIFT)
266 {
267 return AppKeyResult::Action(AppKeyAction::InsertChar(c));
268 }
269
270 AppKeyResult::NotHandled
272 }
273
274 fn status_hint(&self) -> Option<String> {
275 if self.exit_state.is_awaiting() {
276 Some("Press again to exit".to_string())
277 } else {
278 None
279 }
280 }
281
282 fn bindings(&self) -> &KeyBindings {
283 &self.bindings
284 }
285}
286
287pub struct ComposedKeyHandler<H: KeyHandler> {
310 inner: H,
311 pre_hooks: Vec<KeyHookFn>,
312}
313
314impl<H: KeyHandler> ComposedKeyHandler<H> {
315 pub fn new(inner: H) -> Self {
317 Self {
318 inner,
319 pre_hooks: Vec::new(),
320 }
321 }
322
323 pub fn with_pre_hook<F>(mut self, hook: F) -> Self
354 where
355 F: Fn(&KeyEvent, &KeyContext) -> Option<AppKeyResult> + Send + 'static,
356 {
357 self.pre_hooks.push(Box::new(hook));
358 self
359 }
360
361 pub fn inner(&self) -> &H {
363 &self.inner
364 }
365
366 pub fn inner_mut(&mut self) -> &mut H {
368 &mut self.inner
369 }
370}
371
372impl<H: KeyHandler> KeyHandler for ComposedKeyHandler<H> {
373 fn handle_key(&mut self, key: KeyEvent, context: &KeyContext) -> AppKeyResult {
374 for hook in &self.pre_hooks {
376 if let Some(result) = hook(&key, context) {
377 return result;
378 }
379 }
380
381 self.inner.handle_key(key, context)
383 }
384
385 fn status_hint(&self) -> Option<String> {
386 self.inner.status_hint()
387 }
388
389 fn bindings(&self) -> &KeyBindings {
390 self.inner.bindings()
391 }
392}
393
394#[cfg(test)]
395mod tests {
396 use super::*;
397
398 #[test]
399 fn test_default_handler_force_quit_in_modal() {
400 let mut handler = DefaultKeyHandler::default();
401 let context = KeyContext {
402 input_empty: true,
403 is_processing: false,
404 widget_blocking: true, };
406
407 let key = KeyEvent::new(KeyCode::Char('q'), KeyModifiers::CONTROL);
409 let result = handler.handle_key(key, &context);
410 assert_eq!(result, AppKeyResult::Action(AppKeyAction::Quit));
411
412 let esc = KeyEvent::new(KeyCode::Esc, KeyModifiers::NONE);
414 let result = handler.handle_key(esc, &context);
415 assert_eq!(result, AppKeyResult::NotHandled);
416 }
417
418 #[test]
419 fn test_emacs_handler_processing_mode() {
420 let mut handler = DefaultKeyHandler::new(KeyBindings::emacs());
422 let context = KeyContext {
423 input_empty: true,
424 is_processing: true, widget_blocking: false,
426 };
427
428 let esc = KeyEvent::new(KeyCode::Esc, KeyModifiers::NONE);
430 let result = handler.handle_key(esc, &context);
431 assert_eq!(result, AppKeyResult::Action(AppKeyAction::Interrupt));
432
433 let a = KeyEvent::new(KeyCode::Char('a'), KeyModifiers::NONE);
435 let result = handler.handle_key(a, &context);
436 assert_eq!(result, AppKeyResult::Handled);
437 }
438
439 #[test]
440 fn test_emacs_handler_exit_mode() {
441 let mut handler = DefaultKeyHandler::new(KeyBindings::emacs());
443 let context = KeyContext {
444 input_empty: true,
445 is_processing: false,
446 widget_blocking: false,
447 };
448
449 let ctrl_d = KeyEvent::new(KeyCode::Char('d'), KeyModifiers::CONTROL);
451 let result = handler.handle_key(ctrl_d, &context);
452 assert_eq!(result, AppKeyResult::Handled);
453
454 assert!(handler.status_hint().is_some());
456
457 let result = handler.handle_key(ctrl_d, &context);
459 assert_eq!(result, AppKeyResult::Action(AppKeyAction::RequestExit));
460
461 assert!(handler.status_hint().is_none());
463 }
464
465 #[test]
466 fn test_minimal_handler_quit() {
467 let mut handler = DefaultKeyHandler::default(); let context = KeyContext {
469 input_empty: true,
470 is_processing: false,
471 widget_blocking: false,
472 };
473
474 let esc = KeyEvent::new(KeyCode::Esc, KeyModifiers::NONE);
476 let result = handler.handle_key(esc, &context);
477 assert_eq!(result, AppKeyResult::Action(AppKeyAction::Quit));
478 }
479
480 #[test]
481 fn test_default_handler_char_input() {
482 let mut handler = DefaultKeyHandler::default();
483 let context = KeyContext {
484 input_empty: true,
485 is_processing: false,
486 widget_blocking: false,
487 };
488
489 let a = KeyEvent::new(KeyCode::Char('a'), KeyModifiers::NONE);
491 let result = handler.handle_key(a, &context);
492 assert_eq!(result, AppKeyResult::Action(AppKeyAction::InsertChar('a')));
493
494 let shift_a = KeyEvent::new(KeyCode::Char('A'), KeyModifiers::SHIFT);
496 let result = handler.handle_key(shift_a, &context);
497 assert_eq!(result, AppKeyResult::Action(AppKeyAction::InsertChar('A')));
498 }
499
500 #[test]
501 fn test_exit_mode_cancelled_by_other_key() {
502 let mut handler = DefaultKeyHandler::new(KeyBindings::emacs());
504 let context = KeyContext {
505 input_empty: true,
506 is_processing: false,
507 widget_blocking: false,
508 };
509
510 let ctrl_d = KeyEvent::new(KeyCode::Char('d'), KeyModifiers::CONTROL);
512 let result = handler.handle_key(ctrl_d, &context);
513 assert_eq!(result, AppKeyResult::Handled);
514 assert!(handler.status_hint().is_some());
515
516 let a = KeyEvent::new(KeyCode::Char('a'), KeyModifiers::NONE);
518 let result = handler.handle_key(a, &context);
519 assert_eq!(result, AppKeyResult::Action(AppKeyAction::InsertChar('a')));
520
521 assert!(handler.status_hint().is_none());
523 }
524
525 #[test]
526 fn test_custom_binding_basic() {
527 let mut handler = DefaultKeyHandler::new(KeyBindings::emacs())
529 .with_custom_binding(KeyCombo::ctrl('t'), || AppKeyAction::custom("toggle"));
530
531 let context = KeyContext {
532 input_empty: true,
533 is_processing: false,
534 widget_blocking: false,
535 };
536
537 let ctrl_t = KeyEvent::new(KeyCode::Char('t'), KeyModifiers::CONTROL);
539 let result = handler.handle_key(ctrl_t, &context);
540
541 if let AppKeyResult::Action(AppKeyAction::Custom(any)) = result {
542 assert!(any.downcast_ref::<&str>().is_some());
543 } else {
544 panic!("Expected Custom action, got {:?}", result);
545 }
546 }
547
548 #[test]
549 fn test_custom_binding_overrides_standard() {
550 let mut handler = DefaultKeyHandler::new(KeyBindings::emacs())
552 .with_custom_binding(KeyCombo::ctrl('p'), || AppKeyAction::custom("custom_up"));
553
554 let context = KeyContext {
555 input_empty: true,
556 is_processing: false,
557 widget_blocking: false,
558 };
559
560 let ctrl_p = KeyEvent::new(KeyCode::Char('p'), KeyModifiers::CONTROL);
561 let result = handler.handle_key(ctrl_p, &context);
562
563 if let AppKeyResult::Action(AppKeyAction::Custom(_)) = result {
565 } else {
567 panic!(
568 "Expected Custom action to override MoveUp, got {:?}",
569 result
570 );
571 }
572 }
573
574 #[test]
575 fn test_composed_handler_basic() {
576 let base = DefaultKeyHandler::new(KeyBindings::minimal());
577 let mut composed = ComposedKeyHandler::new(base);
578
579 let context = KeyContext {
580 input_empty: true,
581 is_processing: false,
582 widget_blocking: false,
583 };
584
585 let esc = KeyEvent::new(KeyCode::Esc, KeyModifiers::NONE);
587 let result = composed.handle_key(esc, &context);
588 assert_eq!(result, AppKeyResult::Action(AppKeyAction::Quit));
589 }
590
591 #[test]
592 fn test_composed_handler_pre_hook_intercepts() {
593 let base = DefaultKeyHandler::new(KeyBindings::minimal());
594 let mut composed = ComposedKeyHandler::new(base).with_pre_hook(|key, _ctx| {
595 if key.code == KeyCode::F(1) {
597 return Some(AppKeyResult::Action(AppKeyAction::custom("help")));
598 }
599 None
600 });
601
602 let context = KeyContext {
603 input_empty: true,
604 is_processing: false,
605 widget_blocking: false,
606 };
607
608 let f1 = KeyEvent::new(KeyCode::F(1), KeyModifiers::NONE);
610 let result = composed.handle_key(f1, &context);
611
612 if let AppKeyResult::Action(AppKeyAction::Custom(_)) = result {
613 } else {
615 panic!("Expected hook to intercept F1, got {:?}", result);
616 }
617
618 let esc = KeyEvent::new(KeyCode::Esc, KeyModifiers::NONE);
620 let result = composed.handle_key(esc, &context);
621 assert_eq!(result, AppKeyResult::Action(AppKeyAction::Quit));
622 }
623
624 #[test]
625 fn test_composed_handler_multiple_hooks() {
626 let base = DefaultKeyHandler::new(KeyBindings::minimal());
627 let mut composed = ComposedKeyHandler::new(base)
628 .with_pre_hook(|key, _ctx| {
629 if key.code == KeyCode::F(1) {
631 return Some(AppKeyResult::Action(AppKeyAction::custom("first")));
632 }
633 None
634 })
635 .with_pre_hook(|key, _ctx| {
636 if key.code == KeyCode::F(2) {
638 return Some(AppKeyResult::Action(AppKeyAction::custom("second")));
639 }
640 if key.code == KeyCode::F(1) {
641 return Some(AppKeyResult::Action(AppKeyAction::custom("should_not_see")));
642 }
643 None
644 });
645
646 let context = KeyContext {
647 input_empty: true,
648 is_processing: false,
649 widget_blocking: false,
650 };
651
652 let f1 = KeyEvent::new(KeyCode::F(1), KeyModifiers::NONE);
654 let result = composed.handle_key(f1, &context);
655 if let AppKeyResult::Action(AppKeyAction::Custom(any)) = result {
656 let s = any.downcast_ref::<&str>().unwrap();
657 assert_eq!(*s, "first");
658 } else {
659 panic!("Expected first hook to handle F1");
660 }
661
662 let f2 = KeyEvent::new(KeyCode::F(2), KeyModifiers::NONE);
664 let result = composed.handle_key(f2, &context);
665 if let AppKeyResult::Action(AppKeyAction::Custom(any)) = result {
666 let s = any.downcast_ref::<&str>().unwrap();
667 assert_eq!(*s, "second");
668 } else {
669 panic!("Expected second hook to handle F2");
670 }
671 }
672
673 #[test]
674 fn test_composed_handler_status_hint() {
675 let mut base = DefaultKeyHandler::new(KeyBindings::emacs());
677
678 let context = KeyContext {
680 input_empty: true,
681 is_processing: false,
682 widget_blocking: false,
683 };
684 let ctrl_d = KeyEvent::new(KeyCode::Char('d'), KeyModifiers::CONTROL);
685 base.handle_key(ctrl_d, &context);
686
687 let composed = ComposedKeyHandler::new(base);
689
690 assert!(composed.status_hint().is_some());
692 assert!(composed.status_hint().unwrap().contains("exit"));
693 }
694
695 #[test]
696 fn test_composed_handler_inner_access() {
697 let base = DefaultKeyHandler::new(KeyBindings::emacs());
698 let mut composed = ComposedKeyHandler::new(base);
699
700 assert!(composed.inner().status_hint().is_none());
702
703 let context = KeyContext {
705 input_empty: true,
706 is_processing: false,
707 widget_blocking: false,
708 };
709 let ctrl_d = KeyEvent::new(KeyCode::Char('d'), KeyModifiers::CONTROL);
710 composed.inner_mut().handle_key(ctrl_d, &context);
711
712 assert!(composed.inner().status_hint().is_some());
714 }
715}