1use crossterm::event::{KeyCode, KeyEvent, KeyModifiers};
4
5use super::bindings::KeyBindings;
6use super::exit::ExitState;
7use super::types::{AppKeyAction, AppKeyResult, KeyCombo, KeyContext};
8
9pub trait KeyHandler: Send + 'static {
26 fn handle_key(&mut self, key: KeyEvent, context: &KeyContext) -> AppKeyResult;
37
38 fn status_hint(&self) -> Option<String> {
43 None
44 }
45
46 fn bindings(&self) -> &KeyBindings;
51}
52
53pub struct DefaultKeyHandler {
75 bindings: KeyBindings,
76 exit_state: ExitState,
77 custom_bindings: Vec<(KeyCombo, Box<dyn Fn() -> AppKeyAction + Send + Sync>)>,
78}
79
80impl DefaultKeyHandler {
81 pub fn new(bindings: KeyBindings) -> Self {
83 Self {
84 bindings,
85 exit_state: ExitState::default(),
86 custom_bindings: Vec::new(),
87 }
88 }
89
90 pub fn bindings(&self) -> &KeyBindings {
92 &self.bindings
93 }
94
95 pub fn with_custom_binding<F>(mut self, combo: KeyCombo, action_fn: F) -> Self
117 where
118 F: Fn() -> AppKeyAction + Send + Sync + 'static,
119 {
120 self.custom_bindings.push((combo, Box::new(action_fn)));
121 self
122 }
123
124 fn is_exit_key(&self, key: &KeyEvent) -> bool {
126 KeyBindings::matches_any(&self.bindings.enter_exit_mode, key)
127 }
128
129 fn check_custom_binding(&self, key: &KeyEvent) -> Option<AppKeyAction> {
131 for (combo, action_fn) in &self.custom_bindings {
132 if combo.matches(key) {
133 return Some(action_fn());
134 }
135 }
136 None
137 }
138}
139
140impl Default for DefaultKeyHandler {
141 fn default() -> Self {
142 Self::new(KeyBindings::default())
143 }
144}
145
146impl KeyHandler for DefaultKeyHandler {
147 fn handle_key(&mut self, key: KeyEvent, context: &KeyContext) -> AppKeyResult {
148 if self.exit_state.is_expired() {
150 self.exit_state.reset();
151 }
152
153 if context.widget_blocking {
156 if KeyBindings::matches_any(&self.bindings.force_quit, &key) {
158 return AppKeyResult::Action(AppKeyAction::Quit);
159 }
160 return AppKeyResult::NotHandled;
162 }
163
164 if context.is_processing {
166 if KeyBindings::matches_any(&self.bindings.interrupt, &key) {
167 return AppKeyResult::Action(AppKeyAction::Interrupt);
168 }
169 if KeyBindings::matches_any(&self.bindings.force_quit, &key) {
170 return AppKeyResult::Action(AppKeyAction::Quit);
171 }
172 if self.is_exit_key(&key) {
174 if self.exit_state.is_awaiting() {
175 self.exit_state.reset();
176 return AppKeyResult::Action(AppKeyAction::RequestExit);
177 } else if context.input_empty {
178 self.exit_state = ExitState::awaiting_confirmation(
179 self.bindings.exit_timeout_secs,
180 );
181 return AppKeyResult::Handled;
182 }
183 }
184 return AppKeyResult::Handled;
186 }
187
188 if self.exit_state.is_awaiting() {
190 if self.is_exit_key(&key) {
191 self.exit_state.reset();
192 return AppKeyResult::Action(AppKeyAction::RequestExit);
193 }
194 self.exit_state.reset();
196 }
198
199 if let Some(action) = self.check_custom_binding(&key) {
201 return AppKeyResult::Action(action);
202 }
203
204 if KeyBindings::matches_any(&self.bindings.force_quit, &key) {
206 return AppKeyResult::Action(AppKeyAction::Quit);
207 }
208 if KeyBindings::matches_any(&self.bindings.quit, &key) && context.input_empty {
209 return AppKeyResult::Action(AppKeyAction::Quit);
210 }
211 if self.is_exit_key(&key) {
212 if context.input_empty {
213 self.exit_state = ExitState::awaiting_confirmation(
215 self.bindings.exit_timeout_secs,
216 );
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 if key.modifiers.is_empty() || key.modifiers == KeyModifiers::SHIFT {
266 return AppKeyResult::Action(AppKeyAction::InsertChar(c));
267 }
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<Box<dyn Fn(&KeyEvent, &KeyContext) -> Option<AppKeyResult> + Send>>,
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_bare_minimum_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'), || {
530 AppKeyAction::custom("toggle")
531 });
532
533 let context = KeyContext {
534 input_empty: true,
535 is_processing: false,
536 widget_blocking: false,
537 };
538
539 let ctrl_t = KeyEvent::new(KeyCode::Char('t'), KeyModifiers::CONTROL);
541 let result = handler.handle_key(ctrl_t, &context);
542
543 if let AppKeyResult::Action(AppKeyAction::Custom(any)) = result {
544 assert!(any.downcast_ref::<&str>().is_some());
545 } else {
546 panic!("Expected Custom action, got {:?}", result);
547 }
548 }
549
550 #[test]
551 fn test_custom_binding_overrides_standard() {
552 let mut handler = DefaultKeyHandler::new(KeyBindings::emacs())
554 .with_custom_binding(KeyCombo::ctrl('p'), || {
555 AppKeyAction::custom("custom_up")
556 });
557
558 let context = KeyContext {
559 input_empty: true,
560 is_processing: false,
561 widget_blocking: false,
562 };
563
564 let ctrl_p = KeyEvent::new(KeyCode::Char('p'), KeyModifiers::CONTROL);
565 let result = handler.handle_key(ctrl_p, &context);
566
567 if let AppKeyResult::Action(AppKeyAction::Custom(_)) = result {
569 } else {
571 panic!("Expected Custom action to override MoveUp, got {:?}", result);
572 }
573 }
574
575 #[test]
576 fn test_composed_handler_basic() {
577 let base = DefaultKeyHandler::new(KeyBindings::minimal());
578 let mut composed = ComposedKeyHandler::new(base);
579
580 let context = KeyContext {
581 input_empty: true,
582 is_processing: false,
583 widget_blocking: false,
584 };
585
586 let esc = KeyEvent::new(KeyCode::Esc, KeyModifiers::NONE);
588 let result = composed.handle_key(esc, &context);
589 assert_eq!(result, AppKeyResult::Action(AppKeyAction::Quit));
590 }
591
592 #[test]
593 fn test_composed_handler_pre_hook_intercepts() {
594 let base = DefaultKeyHandler::new(KeyBindings::minimal());
595 let mut composed = ComposedKeyHandler::new(base)
596 .with_pre_hook(|key, _ctx| {
597 if key.code == KeyCode::F(1) {
599 return Some(AppKeyResult::Action(AppKeyAction::custom("help")));
600 }
601 None
602 });
603
604 let context = KeyContext {
605 input_empty: true,
606 is_processing: false,
607 widget_blocking: false,
608 };
609
610 let f1 = KeyEvent::new(KeyCode::F(1), KeyModifiers::NONE);
612 let result = composed.handle_key(f1, &context);
613
614 if let AppKeyResult::Action(AppKeyAction::Custom(_)) = result {
615 } else {
617 panic!("Expected hook to intercept F1, got {:?}", result);
618 }
619
620 let esc = KeyEvent::new(KeyCode::Esc, KeyModifiers::NONE);
622 let result = composed.handle_key(esc, &context);
623 assert_eq!(result, AppKeyResult::Action(AppKeyAction::Quit));
624 }
625
626 #[test]
627 fn test_composed_handler_multiple_hooks() {
628 let base = DefaultKeyHandler::new(KeyBindings::minimal());
629 let mut composed = ComposedKeyHandler::new(base)
630 .with_pre_hook(|key, _ctx| {
631 if key.code == KeyCode::F(1) {
633 return Some(AppKeyResult::Action(AppKeyAction::custom("first")));
634 }
635 None
636 })
637 .with_pre_hook(|key, _ctx| {
638 if key.code == KeyCode::F(2) {
640 return Some(AppKeyResult::Action(AppKeyAction::custom("second")));
641 }
642 if key.code == KeyCode::F(1) {
643 return Some(AppKeyResult::Action(AppKeyAction::custom("should_not_see")));
644 }
645 None
646 });
647
648 let context = KeyContext {
649 input_empty: true,
650 is_processing: false,
651 widget_blocking: false,
652 };
653
654 let f1 = KeyEvent::new(KeyCode::F(1), KeyModifiers::NONE);
656 let result = composed.handle_key(f1, &context);
657 if let AppKeyResult::Action(AppKeyAction::Custom(any)) = result {
658 let s = any.downcast_ref::<&str>().unwrap();
659 assert_eq!(*s, "first");
660 } else {
661 panic!("Expected first hook to handle F1");
662 }
663
664 let f2 = KeyEvent::new(KeyCode::F(2), KeyModifiers::NONE);
666 let result = composed.handle_key(f2, &context);
667 if let AppKeyResult::Action(AppKeyAction::Custom(any)) = result {
668 let s = any.downcast_ref::<&str>().unwrap();
669 assert_eq!(*s, "second");
670 } else {
671 panic!("Expected second hook to handle F2");
672 }
673 }
674
675 #[test]
676 fn test_composed_handler_status_hint() {
677 let mut base = DefaultKeyHandler::new(KeyBindings::emacs());
679
680 let context = KeyContext {
682 input_empty: true,
683 is_processing: false,
684 widget_blocking: false,
685 };
686 let ctrl_d = KeyEvent::new(KeyCode::Char('d'), KeyModifiers::CONTROL);
687 base.handle_key(ctrl_d, &context);
688
689 let composed = ComposedKeyHandler::new(base);
691
692 assert!(composed.status_hint().is_some());
694 assert!(composed.status_hint().unwrap().contains("exit"));
695 }
696
697 #[test]
698 fn test_composed_handler_inner_access() {
699 let base = DefaultKeyHandler::new(KeyBindings::emacs());
700 let mut composed = ComposedKeyHandler::new(base);
701
702 assert!(composed.inner().status_hint().is_none());
704
705 let context = KeyContext {
707 input_empty: true,
708 is_processing: false,
709 widget_blocking: false,
710 };
711 let ctrl_d = KeyEvent::new(KeyCode::Char('d'), KeyModifiers::CONTROL);
712 composed.inner_mut().handle_key(ctrl_d, &context);
713
714 assert!(composed.inner().status_hint().is_some());
716 }
717}