1use crossterm::event::{KeyCode, KeyEvent, KeyModifiers};
5use std::collections::HashMap;
6
7use crate::buffer::AppMode;
8use crate::ui::input::actions::{Action, ActionContext, CursorPosition, NavigateAction};
9
10pub struct KeyMapper {
12 global_mappings: HashMap<(KeyCode, KeyModifiers), Action>,
14
15 mode_mappings: HashMap<AppMode, HashMap<(KeyCode, KeyModifiers), Action>>,
17
18 count_buffer: String,
20
21 vim_command_buffer: String,
23}
24
25impl KeyMapper {
26 #[must_use]
27 pub fn new() -> Self {
28 let mut mapper = Self {
29 global_mappings: HashMap::new(),
30 mode_mappings: HashMap::new(),
31 count_buffer: String::new(),
32 vim_command_buffer: String::new(),
33 };
34
35 mapper.init_global_mappings();
36 mapper.init_mode_mappings();
37 mapper
38 }
39
40 fn init_global_mappings(&mut self) {
42 use KeyCode::{Char, F};
43 use KeyModifiers as Mod;
44
45 self.global_mappings
47 .insert((F(1), Mod::NONE), Action::ShowHelp);
48 self.global_mappings
49 .insert((F(3), Mod::NONE), Action::ShowPrettyQuery);
50 self.global_mappings
51 .insert((F(5), Mod::NONE), Action::ShowDebugInfo);
52 self.global_mappings
53 .insert((F(6), Mod::NONE), Action::ToggleRowNumbers);
54 self.global_mappings
55 .insert((F(7), Mod::NONE), Action::ToggleCompactMode);
56 self.global_mappings
57 .insert((F(8), Mod::NONE), Action::ToggleCaseInsensitive);
58 self.global_mappings
59 .insert((F(9), Mod::NONE), Action::KillLine);
60 self.global_mappings
61 .insert((F(10), Mod::NONE), Action::KillLineBackward);
62 self.global_mappings
63 .insert((F(12), Mod::NONE), Action::ToggleKeyIndicator);
64
65 self.global_mappings
67 .insert((Char('c'), Mod::CONTROL), Action::ForceQuit);
68 self.global_mappings
69 .insert((Char('C'), Mod::CONTROL), Action::ForceQuit);
70 }
71
72 fn init_mode_mappings(&mut self) {
74 self.init_results_mappings();
75 self.init_command_mappings();
76 }
78
79 fn init_results_mappings(&mut self) {
81 use crate::buffer::AppMode;
82 use KeyCode::{Char, Down, End, Esc, Home, Left, PageDown, PageUp, Right, Up, F};
83 use KeyModifiers as Mod;
84
85 let mut mappings = HashMap::new();
86
87 mappings.insert((Up, Mod::NONE), Action::Navigate(NavigateAction::Up(1)));
89 mappings.insert((Down, Mod::NONE), Action::Navigate(NavigateAction::Down(1)));
90 mappings.insert((Left, Mod::NONE), Action::Navigate(NavigateAction::Left(1)));
91 mappings.insert(
92 (Right, Mod::NONE),
93 Action::Navigate(NavigateAction::Right(1)),
94 );
95
96 mappings.insert(
97 (PageUp, Mod::NONE),
98 Action::Navigate(NavigateAction::PageUp),
99 );
100 mappings.insert(
101 (PageDown, Mod::NONE),
102 Action::Navigate(NavigateAction::PageDown),
103 );
104
105 mappings.insert(
107 (Char('f'), Mod::CONTROL),
108 Action::Navigate(NavigateAction::PageDown),
109 );
110 mappings.insert(
111 (Char('b'), Mod::CONTROL),
112 Action::Navigate(NavigateAction::PageUp),
113 );
114
115 mappings.insert((Home, Mod::NONE), Action::Navigate(NavigateAction::Home));
116 mappings.insert((End, Mod::NONE), Action::Navigate(NavigateAction::End));
117
118 mappings.insert(
120 (Char('h'), Mod::NONE),
121 Action::Navigate(NavigateAction::Left(1)),
122 );
123 mappings.insert(
124 (Char('j'), Mod::NONE),
125 Action::Navigate(NavigateAction::Down(1)),
126 );
127 mappings.insert(
128 (Char('k'), Mod::NONE),
129 Action::Navigate(NavigateAction::Up(1)),
130 );
131 mappings.insert(
132 (Char('l'), Mod::NONE),
133 Action::Navigate(NavigateAction::Right(1)),
134 );
135
136 mappings.insert((Left, Mod::NONE), Action::Navigate(NavigateAction::Left(1)));
138 mappings.insert(
139 (Right, Mod::NONE),
140 Action::Navigate(NavigateAction::Right(1)),
141 );
142 mappings.insert((Down, Mod::NONE), Action::Navigate(NavigateAction::Down(1)));
143 mappings.insert((Up, Mod::NONE), Action::Navigate(NavigateAction::Up(1))); mappings.insert(
147 (PageUp, Mod::NONE),
148 Action::Navigate(NavigateAction::PageUp),
149 );
150 mappings.insert(
151 (PageDown, Mod::NONE),
152 Action::Navigate(NavigateAction::PageDown),
153 );
154
155 mappings.insert(
159 (Char('G'), Mod::SHIFT),
160 Action::Navigate(NavigateAction::End),
161 );
162
163 mappings.insert(
165 (Char('0'), Mod::NONE),
166 Action::Navigate(NavigateAction::FirstColumn),
167 );
168 mappings.insert(
169 (Char('^'), Mod::NONE),
170 Action::Navigate(NavigateAction::FirstColumn),
171 );
172 mappings.insert(
173 (Char('$'), Mod::NONE),
174 Action::Navigate(NavigateAction::LastColumn),
175 );
176
177 mappings.insert((Char('H'), Mod::SHIFT), Action::NavigateToViewportTop);
179 mappings.insert((Char('M'), Mod::SHIFT), Action::NavigateToViewportMiddle);
180 mappings.insert((Char('L'), Mod::SHIFT), Action::NavigateToViewportBottom);
181
182 mappings.insert((Esc, Mod::NONE), Action::ExitCurrentMode);
184 mappings.insert((Char('q'), Mod::NONE), Action::Quit);
185 mappings.insert((Char('c'), Mod::CONTROL), Action::Quit); mappings.insert((F(2), Mod::NONE), Action::SwitchMode(AppMode::Command));
189
190 mappings.insert(
192 (Char('i'), Mod::NONE),
193 Action::SwitchModeWithCursor(AppMode::Command, CursorPosition::Current),
194 );
195
196 mappings.insert(
198 (Char('a'), Mod::NONE),
199 Action::SwitchModeWithCursor(AppMode::Command, CursorPosition::End),
200 );
201
202 mappings.insert((Char('p'), Mod::NONE), Action::ToggleColumnPin);
204 mappings.insert((Char('-'), Mod::NONE), Action::HideColumn); mappings.insert(
206 (Char('H'), Mod::CONTROL | Mod::SHIFT),
207 Action::UnhideAllColumns,
208 );
209 mappings.insert((Char('+'), Mod::NONE), Action::UnhideAllColumns); mappings.insert((Char('='), Mod::NONE), Action::UnhideAllColumns); mappings.insert((Char('e'), Mod::NONE), Action::HideEmptyColumns);
213 mappings.insert((Char('E'), Mod::SHIFT), Action::HideEmptyColumns);
214 mappings.insert((Left, Mod::SHIFT), Action::MoveColumnLeft);
215 mappings.insert((Right, Mod::SHIFT), Action::MoveColumnRight);
216 mappings.insert((Char('<'), Mod::NONE), Action::MoveColumnLeft);
218 mappings.insert((Char('>'), Mod::NONE), Action::MoveColumnRight);
219 mappings.insert((Char('/'), Mod::NONE), Action::StartSearch);
221 mappings.insert((Char('\\'), Mod::NONE), Action::StartColumnSearch);
222 mappings.insert((Char('f'), Mod::NONE), Action::StartFilter);
223 mappings.insert((Char('F'), Mod::SHIFT), Action::StartFuzzyFilter);
224
225 mappings.insert((Char('s'), Mod::NONE), Action::Sort(None));
227
228 mappings.insert((Char('N'), Mod::NONE), Action::ToggleRowNumbers);
230 mappings.insert((Char('C'), Mod::NONE), Action::ToggleCompactMode);
231
232 mappings.insert((Char('x'), Mod::CONTROL), Action::ExportToCsv);
234 mappings.insert((Char('j'), Mod::CONTROL), Action::ExportToJson);
235
236 mappings.insert((Char('C'), Mod::SHIFT), Action::ClearFilter);
238
239 mappings.insert((Char(':'), Mod::NONE), Action::StartJumpToRow);
241
242 mappings.insert((Char('n'), Mod::NONE), Action::NextSearchMatch);
244 mappings.insert((Char('N'), Mod::SHIFT), Action::PreviousSearchMatch);
245
246 mappings.insert((Char('v'), Mod::NONE), Action::ToggleSelectionMode);
248
249 mappings.insert((Char('S'), Mod::SHIFT), Action::ShowColumnStatistics);
251
252 mappings.insert((Char('s'), Mod::ALT), Action::CycleColumnPacking);
254
255 mappings.insert((Char(' '), Mod::NONE), Action::ToggleViewportLock);
257 mappings.insert((Char('x'), Mod::NONE), Action::ToggleCursorLock);
258 mappings.insert((Char('X'), Mod::SHIFT), Action::ToggleCursorLock);
259 mappings.insert((Char(' '), Mod::CONTROL), Action::ToggleViewportLock);
260
261 mappings.insert((Char('?'), Mod::NONE), Action::ShowHelp); mappings.insert((Char('P'), Mod::SHIFT), Action::ClearAllPins);
267
268 mappings.insert((Char('r'), Mod::CONTROL), Action::StartHistorySearch);
270
271 self.mode_mappings.insert(AppMode::Results, mappings);
272 }
273
274 fn init_command_mappings(&mut self) {
276 use crate::buffer::AppMode;
277 use KeyCode::{Backspace, Char, Delete, Down, End, Enter, Home, Left, Right, Up, F};
278 use KeyModifiers as Mod;
279
280 let mut mappings = HashMap::new();
281
282 mappings.insert((Enter, Mod::NONE), Action::ExecuteQuery);
284
285 mappings.insert((F(2), Mod::NONE), Action::SwitchMode(AppMode::Results));
287
288 mappings.insert((Char('u'), Mod::CONTROL), Action::ClearLine);
290
291 mappings.insert((Char('z'), Mod::CONTROL), Action::Undo);
293 mappings.insert((Char('y'), Mod::CONTROL), Action::Redo);
294
295 mappings.insert((Left, Mod::NONE), Action::MoveCursorLeft);
297 mappings.insert((Right, Mod::NONE), Action::MoveCursorRight);
298 mappings.insert((Down, Mod::NONE), Action::SwitchMode(AppMode::Results)); mappings.insert((Home, Mod::NONE), Action::MoveCursorHome);
300 mappings.insert((End, Mod::NONE), Action::MoveCursorEnd);
301 mappings.insert((Char('a'), Mod::CONTROL), Action::MoveCursorHome);
302 mappings.insert((Char('e'), Mod::CONTROL), Action::MoveCursorEnd);
303 mappings.insert((Left, Mod::CONTROL), Action::MoveCursorWordLeft);
304 mappings.insert((Right, Mod::CONTROL), Action::MoveCursorWordRight);
305 mappings.insert((Char('b'), Mod::ALT), Action::MoveCursorWordLeft);
306 mappings.insert((Char('f'), Mod::ALT), Action::MoveCursorWordRight);
307 mappings.insert((Char('['), Mod::ALT), Action::JumpToPrevToken);
308 mappings.insert((Char(']'), Mod::ALT), Action::JumpToNextToken);
309 mappings.insert((Char(','), Mod::ALT), Action::JumpToPrevToken);
311 mappings.insert((Char('.'), Mod::ALT), Action::JumpToNextToken);
312
313 mappings.insert((Backspace, Mod::NONE), Action::Backspace);
315 mappings.insert((Delete, Mod::NONE), Action::Delete);
316 mappings.insert((Char('w'), Mod::CONTROL), Action::DeleteWordBackward);
317 mappings.insert((Char('d'), Mod::ALT), Action::DeleteWordForward);
318 mappings.insert((Char('k'), Mod::CONTROL), Action::KillLine);
319 mappings.insert((Char('v'), Mod::CONTROL), Action::Paste);
323
324 mappings.insert((Char('p'), Mod::CONTROL), Action::PreviousHistoryCommand);
326 mappings.insert((Char('n'), Mod::CONTROL), Action::NextHistoryCommand);
327 mappings.insert((Up, Mod::ALT), Action::PreviousHistoryCommand);
328 mappings.insert((Down, Mod::ALT), Action::NextHistoryCommand);
329
330 mappings.insert((Char('*'), Mod::CONTROL), Action::ExpandAsterisk);
332 mappings.insert((Char('*'), Mod::ALT), Action::ExpandAsteriskVisible);
333
334 self.mode_mappings.insert(AppMode::Command, mappings);
337 }
338
339 pub fn map_key(&mut self, key: KeyEvent, context: &ActionContext) -> Option<Action> {
341 if context.mode == AppMode::Results {
343 if let KeyCode::Char(c) = key.code {
344 if key.modifiers.is_empty() {
345 if !self.vim_command_buffer.is_empty() {
347 let command = format!("{}{}", self.vim_command_buffer, c);
349 let action = if command.as_str() == "gg" {
350 self.vim_command_buffer.clear();
352 Some(Action::Navigate(NavigateAction::Home))
353 } else {
354 self.vim_command_buffer.clear();
356 None
357 };
358
359 if action.is_some() {
360 return action;
361 }
362 }
363
364 if c.is_ascii_digit() {
366 self.count_buffer.push(c);
367 return None; }
369
370 if c == 'g' {
374 let key_combo = (key.code, key.modifiers);
375 if let Some(mode_mappings) = self.mode_mappings.get(&context.mode) {
376 if mode_mappings.contains_key(&key_combo) {
377 tracing::debug!(
380 "Key '{}' has standalone mapping, not treating as vim command",
381 c
382 );
383 } else {
384 self.vim_command_buffer.push(c);
386 tracing::debug!("Starting vim command buffer with '{}'", c);
387 return None; }
389 }
390 }
391 }
392 }
393 }
394
395 let action = self.map_key_internal(key, context);
397
398 if !self.count_buffer.is_empty() {
400 if let Some(mut action) = action {
401 if let Ok(count) = self.count_buffer.parse::<usize>() {
402 action = self.apply_count_to_action(action, count);
403 }
404 self.count_buffer.clear();
405 return Some(action);
406 }
407 self.count_buffer.clear();
409 }
410
411 action
412 }
413
414 fn map_key_internal(&self, key: KeyEvent, context: &ActionContext) -> Option<Action> {
416 let key_combo = (key.code, key.modifiers);
417
418 if let Some(action) = self.global_mappings.get(&key_combo) {
420 return Some(action.clone());
421 }
422
423 if let Some(mode_mappings) = self.mode_mappings.get(&context.mode) {
425 if let Some(action) = mode_mappings.get(&key_combo) {
426 return Some(action.clone());
427 }
428 }
429
430 if context.mode == AppMode::Command {
432 if let KeyCode::Char(c) = key.code {
433 if key.modifiers.is_empty() || key.modifiers == KeyModifiers::SHIFT {
434 return Some(Action::InsertChar(c));
436 }
437 }
438 }
439
440 None
442 }
443
444 fn apply_count_to_action(&self, action: Action, count: usize) -> Action {
446 match action {
447 Action::Navigate(NavigateAction::Up(_)) => Action::Navigate(NavigateAction::Up(count)),
448 Action::Navigate(NavigateAction::Down(_)) => {
449 Action::Navigate(NavigateAction::Down(count))
450 }
451 Action::Navigate(NavigateAction::Left(_)) => {
452 Action::Navigate(NavigateAction::Left(count))
453 }
454 Action::Navigate(NavigateAction::Right(_)) => {
455 Action::Navigate(NavigateAction::Right(count))
456 }
457 _ => action,
459 }
460 }
461
462 pub fn clear_pending(&mut self) {
464 self.count_buffer.clear();
465 self.vim_command_buffer.clear();
466 }
467
468 #[must_use]
470 pub fn is_collecting_count(&self) -> bool {
471 !self.count_buffer.is_empty()
472 }
473
474 #[must_use]
476 pub fn get_count_buffer(&self) -> &str {
477 &self.count_buffer
478 }
479}
480
481impl Default for KeyMapper {
482 fn default() -> Self {
483 Self::new()
484 }
485}
486
487#[cfg(test)]
488mod tests {
489 use super::*;
490 use crate::app_state_container::SelectionMode;
491
492 #[test]
493 fn test_basic_navigation_mapping() {
494 let mut mapper = KeyMapper::new();
495 let context = ActionContext {
496 mode: AppMode::Results,
497 selection_mode: SelectionMode::Row,
498 has_results: true,
499 has_filter: false,
500 has_search: false,
501 row_count: 100,
502 column_count: 10,
503 current_row: 0,
504 current_column: 0,
505 };
506
507 let key = KeyEvent::new(KeyCode::Down, KeyModifiers::NONE);
509 let action = mapper.map_key(key, &context);
510 assert_eq!(action, Some(Action::Navigate(NavigateAction::Down(1))));
511
512 let key = KeyEvent::new(KeyCode::Char('j'), KeyModifiers::NONE);
514 let action = mapper.map_key(key, &context);
515 assert_eq!(action, Some(Action::Navigate(NavigateAction::Down(1))));
516 }
517
518 #[test]
519 fn test_vim_count_motion() {
520 let mut mapper = KeyMapper::new();
521 let context = ActionContext {
522 mode: AppMode::Results,
523 selection_mode: SelectionMode::Row,
524 has_results: true,
525 has_filter: false,
526 has_search: false,
527 row_count: 100,
528 column_count: 10,
529 current_row: 0,
530 current_column: 0,
531 };
532
533 let key = KeyEvent::new(KeyCode::Char('5'), KeyModifiers::NONE);
535 let action = mapper.map_key(key, &context);
536 assert_eq!(action, None); assert_eq!(mapper.get_count_buffer(), "5");
538
539 let key = KeyEvent::new(KeyCode::Char('j'), KeyModifiers::NONE);
541 let action = mapper.map_key(key, &context);
542 assert_eq!(action, Some(Action::Navigate(NavigateAction::Down(5))));
543 assert_eq!(mapper.get_count_buffer(), ""); }
545
546 #[test]
547 fn test_global_mapping_override() {
548 let mut mapper = KeyMapper::new();
549 let context = ActionContext {
550 mode: AppMode::Results,
551 selection_mode: SelectionMode::Row,
552 has_results: true,
553 has_filter: false,
554 has_search: false,
555 row_count: 100,
556 column_count: 10,
557 current_row: 0,
558 current_column: 0,
559 };
560
561 let key = KeyEvent::new(KeyCode::F(1), KeyModifiers::NONE);
563 let action = mapper.map_key(key, &context);
564 assert_eq!(action, Some(Action::ShowHelp));
565 }
566
567 #[test]
568 fn test_command_mode_editing_actions() {
569 let mut mapper = KeyMapper::new();
570 let context = ActionContext {
571 mode: AppMode::Command,
572 selection_mode: SelectionMode::Row,
573 has_results: false,
574 has_filter: false,
575 has_search: false,
576 row_count: 0,
577 column_count: 0,
578 current_row: 0,
579 current_column: 0,
580 };
581
582 let key = KeyEvent::new(KeyCode::Char('a'), KeyModifiers::NONE);
584 let action = mapper.map_key(key, &context);
585 assert_eq!(action, Some(Action::InsertChar('a')));
586
587 let key = KeyEvent::new(KeyCode::Char('A'), KeyModifiers::SHIFT);
589 let action = mapper.map_key(key, &context);
590 assert_eq!(action, Some(Action::InsertChar('A')));
591
592 let key = KeyEvent::new(KeyCode::Backspace, KeyModifiers::NONE);
594 let action = mapper.map_key(key, &context);
595 assert_eq!(action, Some(Action::Backspace));
596
597 let key = KeyEvent::new(KeyCode::Delete, KeyModifiers::NONE);
599 let action = mapper.map_key(key, &context);
600 assert_eq!(action, Some(Action::Delete));
601
602 let key = KeyEvent::new(KeyCode::Left, KeyModifiers::NONE);
604 let action = mapper.map_key(key, &context);
605 assert_eq!(action, Some(Action::MoveCursorLeft));
606
607 let key = KeyEvent::new(KeyCode::Right, KeyModifiers::NONE);
609 let action = mapper.map_key(key, &context);
610 assert_eq!(action, Some(Action::MoveCursorRight));
611
612 let key = KeyEvent::new(KeyCode::Char('a'), KeyModifiers::CONTROL);
614 let action = mapper.map_key(key, &context);
615 assert_eq!(action, Some(Action::MoveCursorHome));
616
617 let key = KeyEvent::new(KeyCode::Char('e'), KeyModifiers::CONTROL);
619 let action = mapper.map_key(key, &context);
620 assert_eq!(action, Some(Action::MoveCursorEnd));
621
622 let key = KeyEvent::new(KeyCode::Char('u'), KeyModifiers::CONTROL);
624 let action = mapper.map_key(key, &context);
625 assert_eq!(action, Some(Action::ClearLine));
626
627 let key = KeyEvent::new(KeyCode::Char('w'), KeyModifiers::CONTROL);
629 let action = mapper.map_key(key, &context);
630 assert_eq!(action, Some(Action::DeleteWordBackward));
631
632 let key = KeyEvent::new(KeyCode::Char('z'), KeyModifiers::CONTROL);
634 let action = mapper.map_key(key, &context);
635 assert_eq!(action, Some(Action::Undo));
636
637 let key = KeyEvent::new(KeyCode::Enter, KeyModifiers::NONE);
639 let action = mapper.map_key(key, &context);
640 assert_eq!(action, Some(Action::ExecuteQuery));
641 }
642
643 #[test]
644 fn test_vim_style_append_modes() {
645 let mut mapper = KeyMapper::new();
646 let context = ActionContext {
647 mode: AppMode::Results,
648 selection_mode: SelectionMode::Row,
649 has_results: true,
650 has_filter: false,
651 has_search: false,
652 row_count: 100,
653 column_count: 10,
654 current_row: 0,
655 current_column: 0,
656 };
657
658 let key = KeyEvent::new(KeyCode::Char('i'), KeyModifiers::NONE);
660 let action = mapper.map_key(key, &context);
661 assert_eq!(
662 action,
663 Some(Action::SwitchModeWithCursor(
664 AppMode::Command,
665 CursorPosition::Current
666 ))
667 );
668
669 let key = KeyEvent::new(KeyCode::Char('a'), KeyModifiers::NONE);
671 let action = mapper.map_key(key, &context);
672 assert_eq!(
673 action,
674 Some(Action::SwitchModeWithCursor(
675 AppMode::Command,
676 CursorPosition::End
677 ))
678 );
679
680 }
684
685 #[test]
686 fn test_sort_key_mapping() {
687 let mut mapper = KeyMapper::new();
688 let context = ActionContext {
689 mode: AppMode::Results,
690 selection_mode: SelectionMode::Row,
691 has_results: true,
692 has_filter: false,
693 has_search: false,
694 row_count: 100,
695 column_count: 10,
696 current_row: 0,
697 current_column: 0,
698 };
699
700 let key = KeyEvent::new(KeyCode::Char('s'), KeyModifiers::NONE);
702 let action = mapper.map_key(key, &context);
703 assert_eq!(action, Some(Action::Sort(None)));
704 }
705
706 #[test]
707 fn test_vim_go_to_top() {
708 let mut mapper = KeyMapper::new();
709 let context = ActionContext {
710 mode: AppMode::Results,
711 selection_mode: SelectionMode::Row,
712 has_results: true,
713 has_filter: false,
714 has_search: false,
715 row_count: 100,
716 column_count: 10,
717 current_row: 0,
718 current_column: 0,
719 };
720
721 let key_g1 = KeyEvent::new(KeyCode::Char('g'), KeyModifiers::NONE);
723 let action_g1 = mapper.map_key(key_g1, &context);
724 assert_eq!(action_g1, None); let key_g2 = KeyEvent::new(KeyCode::Char('g'), KeyModifiers::NONE);
727 let action_gg = mapper.map_key(key_g2, &context);
728 assert_eq!(action_gg, Some(Action::Navigate(NavigateAction::Home)));
729 }
730
731 #[test]
732 fn test_bug_reproduction_s_key_not_found() {
733 let mut mapper = KeyMapper::new();
735 let context = ActionContext {
736 mode: AppMode::Results,
737 selection_mode: SelectionMode::Row,
738 has_results: true,
739 has_filter: false,
740 has_search: false,
741 row_count: 100,
742 column_count: 10,
743 current_row: 0,
744 current_column: 0,
745 };
746
747 let key = KeyEvent::new(KeyCode::Char('s'), KeyModifiers::NONE);
750 let action = mapper.map_key(key, &context);
751
752 assert!(
754 action.is_some(),
755 "Bug reproduction: 's' key should map to an action, not return None"
756 );
757 assert_eq!(
758 action,
759 Some(Action::Sort(None)),
760 "Bug reproduction: 's' key should map to Sort action"
761 );
762 }
763}